Implementing Multi-level Dropdown Fields in Flutter

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
  • Implementation
  • Conclusion

-- 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):

  1. provider
  2. http

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.

  • Firstly, we added two Lists prior to our model with specific classes such as Province and State respectively.
  • Furthermore, add two String variables in order to capture the latest value at the time of Dropdown's onChanged entity.
  • and lastly added a method that gets us the actual data and fills up our empty lists.

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.

Article content
Screenshot #1
Article content
Screenshot #2

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 ...


Article content
Final Output Window

Here, this article comes to its conclusion.

  • Hope you enjoyed reading this one!


-- 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!!! ---


Sneh Paghdal

Software Engineer | Founder and CTO @AbrossIT

1y

I'm going to read it

Humza Mehmood

🎨 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 🚀

1y

Impressive 🤩

To view or add a comment, sign in

Others also viewed

Explore topics