Implementing Multi-level Dropdown Fields in Flutter
In certain Applications, you must have seen a country-related dropdown field vis-a-vis when selecting any value out of it, there suddenly appears a city dropdown pertaining to what you've chosen. However, in today's blog, we shall cover this trending stuff with the help of an HTTP request. So let's get started.
The Index
-- A Summarized Project Overview
Our project is gonna be outstandingly simple i.e.
We will parse the HTTP request and fill up our Dropdown fields with that.
So, only a single screen, but we will apply the appropriate folder structure and going to use the provider as a State Management tool.
Let's now move on to the next one.
-- Implementation
Create a new Flutter project and import the following package(s):
Note: You can also automate the same process by writing down the same command at the terminal end.
flutter pub add ----package_title---
Let's prepare our folder structure.
First, create various directories in the lib:-
api_service -- to handle the initial flow of HTTP request
model -- parse request model for further understanding
repo -- would act as a layer b/w service class and view_model
view_model -- support class for repo and UI stuff
presentation -- UI stuff & widgets
Now, under the api_service folder, create a new file with the same_name.dart, and further add the following snippet:
import 'package:http/http.dart' as http;
class ApiService {
Future<CountryStateModel> getCountry() async {
try {
final res = await http.get(Uri.parse(AppConstants().url));
if(200 == res.statusCode) {
return CountryStateModel.fromMap(jsonDecode(res.body));
}
else {
return CountryStateModel.fromMap({});
}
}
catch (e) {
throw "Something went wrong $e";
}
}
}
Next, we need to create a repository for this as well.
For this, under the repo folder, create a new file as country_state_repo.dart and paste the following snippet:
class CountryStateRepo {
final ApiService apiService = ApiService();
Future<CountryStateModel> getCountry() async {
final res = await apiService.getCountry();
return res;
}
}
To further smoothen the process, let's create a new file under the view_model folder as country_state_view_model.dart.
Moreover, this class shall contain the following snippet:
class CountryStateViewModel extends ChangeNotifier {
CountryStateRepo repo = CountryStateRepo();
List<Province> provinceList = [];
List<States> stateList = [];
String? selectedProvince, selectedState;
bool isLoading = false;
dynamic getCountry() async {
isLoading = true;
notifyListeners();
final data = await repo.getCountry();
provinceList = data.provinces;
stateList = data.states;
isLoading = false;
notifyListeners();
}
}
Before we move on, let's shed some light on the above snippet.
We're done with all the service steps, it's now time to add some UI stuff.
Under the presentation folder, add a new file as country_state_view.dart.
Since we need to fetch the above method we have at our view_model section, we would surely need to have our UI class convert to Stateful Widget.
This would be the code for the initState method:
@override
void initState() {
// TODO: implement initState
Future.delayed(Duration.zero, () {
var result = Provider.of<CountryStateViewModel>(context,
listen: false);
result.getCountry();
});
super.initState();
}
Note: Be sure to initialize the view_model class in the root file before MaterialApp to avoid any sort of conflict.
In the Scaffold's body entity, add a ListView and add a dropdown representing our States.
DropdownButton(
items: viewModel.stateList.map(
(e) => DropdownMenuItem(
value: e.name,
child: Text(e.name),
),
).toList(),
onChanged: (dynamic val) {
setState(() {
viewModel.selectedState = val.toString();
});
},
value: viewModel.selectedState,
hint: const Text("Select a State"),
isDense: false,
isExpanded: true,
),
We're done with one, let's create for Province as well.
DropdownButton(
items: viewModel.provinceList.map(
(e) => DropdownMenuItem(
value: e.name,
child: Text(e.name),
),
).toList(),
onChanged: (dynamic val) {
setState(() {
viewModel.selectedProvince = val.toString();
});
},
value: viewModel.selectedProvince,
hint: const Text("Select a Province"),
isDense: false,
isExpanded: true,
),
With some additional configs, our final snippet looks like this:
class MMDropDownView extends StatefulWidget {
const MMDropDownView({Key? key}) : super(key: key);
@override
State<MMDropDownView> createState() => _MMDropDownViewState();
}
class _MMDropDownViewState extends State<MMDropDownView> {
@override
void initState() {
// TODO: implement initState
Future.delayed(Duration.zero, () {
var result = Provider.of<CountryStateViewModel>(context,
listen: false);
result.getCountry();
});
super.initState();
}
@override
Widget build(BuildContext context) {
var viewModel = Provider.of<CountryStateViewModel>(context);
return Scaffold(
appBar: AppBar(
title: const Text("Multi-level Dropdown"),
),
body: viewModel.isLoading
? const Center(
child: CircularProgressIndicator(),
)
: ListView(
padding: const EdgeInsets.symmetric(
horizontal: 10,
vertical: 10,
),
children: [
DropdownButton(
items: viewModel.stateList
.map(
(e) => DropdownMenuItem(
value: e.name,
child: Text(e.name),
),
)
.toList(),
onChanged: (dynamic val) {
setState(() {
viewModel.selectedState = val.toString();
});
},
value: viewModel.selectedState,
hint: const Text("Select a State"),
isDense: false,
isExpanded: true,
),
DropdownButton(
items: viewModel.provinceList
.map(
(e) => DropdownMenuItem(
value: e.name,
child: Text(e.name),
),
)
.toList(),
onChanged: (dynamic val) {
setState(() {
viewModel.selectedProvince =
val.toString();
});
},
value: viewModel.selectedProvince,
hint: const Text("Select a Province"),
isDense: false,
isExpanded: true,
),
],
),
);
}
}
Let's now run the code.
At this point, our base setup has been completed. However, the one thing that's left is, we need to get the provinces prior to our States.
For that, we need to modify our code a bit.
Step 1 - We need to create a temporary list inside view_model class to store the state-selected provinces
Step 2 - Connecting with our 2nd dropdown
Step 3 - Make changes inside 1st one's onChanged entity
Inside the same view_model.dart file, add a list:-
/// A temporary list
List<Province> tempList = [];
In the UI:-
/// Replace the provinnceList with tempList... The rest remains the same
In the onChanged section:-
viewModel.tempList = viewModel.provinceList
.where((item) => item.name == viewModel.selectedState)
.toList();
Equal the tempList with The selected State by making use of where...
it's time to test our code again.
Note: It's kinda better to have a fresh start of your App. Just a suggestion ...
Here, this article comes to its conclusion.
-- The Conclusion
Wrapping up, we got the amazing concept of Multi-level-Dropdown fields. Apart from this, we got to know a better folder structure and learned something about Provider State management in between too. Prior to that, we took help from the code snippets and etc. stuff.
Furthermore, you can get the source code of this Repository from Here.
Don't forget to check out my Youtube handle as it is filled up with Flutter and Github content.
--- Have a nice weekend!!! ---
Software Engineer | Founder and CTO @AbrossIT
1yI'm going to read it
🎨 UI/UX Designer with 4+ Years of Experience | Computer Science Graduate | Digital Content Creator at MasterlyHub | Figma, Adobe XD, Sketch | Help Brands to Create Professional and Profitable User Experiences 🚀
1yImpressive 🤩