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,
));
}
}
Related
I'm getting all the users from API and showing it on a list view. what I need to do is when I search for a specific username, then it needs to show a list tile of users that match the same name .after that when I click that username in the search list view I need to show the user profile of that user which I need user-id
I already used showSearch(context: context, delegate: CustomSearchDelegate());
and I fail
this is my UserList Api Class
class GetSharedPatientList with ChangeNotifier {
Future<List<Content>> fetchPatientList(
BuildContext context,
) async {
final bool isConnected = await InternetConnectionChecker().hasConnection;
final prefs = await SharedPreferences.getInstance();
var uuid = prefs.getString("userId");
final bool session = await Session.sessionValid(context);
if (isConnected) {
if (session) {
try {
final response =
await http.get(Uri.parse('$baseUrl/profile_shares?uuid=$uuid'));
if (response.statusCode == 200) {
List jsonResponse = json.decode(response.body)['content'];
print(jsonResponse);
return jsonResponse.map((data) => Content.fromJson(data)).toList();
} else {
throw Exception('Unexpected error occured!');
}
} catch (e) {
Logger().e(e);
}
} else {}
} else {
Alert(
context: context,
type: AlertType.error,
title: "No Internet",
desc: "Please Check Your Internet Connection",
buttons: [
DialogButton(
onPressed: () => Navigator.pop(context),
color: green,
child: const Text(
"Ok",
style: TextStyle(color: Colors.white, fontSize: 20),
),
)
]).show();
}
throw Exception('Failed to load user');
}
}
CustomSearchDelegate Class
class CustomSearchDelegate extends SearchDelegate {
getList(BuildContext context) async {
List<Content> data = await Provider.of<GetSharedPatientList>(context)
.fetchPatientList(context);
return data;
}
// first overwrite to
// clear the search text
#override
List<Widget>? buildActions(BuildContext context) {
return [
IconButton(
onPressed: () {
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<Content> matchQuery = getList(context);
for (var pateint in matchQuery) {
if (pateint.recipientName!.contains(query.toLowerCase())) {
matchQuery.add(pateint);
}
}
return ListView.builder(
itemCount: matchQuery.length,
itemBuilder: (context, index) {
return ProfileList(
patientid: matchQuery[index].senderId.toString(),
username: matchQuery[index].senderName.toString(),
phn: matchQuery[index].phn.toString(),
sharedDate: matchQuery[index].sharedDate.toString(),
);
},
);
}
// last overwrite to show the
// querying process at the runtime
#override
Widget buildSuggestions(BuildContext context) {
List<Content> matchQuery = getList(context);
for (var pateint in matchQuery) {
if (pateint.recipientName!.toLowerCase().contains(query.toLowerCase())) {
matchQuery.add(pateint);
}
}
return ListView.builder(
itemCount: matchQuery.length,
itemBuilder: (context, index) {
return InkWell(
onTap: () {
UtilFunctions.navigateTo(context,
MainScreen(patientid: matchQuery[index].senderId.toString()));
},
child: ProfileList(
patientid: matchQuery[index].senderId.toString(),
username: matchQuery[index].senderName.toString(),
phn: matchQuery[index].phn.toString(),
sharedDate: matchQuery[index].sharedDate.toString(),
));
},
);
}
}
When I run this I'm getting _TypeError (type 'Future<dynamic>' is not a subtype of type 'List<Content>')
Any Help is much appreciated
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
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
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);
I have a button and if pressed should return a future builder here is my code.
I already search some examples on the web but no luck, Im new in flutter development and trying to create a simple login with api call.
Future<AccessToken>fetchAccessToken() async{final token = await _repository.fetchToKen();
>>return token;
}
onPressed: () {FutureBuilder<AccessToken>(future:bloc.fetchAccessToken(),builder: (context, snapshot) {if (snapshot.hasError) {return Text('Error');} else if (snapshot.hasData) {return Text('data');} else {return `Center`(child: CircularProgressIndicator(),);}},);}
I want to show a progress indicator while waiting for the api response, but after I receive the response, my builder inside the future builder is not called.
You can't simply return a widget and place it in the widget tree like that. Maybe you can use conditional list for hiding and showing the FutureBuilder widget.
import 'package:flutter/material.dart';
class ApiWidget extends StatefulWidget {
#override
_ApiWidgetState createState() => _ApiWidgetState();
}
class _ApiWidgetState extends State<ApiWidget> {
Repository _repository = Repository();
Future<AccessToken> accessTokenFuture;
bool isButtonPressed = false;
#override
Widget build(BuildContext context) {
return Column(children: <Widget>[
FloatingActionButton(onPressed: () {
setState(() {
try {
isButtonPressed = true;
accessTokenFuture = fetchAccessToken();
} catch (_) {
print('Fetch error');
}
});
}, child: Icon(Icons.add),),
if(isButtonPressed)
FutureBuilder<AccessToken>(
future: bloc.fetchAccessToken(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Center(child: CircularProgressIndicator());
}
if (snapshot.hasError) {
return Text('Error');
}
Column(
children: <Widget>[Text(snapshot.data)],
);
},
),
],);
}
}
You can do something like that:
#override
Widget build(BuildContext context) {
return Column(children: <Widget>[
FloatingActionButton(onPressed: () {
setState(() {
try {
isLoading = true;
accessTokenFuture = await fetchAccessToken();
isLoading = false;
} catch (_) {
isLoading = false;
print('Fetch error');
}
});
}, child: Icon(Icons.add),),
_buildAsyncInfo(),
],);
}
Widget _buildAsyncInfo() {
return isLoading ?
CircularProgressIndicator() :
Column(
children: <Widget>[Text(snapshot.data)],
);
}