How to Implement Search Functionality In flutter - flutter

I have been stuck trying implement the search functionality in flutter,
So I have this search icon button
IconButton(
onPressed: () {
showSearch(
context: context,
delegate: CustomSearchDelegate(),
);
},
icon: Icon(Icons.search),
)
OnPressed It uses the CustomSearchDelegate Class Below
class CustomSearchDelegate extends SearchDelegate {
AllBackEnds _allBackEnds = AllBackEnds();
#override
List<Widget> buildActions(BuildContext context) {
return [
IconButton(
onPressed: () {
if (query.isEmpty) {
close(context, null);
} else {
query = '';
}
},
icon: Icon(Icons.clear)),
];
}
#override
Widget buildLeading(BuildContext context) => IconButton(
icon: BackButtonIcon(),
onPressed: () {
close(context, null);
},
);
#override
Widget buildResults(BuildContext context) {
return FutureBuilder<List>(
future: _allBackEnds.getUsers(),
builder: (context, adsSnapshot) {
if (!adsSnapshot.hasData) {
return CustProgIndicator();
} else {
List _dat = adsSnapshot.data!;
return ListView.builder(
itemCount: _dat.length,
shrinkWrap: true,
itemBuilder: (context, int index) {
Map<String, dynamic> _userAds = _dat[index];
return CustomWid(data: _userAds);
},
);
}
});
}
#override
Widget buildSuggestions(BuildContext context) {
return FutureBuilder<List>(
future: _allBackEnds.getUsers(),
builder: (context, adsSnapshot) {
if (!adsSnapshot.hasData) {
return CustProgIndicator();
} else {
List _dat = adsSnapshot.data!;
return ListView.builder(
itemCount: _dat.length,
shrinkWrap: true,
itemBuilder: (context, int index) {
Map<String, dynamic> _userAds = _dat[index];
return CustomWid(data: _userAds);
},
);
}
});
}
}
The _allBackEnds.getUsers() is returning an array of objects like this
[{s_n: 1, name: Drizzy}, {s_n: 2, name: Omah Lay,}];
So I want to search using the name
So the buildSuggesion Widget & buildResults Widget are just displaying a list of custom users card, without the actual search feature.
How can I implement the suggestions & search functionality with my code shown?

the proper way to solve this issue is : first of all you have to make model to store that data,after making model use textfield and enter sometext, in onchange method of textfield you can check that the list of model data is containing this data you have entered in text field like
void searchContact(String textfieldresult,modelList) async {
searchedList.clear();
var value = modelList.where((element) =>
element.name!.toLowerCase().contains(textfieldresult.toLowerCase()) ||
element.name!.toUpperCase().contains(textfieldresult.toUpperCase()));
searchedList.addAll(value);
return searchedList ;
}
this method will return searched values

Related

Flutter Listview Not filter to the search result

I created an appbar search that takes users' names and sends them to the profile page . the problem is listview is not updating when I enter the user name (list order not changing)
when I type different name that isn't int the list it shows empty but when I type a name that in list view it shows me the first item which don't same
this is my search delegate class
class CustomSearchDelegate extends SearchDelegate {
#override
List<Widget>? buildActions(BuildContext context) {
return [
IconButton(
onPressed: () {
if (query == "") {
close(context, null);
} else {
query = query = '';
}
},
icon: const Icon(Icons.clear),
),
];
}
// second overwrite to pop out of search menu
#override
Widget? buildLeading(BuildContext context) {
return IconButton(
onPressed: () {
close(context, null);
},
icon: const Icon(Icons.arrow_back),
);
}
// third overwrite to show query result
#override
Widget buildResults(BuildContext context) {
List<String> matchQuery = [];
for (var documentName in data!) {
if (documentName.recipientName!.contains(query.toLowerCase())) {
matchQuery.add(documentName.recipientName.toString());
}
}
return ListView.builder(
physics: const BouncingScrollPhysics(),
itemCount: matchQuery.length,
itemBuilder: (context, index) {
return InkWell(
onTap: () {
Navigator.pop(context);
UtilFunctions.navigateTo(context,
MainScreen(patientid: data![index].senderId.toString()));
query = "";
},
child: ProfileList(
patientid: data![index].senderId.toString(),
phn: data![index].phn.toString(),
sharedDate: data![index].sharedDate.toString(),
username: data![index].senderName.toString(),
));
},
);
}
#override
Widget buildSuggestions(BuildContext context) {
List<String> matchQuery = [];
for (var document in data!) {
if (document.senderName!.toLowerCase().contains(query.toLowerCase())) {
matchQuery.add(document.senderName.toString());
}
}
return ListView.builder(
physics: const BouncingScrollPhysics(),
itemCount: matchQuery.length,
itemBuilder: (context, index) {
return InkWell(
onTap: () {
close(context, null);
query = "";
UtilFunctions.navigateTo(context,
MainScreen(patientid: data![index].senderId.toString()));
},
child: ProfileList(
patientid: data![index].senderId.toString(),
phn: data![index].phn.toString(),
sharedDate: data![index].sharedDate.toString(),
username: data![index].senderName.toString(),
));
},
);
}
}
you can check my video here listview no updating
import 'package:flutter/material.dart';
import 'package:flutter/src/widgets/container.dart';
import 'package:flutter/src/widgets/framework.dart';
class SearchBarWidget extends StatefulWidget {
const SearchBarWidget({super.key});
#override
State<SearchBarWidget> createState() => _SearchBarWidgetState();
}
class _SearchBarWidgetState extends State<SearchBarWidget> {
List<String> list = ["Arshad", "khan", "junaid", "Babar", "Tariq", "Mobile"];
String search = "";
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: TextField(
onChanged: (value) {
setState(() {
search = value;
});
},
),
),
body: ListView.builder(
itemCount: list.length,
itemBuilder: (context, index) {
if (search.isEmpty) {
return ListTile(
title: Text(list[index]),
);
}
if (list[index].toLowerCase().contains(search)) {
return ListTile(
title: Text(list[index]),
);
}
return Container();
},
),
);
}
}
You can use idea.
Note: remove the for loop and and write the if condition in ListView.builder

Flutter: Show different widget conditionally

I'm making an app where I want to show a progress indicator before the API returns some data. If there is any data on the API call then I want to show Widget-A or else Widget-B. But I'm not sure how to show Widget-B.
I'm only able to do the following so far...
list!.isNotEmpty
? ListView.builder(
itemCount: list!["filtered"]["data"].length,
itemBuilder: (_, index) {
return ListTile(
title: Text(
'${list!["filtered"]["data"][index]["strikePrice"]}'),
);
},
)
: LinearProgressIndicator(),
);
Here ListView.builder() is Widget-A and a button would be Widget-B, which IDK how to show if list is empty.
Can you help me achieve this? TIA.
You could track if it's loading or not like this:
class _NiftyScreenState extends State<NiftyScreen> {
Map<String, dynamic>? niftyDetails = {};
late bool isLoading;
#override
void initState() {
super.initState();
getNiftyDetails();
}
getNiftyDetails() async {
setState(() {
isLoading = true;
});
try {
this.niftyDetails = await fetchNiftyData();
catch (err) {
print(err);
// Here you can save the error message if you want to show it
}
setState(() {
isLoading = false;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: isLoading
? LinearProgressIndicator()
: niftyDetails!.isNotEmpty
? ListView.builder(
itemCount: niftyDetails!["filtered"]["data"].length,
itemBuilder: (_, index) {
return ListTile(
title: Text(
'${niftyDetails!["filtered"]["data"][index]["strikePrice"]}'),
);
},
)
: WidgetB(),
);
}
}
Take a boolean variable, let's say bool _isLoading;
Just before you trigger your API call set it to true setState(() { _isLoading = true });,
then as soon as you receive response from your API set the boolean value to false setState(() { _isLoading = false});.
Lastly,
!_isLoading?
list!.isNotEmpty
? ListView.builder(
itemCount: list!["filtered"]["data"].length,
itemBuilder: (_, index) {
return ListTile(
title: Text(
'${list!["filtered"]["data"][index]["strikePrice"]}'),
);
},
)
: Container(
child: Text("list is empty"), // show whatever you'd like to when list is empty
)
: LinearProgressIndicator(),
);
Or you could use a layout builder
list!.isNotEmpty
? LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
final remoteList = list!["filtered"]["data"];
if (remoteList.isNotEmpty) {
return ListView.builder(
itemCount: list!["filtered"]["data"].length,
itemBuilder: (_, index) {
return ListTile(
title: Text(
'${list!["filtered"]["data"][index]["strikePrice"]}'),
);
},
);
} else {
return WidgetB();
}
},
),
: LinearProgressIndicator(),
One way of doing is by setting a variable before the condition.
Widget widgetToShow = (some condition) ? widgetA : WidgetB;
list!.isNotEmpty
? widgetToShow
: LinearProgressIndicator(),
);
or just use one variable for everything, using IF of CASE statements to handle 3 possible widgets(A, B and ProgressIndicator).

how to trigger search automatically when using SearchDelegate buildSuggestions in flutter

Now I am using SearchDelegate in flutter 2.0.1, this is my buildSuggestions code:
#override
Widget buildSuggestions(BuildContext context) {
var channelRequest = new ChannelRequest(pageNum: 1, pageSize: 10, name: query);
if (query.isEmpty) {
return Container();
}
return FutureBuilder(
future: ChannelAction.fetchSuggestion(channelRequest),
builder: (context, AsyncSnapshot snapshot) {
if (snapshot.hasData) {
List<ChannelSuggestion> suggestions = snapshot.data;
return buildSuggestionComponent(suggestions, context);
} else {
return Text("");
}
});
}
Widget buildSuggestionComponent(List<ChannelSuggestion> suggestions, BuildContext context) {
return ListView.builder(
itemCount: suggestions.length,
itemBuilder: (context, index) {
return ListTile(
title: Text('${suggestions[index].name}'),
onTap: () async {
query = '${suggestions[index].name}';
},
);
},
);
}
when select the recommand text, I want to automatically trigger search event(when I click the suggestion text, trigger the search, fetch data from server side and render the result to UI) so I do not need to click search button. this is my search code:
#override
Widget buildResults(BuildContext context) {
var channelRequest = new ChannelRequest(pageNum: 1, pageSize: 10, name: query);
return buildResultImpl(channelRequest);
}
Widget buildResultImpl(ChannelRequest channelRequest) {
return FutureBuilder(
future: ChannelAction.searchChannel(channelRequest),
builder: (context, AsyncSnapshot snapshot) {
if (snapshot.hasData) {
List<Channel> channels = snapshot.data;
return buildResultsComponent(channels, context);
} else {
return Text("");
}
return Center(child: CircularProgressIndicator());
});
}
what should I do to implement it? I have tried invoke buildResults function in buildSuggestionComponent but it seems not work.
To update the data based on the query, you can make an API call to get the result when clicking on a suggestion, then use a StreamController to stream the results to the buildResults() method and call showResults().
I'm creating a simple app here for demonstration:
import 'dart:async';
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(home: Home()));
}
class Home extends StatefulWidget {
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
final _controller = StreamController.broadcast();
#override
dispose() {
super.dispose();
_controller.close();
}
Future<void> _showSearch() async {
await showSearch(
context: context,
delegate: TheSearch(context: context, controller: _controller),
query: "any query",
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Search Demo"),
actions: <Widget>[
IconButton(
icon: Icon(Icons.search),
onPressed: _showSearch,
),
],
),
);
}
}
class TheSearch extends SearchDelegate<String> {
TheSearch({this.context, this.controller});
BuildContext context;
StreamController controller;
final suggestions =
List<String>.generate(10, (index) => 'Suggestion ${index + 1}');
#override
List<Widget> buildActions(BuildContext context) {
return [IconButton(icon: Icon(Icons.clear), onPressed: () => query = "")];
}
#override
Widget buildLeading(BuildContext context) {
return IconButton(
icon: AnimatedIcon(
icon: AnimatedIcons.menu_arrow,
progress: transitionAnimation,
),
onPressed: () {
close(context, null);
},
);
}
#override
Widget buildResults(BuildContext context) {
return StreamBuilder(
stream: controller.stream,
builder: (context, snapshot) {
if (!snapshot.hasData)
return Container(
child: Center(
child: Text('Empty result'),
));
return Column(
children: List<Widget>.generate(
snapshot.data.length,
(index) => ListTile(
onTap: () => close(context, snapshot.data[index]),
title: Text(snapshot.data[index]),
),
),
);
},
);
}
#override
Widget buildSuggestions(BuildContext context) {
final _suggestions = query.isEmpty ? suggestions : [];
return ListView.builder(
itemCount: _suggestions.length,
itemBuilder: (content, index) => ListTile(
onTap: () {
query = _suggestions[index];
// Make your API call to get the result
// Here I'm using a sample result
controller.add(sampleResult);
showResults(context);
},
title: Text(_suggestions[index])),
);
}
}
final List<String> sampleResult =
List<String>.generate(10, (index) => 'Result ${index + 1}');
I have done it through a simple workaround
Simply add this line after your database call
query = query
But be careful of the call looping

Using search delegate for a listview generated from Future

Here is my listview generated from a Future from a json file.
class _ChorusPage extends State<ChorusPage> {
static Future<List<Chorus>> getList() async {
var data = await rootBundle.loadString('assets/chorusJson.json');
var jsonMap = json.decode(data); // cast<Map<String, dynamic>>();
List<Chorus> choruses = [];
for (var c in jsonMap) {
Chorus chorus = Chorus.fromJson(c);
choruses.add(chorus);
}
// var = User.fromJson(parsedJson);
// choruses = jsonMap.map<Chorus>((json) => Chorus.fromJson(json)).toList();
print(choruses.length);
return choruses;
}
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(title: new Text('Chorus'), actions: <Widget>[
IconButton(icon: Icon(Icons.search), onPressed: () {})
]),
body: Container(
child: FutureBuilder(
future: getList(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.data == null) {
return Container(
child: Center(child: CircularProgressIndicator()));
} else {
return ListView.builder(
itemCount: snapshot.data.length, // + 1,
itemBuilder: (BuildContext context, int index) {
return _listItem(index, snapshot);
});
}
})),
);
}
I am trying to implement a search function using the search delegate. The tutorial I am watching searches a List (https://www.youtube.com/watch?v=FPcl1tu0gDs&t=444s). What I have here is a Future. I am wondering how do you convert a future into a List. Or is there any other workaround.
class DataSearch extends SearchDelegate<String> {
Future<List<Chorus>> chorusList = _ChorusPage.getList();
// ????????????????????? How do I convert.
#override
List<Widget> buildActions(BuildContext context) {
// actions for app bar
return [IconButton(icon: Icon(Icons.clear), onPressed: () {})];
}
#override
Widget buildLeading(BuildContext context) {
// leading icon on the left of the app bar
return IconButton(
icon: AnimatedIcon(
icon: AnimatedIcons.menu_arrow,
progress: transitionAnimation,
),
onPressed: () {});
}
#override
Widget buildResults(BuildContext context) {
// show ssome result based on the selection
throw UnimplementedError();
}
#override
Widget buildSuggestions(BuildContext context) {
/*
final suggestionList = query.isEmpty ? recentChorus : chorus;
return ListView.builder(
itemBuilder: (context, index) => ListTile(
title: Text(suggestList[chorus]),
),
itemCount: suggestionList.length,
);
// show when someone searches for
*/
}
}
In my opinion you should set your chorusList and call somewhere your getList method with the .then method store the value inside your chorusList.
List<Chorus> chorusList;
_ChorusPage.getList().then((value) => chorusList);

Displaying Snackbar inside a SearchDelegate

I am using a SearchDelegate and want to display a Snackbar when the user tries to perform a search with an empty query. I've tried returning Scaffold widgets from both the buildSuggestions and buildResults methods and then using a Builder / GlobalKey inside the buildResults method to display a message to the user if the search query has a length of zero. However this leads to the Scaffold's state being updated during the render method which throws an exception. Has anyone dealt with a similar challenge? Seems like a common use case that you would want to display a Snackbar inside your search delegate, yet I can't seem to fathom an easy way to do it.
Figured it out
class DataSearch extends SearchDelegate<String> {
List<Drug> drugList = new List<Drug>();
DataSearch(Future<List<Drug>> listDrugName) {
this.drugListFuture = listDrugName;
}
#override
List<Widget> buildActions(BuildContext context) {
// actions for app bar
return [
IconButton(
icon: Icon(Icons.clear),
onPressed: () {
query = "";
})
];
}
#override
Widget buildLeading(BuildContext context) {
// leading icon on the left of app bar
return IconButton(
icon: AnimatedIcon(
icon: AnimatedIcons.menu_arrow, progress: transitionAnimation),
onPressed: () {
close(context, null);
});
}
#override
Widget buildResults(BuildContext context) {
// show result from selection
return null;
}
#override
Widget buildSuggestions(BuildContext context) {
return new FutureBuilder(
future: db.getDrugEntries(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (!snapshot.hasData || snapshot.data.length < 1) {
return new Center(
child: new LoadingIndicator(Constants.msgLoading));
} else {
drugList = snapshot.data;
// show when user searches for something
final suggestionList = query.isEmpty
? drugList
: drugList
.where((r) =>
(r.drugId.toLowerCase())
.contains(query.toLowerCase()) ||
(r.fullDrugName.toLowerCase())
.contains(query.toLowerCase()) ||
(r.otherName.toLowerCase())
.contains(query.toLowerCase()) ||
(r.tradeName.toLowerCase())
.contains(query.toLowerCase()))
.toList();
return ListView.builder(
itemBuilder: (context, index) {
String drugName = suggestionList[index].genericName;
String drugId = suggestionList[index].drugId;
int queryIndex = drugName.indexOf(query);
if (queryIndex == -1) {
queryIndex = 0;
}
int queryIndexEnd = queryIndex + query.length;
return Container(button//...onTap:_launchExtraContent(context,drugId);
},
itemCount: suggestionList.length,
);
}
});
}
_
_launchExtraContent(BuildContext context, StringtheFileName) async {
try {
//......
} catch (e) {
_showSnackBar(context,'ERROR: Unable to retrieve file please submit a bug report');
}
}
void _showSnackBar(BuildContext context, String text) {
Scaffold.of(context).showSnackBar(new SnackBar(
content: new Text(
text,
textAlign: TextAlign.center,
),
backgroundColor: Colors.red,
));
}
}