Statefulwidget is not refreshing ListView - flutter

I'm saving the data that is fetched from an API to the sqflite in flutter project, everything is working good, except that after clicking a raised button the data should be insert into the table and a new page should be open but there is no data unless I refresh that page so the data appear
As you can see, here is the code of the raised button:
RaisedButton(
child: Text('Get Cities'),
onPressed: () async {
setState(() {
GetAllData.data.Getdata();
});
await Navigator.push(context, MaterialPageRoute<void>(
builder: (BuildContext context) => StoreList()));
setState(() {});
},
)
Inside the setState I'm calling a function Getdata to get the data from the sqflite, after it getting it the app should open a new page
And below is the code of the page which should show the data in a ListView:
class StoreList extends StatefulWidget { #override
_StoreListState createState() => _StoreListState();}
class _StoreListState extends State<StoreList> {
#override void initState() {
super.initState();
setState(() {
DatabaseProvider_API.db.getRoutes();
});}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Stores List'),
),
body: FutureBuilder<List<Stores>>(
future: DatabaseProvider_API.db.getStores(),
builder: (context, snapshot){
if(snapshot.data == null){
return Center(
child: CircularProgressIndicator(),
);
}
else {
return ListView.separated(
separatorBuilder: (BuildContext context, int index){
return Divider();
},
itemCount: snapshot.data.length,
itemBuilder: (BuildContext context, int index){
String name = snapshot.data[index].sTORENAME;
String name_ar = snapshot.data[index].cITY;
return ListTile(
title: Text(name),
subtitle:Text (name_ar),
onTap: ()async{
setState(() {
});
await
Navigator.push(context, MaterialPageRoute<void>(
builder: (BuildContext context) => Category() ));
},
);
},
);
}
},
),
floatingActionButton: new FloatingActionButton(
onPressed: () {
setState(() {});
},
child: new Icon(Icons.update),
),
);
}
}

Try to add the await keyword before evoke GetAllData.data.GetData()
RaisedButton(
child: Text('Get Cities'),
onPressed: () async {
// await for new data to be inserted
await GetAllData.data.Getdata();
await Navigator.push(context, MaterialPageRoute<void>(
builder: (BuildContext context) => StoreList()));
setState(() {
dataFuture = GetAllData.data.Getdata();
});
},
)

Related

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

close Simple Dialog in flutter when setState needs to called

I'm having a problem calling Navigator.of(context).pop() on my onPressed property in SimpleDialogOption widget. I need to set the state and dismiss the dialog. But calling setState is preventing my dialog to close. Without setState the dialog closes. Here is my dialog
WidgetsBinding.instance.addPostFrameCallback((_) {
showDialog(
builder: (BuildContext context) {
return SimpleDialog(
children: _children(suburbs),
backgroundColor: Colors.white,
title: Text('Pick your suburb'),
);
},
context: context);
});
and the method I use for the list of the Dialog:
List<Widget> _children(List<Suburb> suburbs) {
return suburbs
.map((suburb) => SimpleDialogOption(
onPressed: () {
print('#####################');
setState(() {
postcode = suburb.name;
});
Navigator.of(context).pop();
},
child: Text(suburb.name)))
.toList();
}
you can await until the return value comes from the navigator.pop,
and then call a setState
WidgetsBinding.instance.addPostFrameCallback((_) async {
postcode = await showDialog(
builder: (BuildContext context) {
return SimpleDialog(
children: _children(suburbs),
backgroundColor: Colors.white,
title: Text('Pick your suburb'),
);
},
context: context);
setState(() {
postcode;
});
});
List<Widget> _children(List<Suburb> suburbs) {
return suburbs
.map((suburb) => SimpleDialogOption(
onPressed: () {
print('#####################');
Navigator.of(context).pop(suburb.name);
},
child: Text(suburb.name)))
.toList();
}

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.

Rebuild ListView after operation on List Flutter

I am trying to refresh the list of recipes after i delete one item and i have tried using SetState but I've got no result. I think the issue is that i get to this screen after making a GET call and that call is not executed again after deleting the item. What can I do?
void _pushRecipesByTypeScreen(String type) async {
var recipes = await entityApi.getRecipesByType(type);
Navigator.of(context).push(new MaterialPageRoute(builder: (context) {
return new Scaffold(
appBar: new AppBar(title: new Text(type)),
body: ListView.builder(
itemCount: recipes.length,
itemBuilder: (context, index) {
final item = recipes[index];
return _buildEntityItem2(item);
})
);
}));
}
Widget _buildEntityItem2(Entity entity) {
return ListTile(
title: Text(entity.name),
onTap: () => _pushDeleteScreen(entity),
);
}
void _pushDeleteScreen(Entity entity) {
showDialog(
context: context,
builder: (BuildContext context) {
return new AlertDialog(
title: new Text('Delete "${entity.name}"?'),
actions: <Widget>[
new FlatButton(
child: new Text('CANCEL'),
onPressed: () => Navigator.of(context).pop()),
new FlatButton(
child: new Text('CONFIRM'),
onPressed: () {
_removeEntityItem(entity);
setState(() {});
Navigator.of(context).pop();
})
]);
});
}
void _removeEntityItem(Entity entity) async {
if (await connectivity.checkConnectivity() != ConnectivityResult.none) {
bool success = await entityApi.deleteEntity(entity);
if (success) {
setState(() {});
}
}
}
The setstate should be used outside of dialog for it to reload your listview
void _pushRecipesByTypeScreen(String type) async {
var recipes = await entityApi.getRecipesByType(type);
Navigator.of(context).push(new MaterialPageRoute(builder: (context) {
return new Scaffold(
appBar: new AppBar(title: new Text(type)),
body: ListView.builder(
itemCount: recipes.length,
itemBuilder: (context, index) {
final item = recipes[index];
return _buildEntityItem2(item);
})
);
}));
}
Widget _buildEntityItem2(Entity entity) {
return ListTile(
title: Text(entity.name),
onTap: () async {
if(await _pushDeleteScreen(entity)) {
await _removeEntityItem(entity); //await until this completes
setState(() {});
}
}
);
}
Future<bool> _pushDeleteScreen(Entity entity) {
return showDialog(
context: context,
builder: (BuildContext context) {
return new AlertDialog(
title: new Text('Delete "${entity.name}"?'),
actions: <Widget>[
new FlatButton(
child: new Text('CANCEL'),
onPressed: () => Navigator.of(context).pop(false)),
new FlatButton(
child: new Text('CONFIRM'),
onPressed: () {
Navigator.of(context).pop(true);
})
]);
});
}
// make this return future
Future<void> _removeEntityItem(Entity entity) async {
if (await connectivity.checkConnectivity() != ConnectivityResult.none) {
bool success = await entityApi.deleteEntity(entity);
if (success) {
setState(() {});
}
}
}