In this article we will see how we can filter a Listview in the onChange event of a TextField in Flutter.
Here we will use the below service Url to populate data into the ListView.
https://jsonplaceholder.typicode.com/users
Watch Video Tutorial
Create Model Class
class User { int id; String name; String email; User({this.id, this.name, this.email}); factory User.fromJson(Map<String, dynamic> json) { return User( id: json["id"] as int, name: json["name"] as String, email: json["email"] as String, ); } }
Service Class
In this class we will call the service and get the data. Then we will parse the data to get the list of users.
import 'dart:convert'; import 'package:http/http.dart' as http; import 'User.dart'; class Services { static const String url = 'https://jsonplaceholder.typicode.com/users'; static Future<List<User>> getUsers() async { try { final response = await http.get(url); if (response.statusCode == 200) { List<User> list = parseUsers(response.body); return list; } else { throw Exception("Error"); } } catch (e) { throw Exception(e.toString()); } } static List<User> parseUsers(String responseBody) { final parsed = json.decode(responseBody).cast<Map<String, dynamic>>(); return parsed.map<User>((json) => User.fromJson(json)).toList(); } }
So now we have the list of users from the service.
Show the List
Lets create a new variable in the Main file.
List<User> users = List();
Override the initState method and initialize the variable.
@override void initState() { super.initState(); Services.getUsers().then((usersFromServer) { setState(() { users = usersFromServer; }); }); } // in the build method Expanded( child: ListView.builder( padding: EdgeInsets.all(10.0), itemCount: users.length, itemBuilder: (BuildContext context, int index) { return Card( child: Padding( padding: EdgeInsets.all(10.0), child: Column( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: <Widget>[ Text( users[index].name, style: TextStyle( fontSize: 16.0, color: Colors.black, ), ), SizedBox( height: 5.0, ), Text( users[index].email.toLowerCase(), style: TextStyle( fontSize: 14.0, color: Colors.grey, ), ), ], ), ), ); }, ), ),
Add the Search TextField
List<User> filteredUsers = List(); // in the build method above list TextField( decoration: InputDecoration( contentPadding: EdgeInsets.all(15.0), hintText: 'Filter by name or email', ), onChanged: (string) { setState(() { filteredUsers = users .where((u) => (u.name .toLowerCase() .contains(string.toLowerCase()) || u.email.toLowerCase().contains(string.toLowerCase()))) .toList(); }); }, ),
Update the List
Noe update the list with filteredUsers as the source.
Expanded( child: ListView.builder( padding: EdgeInsets.all(10.0), itemCount: filteredUsers.length, itemBuilder: (BuildContext context, int index) { return Card( child: Padding( padding: EdgeInsets.all(10.0), child: Column( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: <Widget>[ Text( filteredUsers[index].name, style: TextStyle( fontSize: 16.0, color: Colors.black, ), ), SizedBox( height: 5.0, ), Text( filteredUsers[index].email.toLowerCase(), style: TextStyle( fontSize: 14.0, color: Colors.grey, ), ), ], ), ), ); }, ), ),
Adding Wait for searching
We don’t want to search on every keystroke in the TextField, so we will write a debouncer and wait for certain time to search after the user types.
so create a class named “debouncer” and add the following code.
class Debouncer { final int milliseconds; VoidCallback action; Timer _timer; Debouncer({this.milliseconds}); run(VoidCallback action) { if (null != _timer) { _timer.cancel(); } _timer = Timer(Duration(milliseconds: milliseconds), action); } }
So Create a new variable
final _debouncer = Debouncer(milliseconds: 500);
Here we are giving a delay of 500 milliseconds, that means it will start
searching after 500 milliseconds the user stops typing. we are actually adding a timer that cancels every time the user types and starts when he stops typing and the above timer will fire after 500 milliseconds.
Now the onChange callback for the TextField will change like below.
onChanged: (string) { _debouncer.run(() { setState(() { filteredUsers = users .where((u) => (u.name .toLowerCase() .contains(string.toLowerCase()) || u.email.toLowerCase().contains(string.toLowerCase()))) .toList(); }); }); },
Complete Source code
import 'package:flutter/material.dart'; import 'dart:async'; import 'User.dart'; import 'Services.dart'; class UserFilterDemo extends StatefulWidget { UserFilterDemo() : super(); final String title = "Filter List Demo"; @override UserFilterDemoState createState() => UserFilterDemoState(); } class Debouncer { final int milliseconds; VoidCallback action; Timer _timer; Debouncer({this.milliseconds}); run(VoidCallback action) { if (null != _timer) { _timer.cancel(); } _timer = Timer(Duration(milliseconds: milliseconds), action); } } class UserFilterDemoState extends State<UserFilterDemo> { // https://jsonplaceholder.typicode.com/users final _debouncer = Debouncer(milliseconds: 500); List<User> users = List(); List<User> filteredUsers = List(); @override void initState() { super.initState(); Services.getUsers().then((usersFromServer) { setState(() { users = usersFromServer; filteredUsers = users; }); }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(widget.title), ), body: Column( children: <Widget>[ TextField( decoration: InputDecoration( contentPadding: EdgeInsets.all(15.0), hintText: 'Filter by name or email', ), onChanged: (string) { _debouncer.run(() { setState(() { filteredUsers = users .where((u) => (u.name .toLowerCase() .contains(string.toLowerCase()) || u.email.toLowerCase().contains(string.toLowerCase()))) .toList(); }); }); }, ), Expanded( child: ListView.builder( padding: EdgeInsets.all(10.0), itemCount: filteredUsers.length, itemBuilder: (BuildContext context, int index) { return Card( child: Padding( padding: EdgeInsets.all(10.0), child: Column( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: <Widget>[ Text( filteredUsers[index].name, style: TextStyle( fontSize: 16.0, color: Colors.black, ), ), SizedBox( height: 5.0, ), Text( filteredUsers[index].email.toLowerCase(), style: TextStyle( fontSize: 14.0, color: Colors.grey, ), ), ], ), ), ); }, ), ), ], ), ); } }
Source code github link: https://bitbucket.org/vipinvijayan1987/tutorialprojects/src/master/FlutterTutorialProjects/flutter_demo1/lib/widgets/List/
github repo would be helpful
Thank you bro for your open source code 🙂 it was so helpful!
Would you please post a tutorial for filtering list of two line, but when the data list source is the app itself (internal). Thank you
Hello everyone
I start flutter journey and after 3 days trying to fetch data from an api, I am asking your help
I have this kind of data.
{
….
“name”: “Apple”
….
}
Here’s the api : https://finnhub.io/api/v1/stock/profile2?symbol=AAPL&token=
I have followed several tutorials but only this one works a bit : https://youtu.be/-EUsRa2G1zk
Unfortunately the data type in this tutorial is like
[
{
….
“name” : “Apple”
….
}
]
So nothing works with my first api
Could anyone help?
I want to do a listview with a TextField able to retrieve information from an api when user tap (exemple : AAPL)
Please follow this tutorial
https://www.youtube.com/watch?v=wc8ecFSu5N8
Thank you so much!