Using search delegate for a listview generated from Future - flutter

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);

Related

How to Implement Search Functionality In 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

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

Flutter How to Populate ListView on app launch with sqflite?

I'm trying to display data in a ListView with a FutureBuilder. In debug mode, when I launch the app, no data is displayed, but, if I reload the app (hot Reload or hot Restart), the ListView displays all the data. I already tried several approaches to solve this - even without a FutureBuilder, I still haven't succeeded. If I create a button to populate the ListView, with the same method "_getregistos()", the ListView returns the data correctly.
This is the code I'm using:
import 'package:flutter/material.dart';
import 'package:xxxxx/models/task_model.dart';
import 'package:xxxxx/shared/loading.dart';
class AddTask extends StatefulWidget {
static const id = 'add_task';
#override
_AddTaskState createState() => _AddTaskState();
}
class _AddTaskState extends State<AddTask> {
dynamic tasks;
final textController = TextEditingController();
_getRegistos() async {
List<TaskModel> taskList = await _todoHelper.getAllTask();
// print('DADOS DA tasklist: ${taskList.length}');
return taskList;
}
TaskModel currentTask;
final TodoHelper _todoHelper = TodoHelper();
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
padding: EdgeInsets.all(32),
child: Column(
children: <Widget>[
TextField(
controller: textController,
),
FlatButton(
child: Text('Insert'),
onPressed: () {
currentTask = TaskModel(name: textController.text);
_todoHelper.insertTask(currentTask);
},
color: Colors.blue,
textColor: Colors.white,
),
//
FutureBuilder(
future: _getRegistos(),
builder: (context, snapshot) {
if (snapshot.hasData) {
tasks = snapshot.data;
return ListView.builder(
shrinkWrap: true,
itemCount: tasks == null ? 0 : tasks.length,
itemBuilder: (BuildContext context, int index) {
TaskModel t = tasks[index];
return Card(
child: Row(
children: <Widget>[
Text('id: ${t.id}'),
Text('name: ${t.name}'),
IconButton(
icon: Icon(Icons.delete), onPressed: () {})
],
),
);
},
);
}
return Loading();
}),
],
),
),
);
}
}
Thank you.
You need to use ConnectionState inside your builder. Look at this code template: (Currently your builder returns ListView widget without waiting for the future to complete)
return FutureBuilder(
future: yourFuture(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
// future complete
// if error or data is false return error widget
if (snapshot.hasError || !snapshot.hasData) {
return _buildErrorWidget();
}
// return data widget
return _buildDataWidget();
// return loading widget while connection state is active
} else
return _buildLoadingWidget();
},
);
Thanks for your help.
I already implemented ConnectionState in the FutureBuilder and the issue persists.
When I launch the app, I get error "ERROR or No-Data" (is the message I defined in case of error of no-data.
If I click on the FlatButton to call the method "_getTasks()", the same method used in FutureBuilder, everything is ok. The method return data correctly.
This is the code refactored:
import 'package:flutter/material.dart';
import 'package:xxxx/models/task_model.dart';
import 'package:xxxx/shared/loading.dart';
class AddTask extends StatefulWidget {
static const id = 'add_task';
#override
_AddTaskState createState() => _AddTaskState();
}
class _AddTaskState extends State<AddTask> {
final textController = TextEditingController();
Future<List<TaskModel>> _getTasks() async {
List<TaskModel> tasks = await _todoHelper.getAllTask();
print('Tasks data: ${tasks.length}');
return tasks;
}
TaskModel currentTask;
//list to test with the FlatButton List all tasks
List<TaskModel> tasksList = [];
final TodoHelper _todoHelper = TodoHelper();
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
padding: EdgeInsets.all(32),
child: Column(
children: <Widget>[
TextField(
controller: textController,
),
FlatButton(
child: Text('Insert'),
onPressed: () {
currentTask = TaskModel(name: textController.text);
_todoHelper.insertTask(currentTask);
},
color: Colors.blue,
textColor: Colors.white,
),
//when clicking on this flatButton, I can populate the taskList
FlatButton(
child: Text('Show all Tasks'),
onPressed: () async {
List<TaskModel> list = await _getTasks();
setState(() {
tasksList = list;
print(
'TaskList loaded by "flatButton" has ${tasksList.length} rows');
});
},
color: Colors.red,
textColor: Colors.white,
),
//
FutureBuilder(
future: _getTasks(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
// future complete
// if error or data is false return error widget
if (snapshot.hasError || !snapshot.hasData) {
return Text('ERROR or NO-DATA');
}
// return data widget
return ListItems(context, snapshot.data);
// return loading widget while connection state is active
} else
return Loading();
},
),
],
),
),
);
}
}
//*****************************************
class ListItems extends StatelessWidget {
final List<TaskModel> snapshot;
final BuildContext context;
ListItems(this.context, this.snapshot);
#override
Widget build(BuildContext context) {
return Expanded(
child: ListView.builder(
itemCount: snapshot == null ? 0 : snapshot.length,
itemBuilder: (context, index) {
TaskModel t = snapshot[index];
return Text(' ${t.id} - ${t.name}');
}),
);
}
}

Flutter - StreamBuilder - Refresh

I have a StreamBuilder inside my Widget build of UserListDart:
StreamBuilder(
stream: stream.asStream(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if(snapshot.hasData) {
return Expanded(
child: ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (BuildContext context, int index) {
return ListTile(
title: Text(
snapshot.data[index].firstname + " " +
snapshot.data[index].lastname
),
onTap: () {
Navigator.of(context).push(DetailScreenDart(snapshot.data[index]));
},
);
}
)
);
}
}
...
)
The Stream is defined in the initState:
Future<List> stream;
#override
void initState() {
super.initState();
stream = fetchPost();
}
The fetchPost() is an api call:
Future<List<User>> fetchPost() async {
final response = await http.get('url');
final jsonResponse = json.decode(response.body);
List<User> users = [];
for(var u in jsonResponse){
User user = User(
firstname: u["firstname"],
lastname: u["lastname"],
);
users.add(user);
}
return users;
}
I Navigate to another Page to change for example the firstname (api get updated) and I Navigate back to the UserList:
Navigator.pushReplacement(
context,
new MaterialPageRoute(builder: (context) => new UserListDart())
).then((onValue) {
fetchPost();
});
But the StreamBuilder won't get updated and I don't know why.
Note:
I think the StreamBuilder don't realise that a change has happend when I navigate back. It only applies the changes if I reopen the Page..
You should be using setState and updating your stream variable with the result of the fetchList() call:
Navigator.pushReplacement(
context,
new MaterialPageRoute(builder: (context) => new UserListDart())
).then((onValue) {
setState((){
stream = fetchPost();
});
});
Here's a working example of what you want to achieve:
class StreamBuilderIssue extends StatefulWidget {
#override
_StreamBuilderIssueState createState() => _StreamBuilderIssueState();
}
class _StreamBuilderIssueState extends State<StreamBuilderIssue> {
Future<List<String>> futureList;
List<String> itemList = [
'item 1',
'item 1',
'item 1',
'item 1',
'item 1',
];
#override
void initState() {
futureList = fetchList();
super.initState();
}
#override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Expanded(
child: Center(
child: StreamBuilder(
stream: futureList.asStream(),
builder: (context, snapshot){
if(snapshot.hasData){
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (context, index){
return Text(snapshot.data[index]);
},
);
}else{
return CircularProgressIndicator();
}
},
),
),
),
RaisedButton(
onPressed: goToAnotherView,
child: Text('Next View'),
),
RaisedButton(
onPressed: addItem,
child: Text('AddItem'),
)
],
),
);
}
Future<List<String>> fetchList(){
return Future.delayed(Duration(seconds: 2), (){
return itemList;
});
}
void goToAnotherView(){
Navigator.push(context, MaterialPageRoute(
builder: (context){
return StreamBuilderIssueNewView(addItem);
})
).then((res){
setState(() {
futureList = fetchList();
});
});
}
void addItem(){
itemList.add('anotherItem');
}
}
class StreamBuilderIssueNewView extends StatelessWidget {
final Function buttonAction;
StreamBuilderIssueNewView(this.buttonAction);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: Column(
children: <Widget>[
Text('New view'),
RaisedButton(
onPressed: buttonAction,
child: Text('AddItem'),
)
],
),
),
);
}
}
By the way, you could also just use a FutureBuilder as your are not using a real Stream here, just an api fetch and you have to update with setState anyway.

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,
));
}
}