FutureBuilder only works in Debug - flutter

I have a FutureBuilder with a ListView to display custom items (Widgets) with values which are read from .txt files.
The problem is that these items are only displayed if I launch the app in Debug-mode or run-mode. When I try to open the app with the AppLauncher (like a "normal" user would do it) the listView is empty. I tried this on an AVD and on a "real" device.
the Future "listFuture" is used to read the values from the files and return a list of Widgets
class Home extends StatefulWidget {
final Future listFuture = setupList();
#protected
#mustCallSuper
void initState() {
print("init complete");
}
#override
State<StatefulWidget> createState() {
return HomeState();
}
}
If the FutureBuilder gets the data correctly a listView with the list of my widgets should be displayed
child: FutureBuilder<List<SubListItem>>(
future: widget.listFuture,
// ignore: missing_return
builder: (BuildContext context, AsyncSnapshot snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
return new Text("None");
case ConnectionState.waiting:
return new Text("loading");
default:
if (snapshot.hasError) {
print("Error");
return Center(child: (Text("No data")));
} else {
return subListView(context, snapshot);
}
}
},
),
Widget subListView(BuildContext context, AsyncSnapshot snapshot) {
List<Widget> items = snapshot.data;
//This ScrollConfiguration is used to remove any animations while scrolling
return ScrollConfiguration(
behavior: CustomScrollBehavior(),
child: Container(
padding: EdgeInsets.symmetric(horizontal: 4),
child: new ListView.builder(
itemCount: items.length,
itemBuilder: (BuildContext context, int index) {
return Column(
children: <Widget>[items[index]],
);
},
),
),
);
}
Thanks for helping!

Ok, I solved the problem. You just have to call "setState" when your Widget is built.
#protected
#mustCallSuper
void initState() {
super.initState();
Future.delayed(Duration.zero, () {
//This setState is necessary because it refreshes the listView
setState(() {});
});
}

It's looks like a async data issue, try these changes:
Remove listFuture from your StatefulWidget.
Add the listFuture var inside your State.
Move the setupList() method inside your State.
And finally call directly like this:
child: FutureBuilder<List<SubListItem>>(
future: setupList(),
// ignore: missing_return
builder: (BuildContext context, AsyncSnapshot snapshot) {
if(!snapshot.hasData) {
return new Text("loading");
}
else if (snapshot.hasError) {
print("Error");
return Center(child: (Text("No data")));
} else {
return subListView(context, snapshot);
}
}
},
),

Related

How can I listen to changes of multiple providers from a future builder?

I want to display a BarChart where I use as source information two different providers.
My initial approach was to use a future builder where I show a loading icon while the data is being fetched, and then manipulate that data to suite my needs in the graph.
So I used a future builder from the widget where I display my graph and initialized it with a Future that will get the context from another reusable file.
my_wdiget.dart
...
class _MyWidgetState extends State<MyWidget> {
late Future<List<MyObject>> myFutureVariable;
Future<List<MyObject>> _getMyObjects() async {
return getMyObjects(context);
}
#override
void initState() {
super.initState();
myFutureVariable= _getMyObjects();
}
...
FutureBuilder<List<MyObject>>(
future: myFutureVariable,
builder: (BuildContext context,
AsyncSnapshot<List<MyObject>> snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return Center(child: CircularProgressIndicator())),
);
default:
if (snapshot.hasError) {
return Center(
child: Text('Error: ${snapshot.error}'))),
);
} else if (snapshot.data == null) {
return Center(
child:
Text('You don\'t have any item yet.'))),
);
} else {
return BarChart(
_getChartData(snapshot.data!),
),
);
}
}
},
),
And this is the file where I generate the data:
my_object_utils.dart
Future<List<MyObject>> getMyObjects(BuildContext context) async {
await Future.delayed(Duration(seconds: 1)); // Simulation delayed query
var source1= Provider.of<MySource1>(context, listen: false).items;
var source2 = Provider.of<MySource2>(context, listen: false).otherItems;
List<MyObject> myObjects= [];
// Do some stuff to fill the myObjects using source1 and source2
return myObjects;
}
Problems:
This kind of works but I get the warning use_build_context_synchronously from the lines of the Provider.
I want to listen to changes, but if I set the default listen: true it will crash telling me to change that property.
So my question is, how can I have a FutureBuilder listening to changes of multiple providers?
Update using approach suggested #hydra:
If I have:
void test() {
print('a');
setState(() {});
}
Consumer2<MySource1, MySource1>(
builder: (context, sourceOne, sourceTwo, child) {
myFutureVariable = getMyObjects(sourceOne.items, sourceTwo.otherItems),
return FutureBuilder<List<MyObject>>(
future: myFutureVariable,
builder: (context, snapshot) {
...
else{
return child: ElevatedButton(
child: Text('a'),
onPressed: test,
);
}
}
),
},
),
Every time the button is pressed it will trigger the setState and and the circularProgressIndicator will appear although no changes were made in the consumers.
to solve both problems you can use Consumer2
the FutureBuilder will rebuild if either of the two provider changed
Consumer2<MySource1, MySource1>(
builder: (context, sourceOne, sourceTwo, child) {
return FutureBuilder<List<MyObject>>(
future: myFutureVariable(sourceOne.items, sourceTwo.otherItems),
builder: (context, snapshot) {
// ...
}
),
},
),
and update your function to:
Future<List<MyObject>> getMyObjects(final items, final otherItems) async {
// use your items and otherItems here.
await Future.delayed(Duration(seconds: 1)); // just for testing, right?
List<MyObject> myObjects= [];
// Do some stuff to fill the myObjects using source1 and source2
return myObjects;
}

Correct way to load ListView data source initially

I have a stateful widget whose state builds a ListView. The ListView gets its data from an http API. I am using a Future<void> method called getData to retrieve this data and populate a List<> with it before calling setState.
My question is where should I call getData when this screen first launches? If I call it in initState(), I get the following error in the debug console:
[VERBOSE-2:ui_dart_state.cc(198)] Unhandled Exception: dependOnInheritedWidgetOfExactType<_InheritedTheme>() or dependOnInheritedElement() was called before _EventListState.initState() completed.
If I wrap the call to getData in a delayed Future, I do not see the error. Here's my code:
class _EventListState extends State<EventList> {
Future<void> getData() async {
events = [];
events = await Network.getUsers(context);
setState(() {});
}
List<Event> events = [];
#override
initState() {
super.initState();
getData(); // this cause the error
// Future.delayed(Duration(seconds: 1), getData); // this works
}
#override
build(context) {
return PlatformScaffold(
iosContentPadding: true,
body: ListView.builder(
padding: const EdgeInsets.all(10),
physics: const AlwaysScrollableScrollPhysics(),
itemCount: events.length,
itemBuilder: (context, index) => Text(events[index].summary),
),
);
}
}
Forcing a delay to retrieve the data does not feel right, so is there a better way?
Use FutureBuilder.
List<Event> events = [];
#override
Widget build(BuildContext context) {
return FutureBuilder(
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
return PlatformScaffold(
iosContentPadding: true,
body: ListView.builder(
padding: const EdgeInsets.all(10),
physics: const AlwaysScrollableScrollPhysics(),
itemCount: events.length,
itemBuilder: (context, index) => Text(events[index].summary),
),
);
} else if (snapshot.hasError) {
return Center(child: Text('Error: ${snapshot.error}'));
} else {
return Center(child: Text('Please wait its loading...'));
}
},
future: getData(),
);
}
Future<void> getData() async {
events = [];
events = await Network.getUsers(context);
}

Error trying to build a ListView in a Flutter FutureBuilder

I am new to Flutter and building a small app to record my expenses and learn a bit.
I am using Hive to store data. Now I am building a page which targets to show all the previously saved entries. I do this by creating a List with all the data and then trying to use a FutureBuilder to show the data in a ListView.
This is the code so far:
class LogScreen extends StatefulWidget {
const LogScreen({Key? key}) : super(key: key);
#override
_LogScreenState createState() => _LogScreenState();
}
class _LogScreenState extends State<LogScreen> {
get futureEntries => getEntries();
#override
void initState() {
// TODO: implement initState
super.initState();
}
#override
Widget build(BuildContext context) {
return FutureBuilder<Widget>(
future: futureEntries,
builder: (BuildContext context, AsyncSnapshot<Widget> snapshot) {
if (snapshot.hasData) {
return Container(
child: ListView.builder(
itemCount: futureEntries.length,
itemBuilder: (context, index) {
Entry currentEntry = Hive.box<Entry>('entriesBox').getAt(index);
return ListTile(
title: Text('${currentEntry.description}'),
);
},
),
);
} else {
return CircularProgressIndicator();
}
}
);
}
Future<List> getEntries() async {
List listEntries = await DbHelper().getListEntries();
print(listEntries);
return listEntries;
}
}
I am getting the following error though:
The following _TypeError was thrown building LogScreen(dirty, state: _LogScreenState#75644):
type 'Future<List<dynamic>>' is not a subtype of type 'Future<Widget>?'
The relevant error-causing widget was:
LogScreen file:///home/javier/StudioProjects/finanzas/lib/main.dart:55:14
When the exception was thrown, this was the stack:
#0 _LogScreenState.build (package:finanzas/log_screen.dart:29:17)
Could someone please tell me what I am doing wrong and suggest a solution? I come from Python and am having a though time with all these types :-P
Thanks in advance.
The generic type of FutureBuilder<T>() should correspond to the data type your Future will return, not what the builder is building. In your case you have FutureBuilder<Widget> so it expects a Future<Widget>, but your getEntries returns a Future<List<dynamic>>. So this is what the error is hinting at. Your code should probably look like this:
return FutureBuilder<List<Entry>>(
future: futureEntries,
builder: (BuildContext context, AsyncSnapshot<List<Entry>> snapshot) {
if (snapshot.hasData) {
return Container(
child: ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (context, index) {
Entry currentEntry = snapshot.data[index];
return ListTile(
title: Text('${currentEntry.description}'),
);
},
),
);
} else {
return CircularProgressIndicator();
}
}
);
Also note that i replaced the references in your ListView.builder from directly referencing your future to using the data inside the snapshot
Alright. After some research, here's the code that got to work:
Widget build(BuildContext context) {
return FutureBuilder<List>(
future: futureEntries,
builder: (BuildContext context, AsyncSnapshot<List> snapshot) {
if (snapshot.hasData) {
return Container(
child: ListView.builder(
itemCount: snapshot.data!.length,
itemBuilder: (context, index) {
Entry currentEntry = snapshot.data![index];
return ListTile(
title: Text('${currentEntry.description}'),
);
},
),
);
} else {
return CircularProgressIndicator();
}
}
);
}
Future<List> getEntries() async {
List listEntries = await DbHelper().getListEntries();
print(listEntries);
return listEntries;
}
I don't know yet exactly what the exclamation marks after 'data' do, but they did the trick.

I am failing to get data from cloud firestore while using flutter

At first, when i started writing my calls to get data from firestore, it worked. But when i tried writing more docs to my collection, it failed to bring data for the docs i recently added. Then, when i deleted the first one i added, i stopped receiveing data from firestore all together. I have tried several methods, but have all ended in failure.
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
class collect extends StatefulWidget {
#override
_collectState createState() => _collectState();
}
class _collectState extends State<collect>
{
Future _data;
void initState()
{
super.initState();
_data = getStuff();
}
Future getStuff()
async {
var firestore = FirebaseFirestore.instance;
QuerySnapshot qn = await firestore.collection("buses").get();
return qn.docs;
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder(
future: _data,
builder: (_, snapshot)
{
if(snapshot.connectionState == ConnectionState.waiting)
{
return Center(
child:Text("Loading")
);
}
else if(snapshot.connectionState == ConnectionState.done)
{
return ListView.builder(itemCount: snapshot.data.length,itemBuilder:(_, index)
{
return Container(
child: ListTile(
title: Text(snapshot.data[index].data()["name"].toString()),
subtitle: Text(snapshot.data[index].data()["price"].toString()),
),
);
});
}
},
),
);
}
}
```![enter image description here](https://i.stack.imgur.com/L7FqF.jpg)
Define your database call as,
Future getStuff() async {
var docs;
await FirebaseFirestore.instance
.collection("buses")
.get()
.then((querySnapshot) {
docs = querySnapshot.docs;
});
return docs;
}
Then use the FutureBuilder in the build() function as,
return Scaffold(
body: Center(
child: FutureBuilder<dynamic>(
future: getStuff(),
builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
if (snapshot.hasData) {
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (_, index) {
return Container(
child: ListTile(
title: Text(
snapshot.data[index].data()["name"].toString()),
subtitle: Text(
snapshot.data[index].data()["price"].toString()),
),
);
});
} else {
return CircularProgressIndicator();
}
},
),
),
);
I wrapped the FutureBuilder inside a Center just for clarity, you may remove that Center widget.

How to properly "refresh" Widgets inside FutureBuilder()

I have a Listview.builder() inside a FutureBuilder() that displays data fetched from API. I can retrieve the data successfully. But when I call the refreshData() function, previous data gets appended in the list.. How do I properly 'refresh' the widgets inside a FutureBuilder()?
Note: I'm only using get request here, so it's impossible that the data gets duplicated in the back-end. So the problem actually lies in displaying the data.
Here is my code:
class _MyHomePageState extends State<MyHomePage> {
List<Giver> _givers = [];
Future giversList;
getData() async {
_givers.addAll(await NetworkHelper().fetchGivers());
return _givers;
}
refreshData() {
giversList = getData();
}
#override
void initState() {
super.initState();
giversList = getData();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Column(
children: <Widget>[
RaisedButton(
onPressed: (){
setState(() {
refreshData();
});
},
child: Text('Refresh'),
),
FutureBuilder(
future: giversList,
builder: (context, snapShot){
switch(snapShot.connectionState) {
case ConnectionState.none:
return Center(child: Text('none'));
case ConnectionState.active:
case ConnectionState.waiting:
return Center(child: CircularProgressIndicator());
//this is where the listview is created
case ConnectionState.done:
return ListView.builder(
shrinkWrap: true,
itemCount: _givers.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(snapShot.data[index].name),
subtitle: Text(snapShot.data[index].address),
);
});
default:
return Center(child: Text('Default!'));
}
},
)
],
),
),
);
}
}
As #pskink mentioned in the comment above, I just replaced _givers.addAll(await NetworkHelper().fetchGivers()); with _givers = await NetworkHelper().fetchGivers();
Thanks for the help!