Flutter - Implementing a listView search feature
Solution 1
There are probably many ways to implement this based on the resulting experience you want. A simple solution is to create activeSearch
state that toggles a 'search app bar' and a 'normal app bar'
Here's the normal app bar:
return AppBar(
title: Text("My App"),
actions: <Widget>[
IconButton(
icon: Icon(Icons.search),
onPressed: () => setState(() => activeSearch = true),
),
],
);
And here's the search app bar:
return AppBar(
leading: Icon(Icons.search),
title: TextField(
decoration: InputDecoration(
hintText: "here's a hint",
),
),
actions: <Widget>[
IconButton(
icon: Icon(Icons.close),
onPressed: () => setState(() => activeSearch = false),
)
],
);
Note: if you don't want to have a leading icon when search is active you may want to disable the default behavior for a drawer
and back button
icon with:
automaticallyImplyLeading: false
Full example:
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
bool activeSearch;
@override
void initState() {
super.initState();
activeSearch = false;
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: _appBar(),
drawer: _drawer(),
);
}
PreferredSizeWidget _appBar() {
if (activeSearch) {
return AppBar(
leading: Icon(Icons.search),
title: TextField(
decoration: InputDecoration(
hintText: "here's a hint",
),
),
actions: <Widget>[
IconButton(
icon: Icon(Icons.close),
onPressed: () => setState(() => activeSearch = false),
)
],
);
} else {
return AppBar(
title: Text("My App"),
actions: <Widget>[
IconButton(
icon: Icon(Icons.search),
onPressed: () => setState(() => activeSearch = true),
),
],
);
}
}
Widget _drawer() {
return Container();
}
}
UPDATE: Here's a hint at handling results
return AppBar(
...
title: TextField(
onChanged: _search,
),
);
And what _search could look like:
List<MyResultObject> _results;
void _search(String queryString) {
// do some searching and sorting
// then call setState() with the results
// and then in your ListView you can read from results
// (handle empty, default case as well in view)
setState(() {
_results = ...
});
}
List<Widget> _resultWidgets() {
if (_results.isEmpty) return _defaultWidgets();
_results.map((r) => _buildRowWidget(s)).toList();
}
Solution 2
Can u refer a simple search view in this answer. In that example, as the user types, the list will get filtered.
Jake
Work email: jake(at)squaredsoftware.co.uk Please support me by downloading my new iOS application, travelrecce!
Updated on December 06, 2022Comments
-
Jake over 1 year
I've been trying to implement a search bar into my app for bringing selected
listView
items to the top of a list. The list contains quite a few items, around approximately 1700 so the addition of a search bar is essential. I'd like the listView search box to appear from asearch
icon on the right hand side of the topappBar
. Below is a picture of the current view for reference.When you click the search
iconButton
a search field should replace the title in theappBar
. It's going to be evident to the user that this is for the cryptolistView
as I'll add a hint in the search view identifying this.I'm not including all my code as this would be cumbersome for a stack question, but below is my
home_page.dart
file, where as the rest of my classes for the bottom cryptolistView
can be found at this GitHub repo.This is what my 'home_page.dart` looks like;
import 'package:cryptick/cryptoData/crypto_data.dart'; import 'package:cryptick/cryptoData/trending_data.dart'; import 'package:cryptick/modules/crypto_presenter.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'background.dart'; //FOLLOWING DART CODE COPYRIGHT OF 2017 - 2018 SQUARED SOFTWARE LONDON class HomePage extends StatefulWidget { @override _HomePageState createState() => new _HomePageState(); } class ServerStatusScreen extends StatelessWidget { @override Widget build(BuildContext context) { return new Scaffold( appBar: new AppBar( iconTheme: new IconThemeData(color: Colors.white), centerTitle: true, backgroundColor: Colors.black, title: new Text( 'API Server Status', textAlign: TextAlign.center, style: new TextStyle( color: Colors.white, fontSize: 27.5, fontFamily: 'Kanit'), ), ), body: new Center( child: new Column( children: [ new Divider(color: Colors.white), new Text( 'News Feed: ', textAlign: TextAlign.center, style: new TextStyle( color: Colors.black, fontSize: 27.5, fontFamily: 'Kanit', ), ), new Divider(), new Text( 'Crypto Feed: ', textAlign: TextAlign.center, style: new TextStyle( color: Colors.black, fontSize: 27.5, fontFamily: 'Kanit', ), ), new Divider(), new Wrap( alignment: WrapAlignment.center, children: <Widget>[ new Chip( backgroundColor: Colors.black, label: new Text( '© 2017-2018 Squared Software', style: new TextStyle( fontSize: 15.0, fontFamily: 'Poppins', color: Colors.white, ), ), ), ], ), ], ), ), ); } } class MoreInfoScreen extends StatelessWidget { @override Widget build(BuildContext context) { final ThemeData themeData = Theme.of(context); final TextStyle aboutTextStyle = themeData.textTheme.body2; final TextStyle linkStyle = themeData.textTheme.body2.copyWith(color: themeData.accentColor); return new Scaffold( appBar: new AppBar( iconTheme: new IconThemeData(color: Colors.white), centerTitle: true, backgroundColor: Colors.black, title: new Text( 'More Info', textAlign: TextAlign.center, style: new TextStyle( color: Colors.white, fontSize: 27.5, fontFamily: 'Kanit'), ), ), body: new Center( child: new Column( children: [ new Divider(color: Colors.white), new ListTile( title: new Text('Squared Software', style: new TextStyle( fontWeight: FontWeight.w500, fontFamily: 'Poppins', ) ), leading: new CircleAvatar( radius: 30.0, backgroundImage: new AssetImage( 'images/sqinterlock.png' ) ) ), new Divider(), new Text('Where do we get our information?', style: new TextStyle( color: Colors.black, fontFamily: 'Poppins', fontSize: 16.5, ) ), new Divider(color: Colors.white), new Text( "News Feed: bit.ly/2MFpzHX", style: new TextStyle( fontFamily: 'Poppins', fontSize: 16.5, ), ), new Divider(color: Colors.white), new Text( "Crypto Feed: bit.ly/2iIdJht", style: new TextStyle( fontFamily: 'Poppins', fontSize: 16.5, ), ), new Divider(color: Colors.white), new Wrap( alignment: WrapAlignment.center, children: <Widget>[ new Chip( backgroundColor: Colors.black, label: new Text( '© 2017-2018 Squared Software', style: new TextStyle( fontSize: 15.0, fontFamily: 'Poppins', color: Colors.white, ), ), ), ], ), ], ), ), ); } } class _HomePageState extends State<HomePage> implements CryptoListViewContract { CryptoListPresenter _presenter; List<Crypto> _currencies; bool _isLoading; final List<MaterialColor> _colors = [Colors.blue, Colors.indigo, Colors.red]; _HomePageState() { _presenter = new CryptoListPresenter(this); } @override void onLoadTrendingComplete(Trending trending) { // TODO: articlesMap = trending.articles; for (Map articleMap in articlesMap) { articles.add(Articles.fromMap(articleMap)); } if (mounted) setState(() {}); } @override void onLoadTrendingError() { // TODO: } List articlesMap = []; List<Articles> articles = []; @override void initState() { super.initState(); _isLoading = true; _presenter.loadCurrencies(); _presenter.loadTrending(); } @override Widget build(BuildContext context) { return new Scaffold( appBar: new AppBar( title: new Text( "Cryp - Tick Exchange", style: new TextStyle( color: Colors.white, fontFamily: 'Poppins', fontSize: 22.5, ), ), iconTheme: new IconThemeData(color: Colors.white), backgroundColor: const Color(0xFF273A48), elevation: 0.0, centerTitle: true, ), drawer: new Drawer( child: new ListView(padding: EdgeInsets.zero, children: <Widget>[ new DrawerHeader( child: new CircleAvatar( child: new Image.asset('images/ctavatar.png'), ), decoration: new BoxDecoration( color: Colors.black, ), ), new MaterialButton( child: new Text( 'Server Status', textAlign: TextAlign.center, style: new TextStyle(fontSize: 27.5, fontFamily: 'Kanit'), ), onPressed: () { Navigator.push( context, MaterialPageRoute( builder: (context) => ServerStatusScreen()), ); }), new Divider(), new MaterialButton( child: new Text( 'More Info', textAlign: TextAlign.center, style: new TextStyle(fontSize: 27.5, fontFamily: 'Kanit'), ), onPressed: () { Navigator.push( context, MaterialPageRoute(builder: (context) => MoreInfoScreen()), ); }), new Divider(), new Wrap( alignment: WrapAlignment.center, children: <Widget>[ new Chip( backgroundColor: Colors.black, label: new Text( 'v0.0.1', style: new TextStyle( fontSize: 15.0, fontFamily: 'Poppins', color: Colors.white, ), ), ), ], ), ]), ), body: _isLoading ? new Center(child: new CupertinoActivityIndicator(radius: 15.0)) : _allWidget()); } Widget _allWidget() { final _width = MediaQuery.of(context).size.width; final _height = MediaQuery.of(context).size.height; //CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED final headerList = new ListView.builder( itemBuilder: (context, index) { EdgeInsets padding = index == 0 ? const EdgeInsets.only( left: 20.0, right: 10.0, top: 4.0, bottom: 30.0) : const EdgeInsets.only( left: 10.0, right: 10.0, top: 4.0, bottom: 30.0); return new Padding( padding: padding, child: new InkWell( onTap: () { print('@url'); }, child: new Container( decoration: new BoxDecoration( borderRadius: new BorderRadius.circular(10.0), color: const Color(0xFF273A48), boxShadow: [ new BoxShadow( color: Colors.black.withAlpha(70), offset: const Offset(3.0, 10.0), blurRadius: 15.0) ], image: new DecorationImage( image: new NetworkImage(articles[index].urlToImage), fit: BoxFit.fitHeight, ), ), height: 200.0, width: 275.0, child: new Stack( children: <Widget>[ new Align( alignment: Alignment.bottomCenter, child: new Container( padding: new EdgeInsets.only(left: 10.0), decoration: new BoxDecoration( color: const Color(0xFF273A48), borderRadius: new BorderRadius.only( bottomLeft: new Radius.circular(10.0), bottomRight: new Radius.circular(10.0)), ), height: 50.0, child: new Row( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ new Expanded(child: new Text( articles[index].title, overflow: TextOverflow.ellipsis, maxLines: 2, style: new TextStyle( color: Colors.white, fontFamily: 'Poppins', ), ), ), ], ) ), ) ], ), ), ), ); }, scrollDirection: Axis.horizontal, itemCount: articles.length, ); final body = new Scaffold( backgroundColor: Colors.transparent, body: new Container( child: new Stack( children: <Widget>[ new Padding( padding: new EdgeInsets.only(top: 10.0), child: new Column( crossAxisAlignment: CrossAxisAlignment.center, mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.start, children: <Widget>[ new Align( alignment: Alignment.centerLeft, child: new Padding( padding: new EdgeInsets.only( left: 10.0, ), child: new Text( "Trending News", style: new TextStyle( letterSpacing: 0.8, fontFamily: 'Kanit', fontSize: 17.5, color: Colors.white, ), )), ), new Container( height: 300.0, width: _width, child: headerList), new Expanded(child: ListView.builder( itemBuilder: (BuildContext context, int index) { final int i = index; final Crypto currency = _currencies[i]; final MaterialColor color = _colors[i % _colors.length]; return new ListTile( title: new Column( children: <Widget>[ new Row( crossAxisAlignment: CrossAxisAlignment.start, children: <Widget>[ new Container( height: 72.0, width: 72.0, decoration: new BoxDecoration( color: Colors.white, boxShadow: [ new BoxShadow( color: Colors.black.withAlpha(80), offset: const Offset(2.0, 2.0), blurRadius: 15.0) ], borderRadius: new BorderRadius.all( new Radius.circular(35.0)), image: new DecorationImage( image: new ExactAssetImage( "cryptoiconsBlack/" + currency.symbol.toLowerCase() + "@2x.png", ), fit: BoxFit.cover, )), ), new SizedBox( width: 8.0, ), new Expanded( child: new Column( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: <Widget>[ new Text( currency.name, style: new TextStyle( fontSize: 15.0, fontFamily: 'Poppins', color: Colors.black87, fontWeight: FontWeight.bold), ), _getSubtitleText(currency.price_usd, currency.percent_change_1h), ], )), ], ), new Divider(), ], ), ); })) ], ), ), ], ), ), ); return new Container( decoration: new BoxDecoration( color: const Color(0xFF273A48), ), child: new Stack( children: <Widget>[ new CustomPaint( size: new Size(_width, _height), painter: new Background(), ), body, ], ), ); } // CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED Widget _getSubtitleText(String priceUSD, String percentageChange) { TextSpan priceTextWidget = new TextSpan( text: "\$$priceUSD\n", style: new TextStyle( color: Colors.black, fontSize: 14.0, )); String percentageChangeText = "1 hour: $percentageChange%"; TextSpan percentageChangeTextWidget; if (double.parse(percentageChange) > 0) { percentageChangeTextWidget = new TextSpan( text: percentageChangeText, style: new TextStyle( color: Colors.green, fontFamily: 'PoppinsMediumItalic', )); } else { percentageChangeTextWidget = new TextSpan( text: percentageChangeText, style: new TextStyle( color: Colors.red, fontFamily: 'PoppinsMediumItalic', )); } return new RichText( text: new TextSpan( children: [priceTextWidget, percentageChangeTextWidget])); } //Works with cryptoListViewContract implimentation in _MyHomePageState @override void onLoadCryptoComplete(List<Crypto> items) { // TODO: implement onLoadCryptoComplete setState(() { _currencies = items; _isLoading = false; }); } @override void onLoadCryptoError() { // TODO: implement onLoadCryptoError } }
Thanks for the help, Jake
-
Ashton Thomas over 5 yearsHave you consider AppBar.actions? If so, what is the desired affect? That is, do you want a have a dialog pop up to search? Would be cool to have an TextField instead of an Icon and you just expand that our while hiding the other parts of the AppBar.
-
Jake over 5 yearsYeah I'll edit the question after this to reflect this comment but is there anyway when you click the search
iconButton
a search field could replace the title in theappBar
?. It's going to be evident to the user that this is for the cryptolistView
as I'll add a hint in the search view identifying this. Thanks -
Paresh Mangukiya almost 4 yearsTo use this plugin And use it however you want :- stackoverflow.com/a/61727414/10563627
-
Paresh Mangukiya almost 4 yearsThere's the Flutter library you could use to implement the same sort of design that Youtube/Instagram uses : github.com/pkmangukiya/flutter_search_view_pk
-
-
Jake over 5 yearsThis looks great, just implementing it now
-
Jake over 5 yearsI'll implement the question from Ashton first, then I'll add that after. Thanks for the additional info :)
-
Jake over 5 yearsIs there anyway you could provide or hint at the code I would need to actually sort the items? The search bar is really good thanks for what you've done so far.
-
Ashton Thomas over 5 years@Jake, I added quick update at the end that may help
-
Jake over 5 yearsBrilliant, thanks for the help. I'll take a look at the question @Dinesh Balasubramanian provided and reference it soon.
-
Jake over 5 yearsHey @Ashton, just noticed that when the on screen iOS keyboard pops up when I type a value to the search field, I get a severe pixel overflow. Anything I could do about this? Thanks
-
Ashton Thomas over 5 yearsYou need to wrap the body of your scaffold in a listview to creat a scrollable area