For this, I don’t have any idea what to use but I am thinking of using something which can be used by most of you.
- Heroku: This requires an easy setup
- Firebase: This requires some setup and this might be difficult
- Custom Server: This will be not easy for the beginner as this will include some other stack
I will go with Heroku, and I have a GitHub repo that is one-click deploy. Just deploy that on your Heroku and get try it yourself. This is something that anyone of you can do because you don’t need to write any code.
Backend
As this will require some backend and for that, I can’t keep any server as long as this article is available. So, I came up with a solution which is on click deploy. You can deploy this API using a single click, no setup is required.
You just need one free Heroku account and you have a running API. No Credit Card is required for a free Heroku account.
Testing Backend
To Test the backend API you first need to find the URL of your deployed Heroku app. You can find an Open App
button at the right top corner of your Heroku app dashboard.
Let’s consider that your app URL is https://pin-code-api.herokuapp.com/
then you will API endpoint like this
Open your app https://pin-code-api.herokuapp.com/ and if you see output it means you have successfully deployed the application.
To test the Backend API
you will need the API docs. From API docs you will get the endpoint and the request method details.
API Docs
You app url is going to be the baseurl of your API - https://pin-code-api.herokuapp.com/
To make the request to the endpoint you need to concatenate the base_url and path. Concatenating will give you the endpoint and when you will GET request to the endpoint you will get the desired output.
path | Summary |
---|---|
/api/pin/ | Query one post office from each state |
/api/pin/:state | Query on post office from the selected District and selected State |
I am proving the minimum docs that we require for the flutter app. If you want to know more about the complete API you can head over to the Github repository.
If you would like to have a look a the API Code then you can find it on my github.
{
"message":"List of Post Office",
"data":[
{
"postOfficeName":"string",
"pincode":"string",
"districtsName":"string",
"city":"string",
"state":"string",
},
{
"postOfficeName":"string",
"pincode":"string",
"districtsName":"string",
"city":"string",
"state":"string",
}
]
}
Get Started
First, let’s create a model because this is something which we will be requiring at very first.
After seeing the above sample response you probably got the idea about the shape of the data.
particular | Type |
---|---|
pincode | String |
postOfficeName | String |
city | String |
districtsName | String |
state | String |
class PostOffice {
final String postOfficeName;
final String pinCode;
final String districtsName;
final String city;
final String state;
PostOffice({
this.pinCode,
this.postOfficeName,
this.city,
this.districtsName,
this.state,
});
factory PostOffice.fromJson(Map<String, dynamic> json) {
return PostOffice(
state: json['State'],
districtsName: json['DistrictsName'],
city: json['City'],
postOfficeName: json['PostOfficeName'],
pinCode: json['Pincode'],
);
}
}
There might be some people who are not aware of factory constructor. You can just copy and paste the above code, it will work.
If you are interested in understanding then let me make you understand this in simple language.
PostOffice.fromJson factory constructor initializes all final variable from a JSON object and returns an object of PostOffice.
factory PostOffice.fromJson(Map<String, dynamic> json) {
return PostOffice(
state: json['State'],
districtsName: json['DistrictsName'],
city: json['City'],
postOfficeName: json['PostOfficeName'],
pinCode: json['Pincode'],
);
}
Flutter UI
Now we will create the UI of the app, the UI will be very much similar to the previous one. In Part 1 of this article, we saw two dropdowns, country, and province(state). In this part, we will see the province(state) and District dropdown.
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:multilevel_dropdown_flutter/model/post_office.dart';
class NetworkDropDown extends StatefulWidget {
@override
_NetworkDropDownState createState() => _NetworkDropDownState();
}
class _NetworkDropDownState extends State<NetworkDropDown> {
final String baseURL = "https://pin-code-api.herokuapp.com";
List<PostOffice> states = [];
List<PostOffice> districts = [];
PostOffice selectedState;
PostOffice selectedDistrict;
@override
void initState() {
super.initState();
// On Page Load Get all the states from the server
listState().then((List<PostOffice> value) {
print('List of State/Province is loaded')
setState(() {
states = value;
});
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Network Dropdown'),
),
);
}
Future<List<PostOffice>> apiCall(String endpoint) async {
http.Response response = await http.get(endpoint);
String body = response.body;
Map<String, dynamic> jsonResponse = json.decode(body);
List data = jsonResponse['data'];
return data.map((e) => PostOffice.fromJson(e)).toList();
}
}
Variable
There are various things in the app and I will cover all of them one by one. As I told earlier we will have two dropdown province(state) and District. So we need two arrays for them and also two variables to store their current value. I store the base_url in one variable so that we can easily change and maintain the code.
final String baseURL = "https://pin-code-api.herokuapp.com";
List<PostOffice> states = [];
List<PostOffice> districts = [];
PostOffice selectedState;
PostOffice selectedDistrict;
Method - apiCall
This method is sending a request to the given endpoint and return a List of PostOffices.
Future<List<PostOffice>> listState(String endpoint) async {
http.Response response = await http.get(endpoint);
String body = response.body;
Map<String, dynamic> jsonResponse = json.decode(body);
List data = jsonResponse['data'];
return data.map((e) => PostOffice.fromJson(e)).toList();
}
initState
when the page will open we need to fill the dropdown of states with data. We have to lead the data from the network and set it into the dropdown. This is needed to be done the first time the page gets shown, so for that initState
is the best of doing that.
As initState
can’t be async it means we can’t use await. But there is two way to use an async method in the non-async method.
- use then with an async method
- call another async method
@override
void initState() {
super.initState();
// On Page Load Get all the states from the server
String endpoint = "$baseURL/api/pin";
listState(endpoint).then((List<PostOffice> value) {
print('List of State/Province is loaded')
setState(() {
states = value;
});
});
}
Now we can focus on building the UI of the body, I mean adding two dropdowns. We have loaded the state data in initState
and so now we will show the value in the dropdown.
building body UI
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Network Dropdown'),
),
body: ListView(
padding: EdgeInsets.all(20.0),
children: [
// State Dropdown
DropdownButton<PostOffice>(
hint: Text('State'),
value: selectedState,
isExpanded: true,
items: states.map((PostOffice postOffice) {
return DropdownMenuItem<PostOffice>(
value: postOffice,
child: Text(postOffice.state),
);
}).toList(),
onChanged: onStateChange,
),
// State Dropdown Ends here
],
),
);
}
void onStateChange(PostOffice state) {
setState(() {
selectedState = state;
});
// Sending Request to get Districts of selected State
String endpoint = "$baseURL/api/pin/${selectedState.state}";
listState(endpoint).then((List<PostOffice> value) {
setState(() {
districts = value;
});
});
}
Second Dropdown
Now we will add the second dropdown in the body. In the previous method onStateChange
we load the district.
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Network Dropdown'),
),
body: ListView(
padding: EdgeInsets.all(20.0),
children: [
// State Dropdown
DropdownButton<PostOffice>(
hint: Text('State'),
value: selectedState,
isExpanded: true,
items: states.map((PostOffice postOffice) {
return DropdownMenuItem<PostOffice>(
value: postOffice,
child: Text(postOffice.state),
);
}).toList(),
onChanged: onStateChange,
),
// State Dropdown Ends here
// Districts Dropdown
DropdownButton<PostOffice>(
hint: Text('District'),
value: selectedDistrict,
isExpanded: true,
items: districts.map((PostOffice postOffice) {
return DropdownMenuItem<PostOffice>(
value: postOffice,
child: Text(postOffice.districtsName),
);
}).toList(),
onChanged: onDistrictChange,
),
// Districts Dropdown Ends here
],
),
);
}
void onDistrictChange(PostOffice district) {
setState(() {
selectedDistrict = district;
});
}
Issue
The above code will work fine for one time but when you will try this for the second time it will not work fine. The reason is when you change the state then the corresponding district is also being changed.
We need to reset the value of district to null when we change the state. selectedDistrict
of State A
will not be available in State A
.
void onStateChange(PostOffice state) {
setState(() {
selectedState = state;
districts = [];
selectedDistrict = null;
});
// Sending Request to get Districts of selected State
String endpoint = "$baseURL/api/pin/${selectedState.state}";
listState(endpoint).then((List<PostOffice> value) {
setState(() {
districts = value;
});
});
}
Final Code
Here is the final code of network dropdown, the complete code is also available on GitHub.
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:multilevel_dropdown_flutter/model/post_office.dart';
class NetworkDropDown extends StatefulWidget {
@override
_NetworkDropDownState createState() => _NetworkDropDownState();
}
class _NetworkDropDownState extends State<NetworkDropDown> {
final String baseURL = "https://pin-code-api.herokuapp.com";
PostOffice selectedState;
PostOffice selectedDistrict;
List<PostOffice> states = [];
List<PostOffice> districts = [];
@override
void initState() {
super.initState();
// On Page Load Get all the states from the server
String endpoint = "$baseURL/api/pin";
listState(endpoint).then((List<PostOffice> value) {
setState(() {
states = value;
});
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Network Dropdown'),
),
body: ListView(
padding: EdgeInsets.all(20.0),
children: [
// State Dropdown
DropdownButton<PostOffice>(
hint: Text('State'),
value: selectedState,
isExpanded: true,
items: states.map((PostOffice postOffice) {
return DropdownMenuItem<PostOffice>(
value: postOffice,
child: Text(postOffice.state),
);
}).toList(),
onChanged: onStateChange,
),
// State Dropdown Ends here
// Districts Dropdown
DropdownButton<PostOffice>(
hint: Text('District'),
value: selectedDistrict,
isExpanded: true,
items: districts.map((PostOffice postOffice) {
return DropdownMenuItem<PostOffice>(
value: postOffice,
child: Text(postOffice.districtsName),
);
}).toList(),
onChanged: onDistrictChange,
),
// Districts Dropdown Ends here
],
),
);
}
void onStateChange(state) {
setState(() {
selectedState = state;
districts = [];
selectedDistrict = null;
});
String endpoint = "$baseURL/api/pin/${selectedState.state}";
listState(endpoint).then((List<PostOffice> value) {
setState(() {
districts = value;
});
});
}
void onDistrictChange(district) {
setState(() {
selectedDistrict = district;
});
}
Future<List<PostOffice>> listState(String endpoint) async {
http.Response response = await http.get(endpoint);
String body = response.body;
Map<String, dynamic> jsonResponse = json.decode(body);
List data = jsonResponse['data'];
return data.map((e) => PostOffice.fromJson(e)).toList();
}
}
Conclusion
I hope you got an idea about the multi-level dropdown using data from the network. I tried to make it as simple, if you like it then let me know. Probably, I put the maximum effort into this article compared to others.
If you have any suggestions or questions you can ask in a comment. I would love to answer them