We always use listview to display too many items like contact numbers and products in any shopping application. Let's assume a situation, you want to see an item that's placed in the bottom of list. So, it will irritate you to always scroll it down to see a particular item.
In mobility, we can make it easy with the help of a search feature. ListView with search functionality helps the user to easily get the required results. In the search box, we can filter the data according to data entered by the user and this option is really necessary for making it user-friendly. By using this feature user can filter items by name, occupation, address, etc. Search functionality reduces navigation complexity for the user and also saves the user’s valuable time. In this post, we going to create a Flutter application with listview items and we applying a custom filter inside a ListView. When user will write some characters inside an input search field. The search function will filter the listview with a matching string from the user input. The final output of app looks like below:
>Creating a New Project 1. Create a new project from File ⇒ New Flutter Project with your development IDE and add http: "^0.12.0" for fetch data from the server. 2. Create a PODO model country.dart file to representing a country in the list view.
country.dart
class Country {
String name;
String flag;
String capital;
Country({this.name, this.flag, this.capital});
factory Country.fromJson(Map<String, dynamic> json) {
return new Country(
name: json['name'] as String,
flag: json['flag'] as String,
capital: json['capital'] as String,
);
}
}
3. Now create a networklayer.dart business layer of the application. It will fetch some data from the server and prase it in above PODO model.
networklayer.dart
import 'dart:async';
import 'package:http/http.dart' as http;
Future<String> fetchCountry(http.Client client) async {
final response = await client.get('http://restcountries.eu/rest/v2/all');
// Use the compute function to run parsePhotos in a separate isolate
return response.body;
}
4. Now start work on user interface part of this application. Open main.dart file and start editing it. As we have set our theme and change debug banner property of Application.
5. Create home.dart widget for display search box in the app bar. When you tap on search icon, a big search box will open for type something and apply filter. Let's see complete widget snippet and try to understand it. We have written some doc in this file to understand it deeply with yellow color.
home.dart
import 'dart:convert';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_list_filter/country.dart';
import 'package:flutter_list_filter/list.dart';
import 'package:flutter_list_filter/netwoklayer.dart';
import 'package:http/http.dart' as http;
class MainPage extends StatefulWidget {
const MainPage();
@override
_MainPageState createState() => new _MainPageState();
}
class _MainPageState extends State<MainPage>
with SingleTickerProviderStateMixin {
static final GlobalKey<ScaffoldState> scaffoldKey =
new GlobalKey<ScaffoldState>();
//it will hold search box value
TextEditingController _searchQuery;
bool _isSearching = false;//maintain search box on/off state
List<Country> filteredRecored;
//getting data from the server
@override
void initState() {
super.initState();
_searchQuery = new TextEditingController();
fetchCountry(new http.Client()).then((String) {
parseData(String);
});
}
List<Country> allRecord;
//parsing server response.
parseData(String responseBody) {
final parsed = json.decode(responseBody).cast<Map<String, dynamic>>();
setState(() {
allRecord =
parsed.map<Country>((json) => new Country.fromJson(json)).toList();
});
filteredRecored = new List<Country>();
filteredRecored.addAll(allRecord);
}
//It'll open search box
void _startSearch() {
ModalRoute.of(context)
.addLocalHistoryEntry(new LocalHistoryEntry(onRemove: _stopSearching));
setState(() {
_isSearching = true;
});
}
//It'll close search box.
void _stopSearching() {
_clearSearchQuery();
setState(() {
_isSearching = false;
filteredRecored.addAll(allRecord);
});
}
//clear search box data.
void _clearSearchQuery() {
setState(() {
_searchQuery.clear();
updateSearchQuery("Search query");
});
}
//Create a app bar title widget
Widget _buildTitle(BuildContext context) {
var horizontalTitleAlignment =
Platform.isIOS ? CrossAxisAlignment.center : CrossAxisAlignment.start;
return new InkWell(
onTap: () => scaffoldKey.currentState.openDrawer(),
child: new Padding(
padding: const EdgeInsets.symmetric(horizontal: 12.0),
child: new Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: horizontalTitleAlignment,
children: <Widget>[
new Text('Seach box',
style: new TextStyle(color: Colors.white),),
],
),
),
);
}
//Creating search box widget
Widget _buildSearchField() {
return new TextField(
controller: _searchQuery,
autofocus: true,
decoration: const InputDecoration(
hintText: 'Search...',
border: InputBorder.none,
hintStyle: const TextStyle(color: Colors.white30),
),
style: const TextStyle(color: Colors.white, fontSize: 16.0),
onChanged: updateSearchQuery,
);
}
//It'll update list items atfer searching complete.
void updateSearchQuery(String newQuery) {
filteredRecored.clear();
if (newQuery.length > 0) {
Set<Country> set = Set.from(allRecord);
set.forEach((element) => filterList(element, newQuery));
}
if (newQuery.isEmpty) {
filteredRecored.addAll(allRecord);
}
setState(() {});
}
//Filtering the list item with found match string.
filterList(Country country, String searchQuery) {
setState(() {
if (country.name.toLowerCase().contains(searchQuery) ||
country.name.contains(searchQuery)) {
filteredRecored.add(country);
}
});
}
List<Widget> _buildActions() {
if (_isSearching) {
return <Widget>[
new IconButton(
icon: const Icon(Icons.clear,color: Colors.white,),
onPressed: () {
if (_searchQuery == null || _searchQuery.text.isEmpty) {
Navigator.pop(context);
return;
}
_clearSearchQuery();
},
),
];
}
return <Widget>[
new IconButton(
icon: const Icon(Icons.search,color: Colors.white,),
onPressed: _startSearch,
),
];
}
@override
Widget build(BuildContext context) {
return new Scaffold(
key: scaffoldKey,
appBar: new AppBar(
leading: _isSearching ? new BackButton( color: Colors.white,) : null,
title: _isSearching ? _buildSearchField() : _buildTitle(context),
actions: _buildActions(),
),
body: filteredRecored != null && filteredRecored.length > 0
? new CountyList(country: filteredRecored)
: allRecord == null
? new Center(child: new CircularProgressIndicator())
: new Center(
child: new Text("No recored match!"),
),
);
}
}
6. At the end, create final widget list.dart for this example. It'll show list of the countries. In this widget, we passing all or filtered list of items.
list.dart
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_list_filter/country.dart';
class CountyList extends StatelessWidget {
final List<Country> country;
CountyList({Key key, this.country}) : super(key: key);
@override
Widget build(BuildContext context) {
return new ListView.builder(
itemCount: country == null ? 0 : country.length,
itemBuilder: (BuildContext context, int index) {
return
new Card(
child: new Container(
child: new Center(
child: new Column(
// Stretch the cards in horizontal axis
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
new Text(
// Read the name field value and set it in the Text widget
country[index].name,
// set some style to text
style: new TextStyle(
fontSize: 20.0, color: Colors.lightBlueAccent),
),
new Text(
// Read the name field value and set it in the Text widget
"Capital:- " + country[index].capital,
// set some style to text
style: new TextStyle(
fontSize: 20.0, color: Colors.amber),
),
],
)),
padding: const EdgeInsets.all(15.0),
),
);
});
}
}
If you have followed the article carefully, you can see the app running very smoothly as shown in the above video. But if you are facing any problem or you have any quires, please feel free to ask it from below comment section and you can ask it on SLACK.