Flutter is all about widgets composition. Developers can have fun by combining widgets for better apps. DropDownButton and DropDownList are the right widgets to show the selectable items list. Also to let users select the items amongst the list. It is nice enough to use built-in Flutter widgets. Although, we can also get help from dart packages as we did to serialize JSON.

DropDownSearch is one of the most popular widgets which helps us make a dropdown with advanced features such as filters for API fetched data, validators, and so on. ‌‌I chose to use dropdown_search dart package since it has not only the highest Likes but also popularity. Their examples and reference are also great to have a look into when you start to develop something with this package. In this article, we are going to explore how to implement this package and how to use it with json_serializable package !

Firstly, let's install DropDownSearch package.
In terminal run

$ flutter pub add dropdown_search

You can check in pubspec.yaml file the following lines added.

dependencies:
dropdown_search: <latest_version>

Now run flutter pub get. The package must be added by now. To implement the package to the file, you can simply add this line to the dart file.

import ‘package:dropdown_search/dropdown_search.dart’;

Inside of the same file that you imported the package, make a simple dropdownsearch widget

List<String>dropDownAnimals = ['Dog', 'Cat', 'Panda', 'Whale'];
Widget dropDownExample =DropdownSearch<String>(
        mode: Mode.BOTTOM_SHEET,
        showSelectedItems: true,
        items: dropDownAnimals,
        label: "Animals",
        popupItemDisabled: (String s) => s.startsWith('D'),
        onChanged: print,
       );

As you can see, here is our first dropdown widget in the screen with the item list.

If you add popupItemDisabled: (String s) => s.startsWith(‘D'),  in the widget you can see Dog is muted. User can’t choose this option also.

If you set mode: Mode.BOTTOM_SHEET, options will show up as a bottom sheet.

muted item in the list / bottom sheet list

You can use a validator or make this widget look better with decoration. This is fun to play with. But, we can look much deeper into this widget when it works with API! When you want to use fetched API data with dropdown, this package will help you.

To use API and get data from it, we can put onFind function instead of Items.

DropdownSearch<UserModel>(
	label: "Animals",
	onFind: (String filter) => getData(filter),
	onChanged: (UserModel data) => print(data),
),

When users tap the dropdown once, it automatically calls getData function  which calls API to get the data. If you don’t pass parameters from it you can simply write down as the following codes do.

onFind : (_) => getData()

So we need to fetch some data to play around! To do this, let’s start from zero again to make a new app.

To fetch API data, we need an API. I will use a random user API which you can ask for random user profiles with pictures, names and various data of them. I put a query for having 5 random users' data.
https://randomuser.me/api/?results=5
It allows me  to have 5 random user’s data in the value of ”results” key.

{
"gender": "female",

"name": {
"title": "Miss",
"first": "Tania",
"last": "Günter"
},

"location": {

"street": {
"number": 7845,
"name": "Mittelweg"
},
"city": "Wustrow (Wendland)",
"state": "Thüringen",
"country": "Germany",
"postcode": 56698,

"coordinates": {
"latitude": "-42.9521",
"longitude": "96.0809"
},

"timezone": {
"offset": "+5:45",
"description": "Kathmandu"
}
},
"email": "tania.gunter@example.com",

"login": {
"uuid": "e44ccaff-81f3-45bf-80ef-17e1ad8956db",
"username": "whitecat531",
"password": "train",
"salt": "e8luU7Hd",
"md5": "6cad984463c01b8551a60f0a0e04100e",
"sha1": "fb0608df9bc9f76292dfc4030f20d030057fc8a6",
"sha256": "3735868d9b6d721beec03806a534497ddaddabac3936d4791399427464f9bfa2"
},

"dob": {
"date": "1994-02-20T06:27:10.349Z",
"age": 28
},

"registered": {
"date": "2004-04-14T01:26:36.091Z",
"age": 18
},
"phone": "0855-3487016",
"cell": "0173-9795330",

"id": {
"name": "",
"value": null
},

"picture": {
"large": "https://randomuser.me/api/portraits/women/63.jpg",
"medium": "https://randomuser.me/api/portraits/med/women/63.jpg",
"thumbnail": "https://randomuser.me/api/portraits/thumb/women/63.jpg"
},
"nat": "DE"
},

You can check that there are 5 objects are having this structure from the ”result” key value. To handle this data in our code we need to build a response model. It’s time to create User class.
I created new user_model.dart in /lib/model. I created a new directory /model under /lib.

import 'package:json_annotation/json_annotation.dart';

part 'user_model.g.dart';

@JsonSerializable()
class User {
User(
{required this.gender,
required this.name,
required this.location,
required this.email,
required this.picture,
required this.nat});

final String gender;
final Map<String, String> name;
final Map<String, dynamic> location;
final String email;
final Map<String, String> picture;
final String nat;

factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);

Map<String, dynamic> toJson() => _$UserToJson(this);
}

You can choose which field to use from the data I chose. Now run the command to generate user_dart.g.dart
flutter pub run build_runner build
Once the file is generated, you can check from the same directory /lib/model.
user_model.g.dart

// GENERATED CODE - DO NOT MODIFY BY HAND

part of 'user_model.dart';

// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************

User _$UserFromJson(Map<String, dynamic> json) => User(
gender: json['gender'] as String,
name: Map<String, String>.from(json['name'] as Map),
location: json['location'] as Map<String, dynamic>,
email: json['email'] as String,
picture: Map<String, String>.from(json['picture'] as Map),
nat: json['nat'] as String,
);

Map<String, dynamic> _$UserToJson(User instance) => <String, dynamic>{
'gender': instance.gender,
'name': instance.name,
'location': instance.location,
'email': instance.email,
'picture': instance.picture,
'nat': instance.nat,
};

As we read from the first article, flutter pub run build_runner build command automatically creates <model>.g.dart file with the proper type of each field. Once you check the model part is done, let’s move on to the API part!

I created user_api.dart in /lib/api directory. For HTTP requests, we can add http package.
Run the command flutter pub add http then run flutter pub get to complete installing the package.

import 'dart:convert';
import 'package:dropdown_with_json/model/user_model.dart';
import 'package:http/http.dart' as http;

Future<List<User>> getUserData() async {
String url = 'https://randomuser.me/api/?results=5';

final response = await http.get(
Uri.parse(url),
headers: <String, String>{
'Content-Type': 'application/json; charset=UTF-8',
'Access-Control-Allow-Origin': "*"
},
);
final List<User> userDataList = [];
if (response.statusCode == 200) {
var rawData = jsonDecode(response.body)['results'];
for (var value in rawData) {
userDataList.add(User.fromJson(value));
}
print(userDataList);
return userDataList;
} else {
throw Exception('Response is not 200');
}
}


If we separate codes into small pieces,

import 'dart:convert';
import 'package:dropdown_with_json/model/user_model.dart';
import 'package:http/http.dart' as http;

To use jsonDecode to decode response data from its body, we import dart:convert. To serialize API response data as Userclass, we import the model.

Future<List<User>> getUserData() async {}
Since the return value will be returned as List<User> with async, we can declare the return value type  for this function as Future<List<User>>. I made for loop to add each raw data from response.bodywith "results" key to empty List<User> userDataList.

final List<User> userDataList = [];
    if (response.statusCode == 200) {
    var rawData = jsonDecode(response.body)['results'];
    for (var value in rawData) {
    userDataList.add(User.fromJson(value));
}
    return userDataList;
} else {
	throw Exception('Response is not 200');
}

So in the end, getUserData() function returns userDataList when the response status is 200 otherwise throws the exception.

What should we do now? We need to put getUserData()function inside of our DropDownSearch widget. I told you in the widget, we can put onFind to use API to customize the widget. In this case, I created a new dropdown widget class in the new dart file called custom_dropdown.dart in /lib .  And I wrote like this.

import 'package:dropdown_search/dropdown_search.dart';
import 'package:dropdown_with_json/api/user_api.dart';
import 'package:dropdown_with_json/model/user_model.dart';
import 'package:flutter/material.dart';

class CustomDropDown extends StatelessWidget {
const CustomDropDown({Key? key}) : super(key: key);

@override
Widget build(BuildContext context) {
Widget customDropDownExample = DropdownSearch<User>(
	mode: Mode.MENU,
	label: "Users",
	onFind: (_) => getUserData(),
	itemAsString: (User? u) {
		return "${u!.name['first']} ${u.name['last']}";
		},
	);
	return Padding(
    	padding: EdgeInsets.all(10), 
    	child: customDropDownExample
        );
	}
}

Firstly, I imported user_api.dart, user_model.dart, and material.dart package. Of course I imported dropdown_search.dart package too.

Widget customDropDownExample = DropdownSearch<User>(
	mode: Mode.MENU,
	label: "Users",
	onFind: (_) => getUserData(),
	itemAsString: (User? u) {
	return "${u!.name['first']} ${u.name['last']}";
	},
);

I made a custom widget separated with the name customDropDownExample. As you can see, DropDownSearch widget can use a specific type as we did with String. This time, we are using a custom model User so I wrote it down in the bracket next to the DropDownSearch. At onFind you can see getUserData() is assigned. This means, once a user taps the dropdown it derives onFind so it will get the return value of getUserData() which is Future<List>. Then, itemAsString will take care of the data in User form. To show the user data in proper name format, I combine the first and the last name into one string.

Tada! When I put the widget in the main.dart instead of the first simple DropDown, You can see the customized widget working with the connected API!


You can also change the dropdown button, popup barrier colors. You can use InputDecoration() to decorate the form. With setting showSearchBox as true, you can search specific results with filtering. In the following image you can see dropDownButton.

Changed with a searching icon with a different color. Also, showSearchBox is activated. You can also use a built-in validator for some situations such as when users submit their choice from the dropdown and the choice is required.

validator: (value) {
        if (value == null) {
          return 'Required Field';
        }
        return null;
      },
  • ‌Conclusion

We have looked at how to serialize JSON with packages automatically and apply the results to customized dropdown made by `dropdownsearch` package. Personally, it is fun to consider how to show data from a server on a mobile platform and implement it to the app with customized widgets while developing in Flutter. There are plenty of ways to show your data to the users and I have had fun with the packages I introduced in this article. I hope this blog helps someone having similar interests to mine.

•    References in this article

‌https://pub.dev/packages/dropdown_search

https://flutter.dev/docs/development/data-and-backend/json

https://pub.dev/packages/json_annotation

https://pub.dev/packages/json_serializable

First article