How to implement FutureBuilder with already obtained future - flutter

I'm a little confused about how to implement Flutter's FutureBuilder according to the docs. I have a Future Builder here which work, with the only problem being that it every time the build method is run so to is the future being used:
FutureBuilder(
future: DBProvider.db.getUser(),
builder: (_, userData) {
switch (userData.connectionState) {
case ConnectionState.none:
return Container();
case ConnectionState.waiting:
return Container();
case ConnectionState.active:
case ConnectionState.done:
newUser = userData.data;
return ListView(
shrinkWrap: true,
children: <Widget>[
... // Lot's of things
],
);
}
return null;
},
),
I then realize the importance of this part of the docs where it says the future must be "obtained earlier" so this problem doesn't happen:
The future must have been obtained earlier, e.g. during
State.initState, State.didUpdateConfig, or
State.didChangeDependencies. It must not be created during the
State.build or StatelessWidget.build method call when constructing the
FutureBuilder. If the future is created at the same time as the
FutureBuilder, then every time the FutureBuilder's parent is rebuilt,
the asynchronous task will be restarted.
Here's where my question comes from, I understand what those words are saying, but in terms of writing the code according to that what exactly does mean / look like?
Do I simply make a function and call it in initState like this:
#override
void initState() {
super.initState();
_getUser();
}
_getUser() async {
newUser = await DBProvider.db.getUser();
}
In this case what goes in the future argument of the FutureBuilder? Or this completely not right?

Yes, you are on the right track. The easiest way to deal with this is to store a Future variable in your state. In initState make the API call and store the Future. You can then pass this Future to FutureBuilder.
Something like this:
class MyWidget extends StatefulWidget {
State createState() {
return MyState();
}
}
class MyState extends State<MyWidget> {
Future userFuture;
void initState() {
super.initState();
// Note here we are not awaiting the user, but rather storing
// the future in the variable
userFuture = DBProvider.db.getUser();
}
Widget build(BuildContext context) {
return FutureBuilder(
future: userFuture,
builder: (BuildContext, snapshot) {
...
}
);
}
}

Related

Flutter - FutureBuilder fires twice on hot reload

In my flutter project when I start the project in the simulator everything works fine and the future builder only fires once, but when I do hot reload the FutureBuilder fires twice which causes an error any idea how to fix this?
Future frameFuture() async {
var future1 = await AuthService.getUserDataFromFirestore();
var future2 = await GeoService.getPosition();
return [future1, future2];
}
#override
void initState() {
user = FirebaseAuth.instance.currentUser!;
super.initState();
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: frameFuture(),
builder: (context, snap) {
if (snap.connectionState == ConnectionState.done && snap.hasData) return HomePage();
else return Container(
color: Colors.black,
child: Center(
child: spinKit,
),
);
}
);
}
I solved the issue. I put the Future function in the initState and then used the variable in the FutureBuilder. I'm not sure why it works this way, but here's the code:
var futures;
Future frameFuture() async {
var future1 = await AuthService.getUserDataFromFirestore();
var future2 = await GeoService.getPosition();
return [future1, future2];
}
#override
void initState() {
user = FirebaseAuth.instance.currentUser!;
super.initState();
futures = frameFuture();
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: futures,
builder: (context, snap) {
if (snap.connectionState == ConnectionState.done && snap.hasData) return HomePage();
else return Container(
color: Colors.black,
child: Center(
child: spinKit,
),
);
}
);
}
The solution as you already figured out is to move the future loading process to the initState of a StatefulWidget, but I'll explain the why it happens:
You were calling your future inside your build method like this:
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: frameFuture(),
The issue is that Flutter calls the build method each time it renders the Widget, whenever a dependency changes(InheritedWidget, setState) or Flutter decides to rebuild it. So each time you redraw your UI frameFuture() gets called, this makes your build method to have side effects (this async call) which it should not, and is encouraged for widgets not to have side effects.
By moving the async computation to the initState you're only calling it once and then accessing the cached variable futures from your state.
As a plus here is an excerpt of the docs of the FutureBuilder class
"The future must have been obtained earlier, e.g. during State.initState, State.didUpdateWidget, or State.didChangeDependencies. It must not be created during the State.build or StatelessWidget.build method call when constructing the FutureBuilder. If the future is created at the same time as the FutureBuilder, then every time the FutureBuilder's parent is rebuilt, the asynchronous task will be restarted."
Hope this makes clear the Why of the solution.
This can happen even when the Future is called from initState. The prior solution I was using felt ugly.
The cleanest solution is to use AsyncMemoizer which effectively just checks if a function is run before
import 'package:async/async.dart';
class SampleWid extends StatefulWidget {
const SampleWid({Key? key}) : super(key: key);
final AsyncMemoizer asyncResults = AsyncMemoizer();
#override
_SampleWidState createState() => _SampleWidState();
}
class _SampleWidState extends State<SampleWid> {
#override
void initState() {
super.initState();
_getData();
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: widget.asyncResults.future,
builder: (context, snapshot) {
if (!snapshot.hasData) return yourLoadingAnimation();
// ... Do things with the data!
});
}
// The async and await here aren't necessary.
_getData() async () {
await widget.asyncResults.runOnce(() => yourApiCall());
}
}
Surprisingly, there's no .reset() method. It seems like the best way to forcibly rerun it is to override it with a new AsyncMemoizer(). You could do that easily like this
_getData() async ({bool reload = false}) {
if (reload) widget.asyncResults = AsyncMemoizer();
await widget.asyncResults.runOnce(() => yourApiCall());
}

Flutter: How to fetch data from api only once while using FutureBuilder?

How can I fetch data only once while using FutureBuilder to show a loading indicator while fetching?
The problem is that every time the user opens the screen it will re-fetch the data even if I set the future in initState().
I want to fetch the data only the first time the user opens the screen then I will use the saved fetched data.
should I just use a stateful widget with a loading variable and set it in setState()?
I'm using Provider package
Future<void> fetchData() async {
try {
final response =
await http.get(url, headers: {'Authorization': 'Bearer $_token'});......
and my screen widget:
class _MyScreenState extends State<MyScreen> {
Future<void> fetchData;
#override
void initState() {
fetchData =
Provider.of<Data>(context, listen: false).fetchData();
super.initState();
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: fetchData,
builder: (ctx, snapshot) =>
snapshot.connectionState == ConnectionState.done
? Consumer<Data>(
builder: (context, data, child) => Text(data.fetchedData)): Center(
child: CircularProgressIndicator(),
),
);
}
}
If you want to fetch the data only once even if the widget rebuilds, you would have to make a model for that. Here is how you can make one:
class MyModel{
String value;
Future<String> fetchData() async {
if(value==null){
try {
final response =
await http.get(url, headers: {'Authorization': 'Bearer $_token'});......
value=(YourReturnedString)
}
}
return value;
}
}
Don't forget to place MyModel as a Provider. In your FutureBuilder:
#override
Widget build(context) {
final myModel=Provider.of<MyModel>(context)
return FutureBuilder<String>(
future: myModel.fetchData(),
builder: (context, snapshot) {
// ...
}
);
}
A simple approach is by introducing a StatefulWidget where we stash our Future in a variable. Now every rebuild will make reference to the same Future instance:
class MyWidget extends StatefulWidget {
#override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
Future<String> _future;
#override
void initState() {
_future = callAsyncFetch();
super.initState();
}
#override
Widget build(context) {
return FutureBuilder<String>(
future: _future,
builder: (context, snapshot) {
// ...
}
);
}
}
Or you can simply use a FutureProvider instead of the StatefulWidget above:
class MyWidget extends StatelessWidget {
// Future<String> callAsyncFetch() => Future.delayed(Duration(seconds: 2), () => "hi");
#override
Widget build(BuildContext context) {
// print('building widget');
return FutureProvider<String>(
create: (_) {
// print('calling future');
return callAsyncFetch();
},
child: Consumer<String>(
builder: (_, value, __) => Text(value ?? 'Loading...'),
),
);
}
}
You can implement provider and pass data among its child.
Refer this example for fetching the data once and using it throughout its child.
As Aashutosh Poudel suggested, you could use an external object to maintain your state,
FOR OTHERS COMING HERE!
To manage state for large applications, the stateful widgets management becomes a bit painful. Hence you have to use an external state object that is shall be your single source of truth.
State management in flutter is done by the following libraries | services:
i. Provider: Well, i have personally played with this a little bit, even did something with it. I could suggest this for beginners.
ii. GetX: That one library that can do everything, its a good one and is recommended for novice || noob.
iii. Redux: For anyone coming from the react and angular world to flutter, this is a very handy library. I personally love this library, plus when you give it additional plugins, you are just superman
iv. Bloc: Best for data that is in streams. in other words, best for reactive programming approach....
Anyways, that was a lot given your question. Hope i helped

State.initState() must be a void method without an `async` keyword

![State.initState() must be a void method without an async keyword.
how can i solve this probelms]1
#override
Future<void> initState() async {
// TODO: implement initState
super.initState();
_current_location();
BitmapDescriptor.fromAssetImage(
ImageConfiguration(devicePixelRatio: 2.5),
'assets/fff.png').then((onValue) {
pinLocationIcon = onValue;
});
//createCustomMarker(context);
// final Marker marker = Marker(icon: BitmapDescriptor.fromBytes(markerIcon));
DatabaseReference ref = FirebaseDatabase.instance.reference();
ref.child('users').once().then((DataSnapshot snapshot) {
Map<dynamic, dynamic> values = snapshot.value;
print(values.toString());
values.forEach((k, v) {
allMarkers.add(Marker(
markerId: MarkerId(k),
draggable: false,
icon: pinLocationIcon,
position: LatLng(v["latitude"], v["longitude"]),
infoWindow: InfoWindow(title: v["name"]),
onTap: () {
_onMarkerTapped(v["name"]);
},
),);
});
});
}
initState must be a method which takes no parameters and returns void. This is because it overrides the method of the same name in the superclass (either StatelessWidget or State<StatefulWidgetType>. As such, this limitation is a contract that is fixed and binding; you cannot change it.
Of course, this also means that initState cannot be marked as async. This is because any method marked as async will implicitly return a Future, but if the method returns anything, it cannot have a return type of void which breaks the override contract.
If you need to call an async method from within initState, you can do so simply by not awaiting it:
#override
void initState() {
super.initState();
doSomeAsyncStuff();
}
Future<void> doSomeAsyncStuff() async {
...
}
If, however, you need the data from the async method for your widget, you cannot simply wait for the Future to return before you build the widget. Flutter does not allow this, because there is no telling how long it will take for the Future to return, and stalling the widget building until then could potentially block your entire app.
Instead, you need to have your widget build normally and then have a way to notify your widget to update when the Future has returned. This is most easily done with a FutureBuilder:
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: doSomeAsyncStuff(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
// Future hasn't finished yet, return a placeholder
return Text('Loading');
}
return Text('Loading Complete: ${snapshot.data}');
}
);
}
(Notice how instead of calling the async method from initState, I am calling it from the FutureBuilder during the build process.)
EDIT: As pointed out, this approach only works in OP's situation where the awaited future will always eventually return a value. This is not always the case - sometimes the future doesn't return a value at all and is just a long-running process. Sometimes the future might return null instead of concrete data. And sometimes the future may result in an error instead of completing successfully. In any of these cases, snapshot.data will be null after the future completes, in which case snapshot.hasData will always be false.
In these situations, instead of depending on snapshot.hasData to wait for data to appear, you can use snapshot.connectionState to monitor the state of the future itself:
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: doSomeAsyncStuff(),
builder: (context, snapshot) {
if (snapshot.connectionState != ConnectionState.done) {
// Future hasn't finished yet, return a placeholder
return Text('Loading');
}
return Text('Loading Complete');
}
);
}

FutureBuilder runs twice

I have problems with FutureBuilder starting twice.
First it fetch the data correctly, returning my StartScreen, then after few seconds, the StartScreen rebuilds and I noticed that the FutureBuilder fires again.
Here is my code and it's pretty simple, so I wonder what may the problem be?!?
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
FirebaseUser user;
#override
void initState() {
// TODO: implement initState
super.initState();
getNewestlocation();
}
#override
void dispose() {
super.dispose();
}
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'APP',
theme: buildTheme(),
home: FutureBuilder<FirebaseUser>(
future: Provider.of<AuthService>(context).getUser(),
builder: (context, AsyncSnapshot<FirebaseUser> snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.error != null) {
print('error');
return Text(snapshot.error.toString());
}
user = snapshot.data;
print('user here $user');
return snapshot.hasData ? StartScreen(user) : LoginScreen();
} else {
return LoadingCircle();
}
},
),
);
}
}
Can anyone help me with this, please?
The future is firing again because you're creating it in the build method at the same time as the FutureBuilder.
From the FutureBuilder docs:
The future must have been obtained earlier, e.g. during State.initState, State.didUpdateConfig, or State.didChangeDependencies. It must not be created during the State.build or StatelessWidget.build method call when constructing the FutureBuilder. If the future is created at the same time as the FutureBuilder, then every time the FutureBuilder's parent is rebuilt, the asynchronous task will be restarted.
So to prevent it from firing you'd have to do something like this:
class _MyAppState extends State<MyApp> {
Future<String> _myString;
#override
void initState() {
super.initState();
_myString = _fetchString();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder(
future: _myString,
builder: (context, snapshot) {
// build page stuff...
},
),
);
}
}
Future<String> _fetchString() async {
print('running future function');
await Future.delayed(Duration(seconds: 3));
return 'potatoes';
}
Note, to access a provider in initState() you have to set listen to false, as detailed in this answer.
I think you have some things bad in your code, maybe that's not the problem but is good to correct that:
first: It is not recommendable to do that job in your main file, you should have something like a Splash page to handle that.
second: You should use blocs and not write your logic code on the same place at the view(UI)
If you're using android studio, try if running from the terminal fix the issue. The run button attached the debug service, which then force the entire app to be rebuilt

Infinite loop on using FutureBuilder with API call

I am trying to populate my ListView with the result from an API. The API call must take place after the values have been retrieved from Shared Preference. However on execution my function for API call runs an infinite loop and the UI doesn't render. I tracked this behaviour through debug statements.
The circular indicator that should be shown when Future builder is building UI is also not showing.
How can I resolve this?
My code:
class _MyHomePageState extends State<MyHomePage>{
#override MyHomePage get widget => super.widget;
String userID = "";
String authID = "";
//Retrieving values from Shared Preferences
Future<List<String>> loadData() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
List<String> l= new List<String>();
if(prefs.getString("ID") == null){
l.add("null");
}
else{
l.add(prefs.getString("ID"));
}
if(prefs.getString("authID") == null){
l.add("null");
}
else{
l.add(prefs.getString("authID"));
}
return l;
}
//Setting values retrieved from Shared Pref
setData() async{
await loadData().then((value) {
setState(() {
userID = value[0];
print('the user ID is' + userID);
authID = value[1];
print('the authID is' + authID);
});
// getAllTasks(userID, authID);
});
print("Set data execution completed ");
}
//FUNCTION to use values from Shared Pref and make API Call
Future<List<Task>> getAllTasks() async{
await setData();
//Waiting for Set Data to complete
print('Ive have retrived the values ' + userID + authID );
List<Task> taskList;
await getTasks(userID, authID, "for_me").then((value){
final json = value;
if(json!="Error"){
Tasks tasks = tasksFromJson(json); //of Class Tasks
taskList = tasks.tasks; //getting the list of tasks from class
}
});
if(taskList != null) return taskList;
else {
print('Tasklist was null ');
throw new Exception('Failed to load data ');
}
}
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context){
_signedOut(){
widget.onSignedOut();
}
//To CREATE LIST VIEW
Widget createTasksListView(BuildContext context, AsyncSnapshot snapshot) {
var values = snapshot.data;
return ListView.builder(
itemCount: values == null ? 0 : values.length,
itemBuilder: (BuildContext context, int index) {
return values.isNotEmpty ? Ink(....
) : CircularProgressIndicator();
},
);
}
//MY COLUMN VIEW
Column cardsView = Column(
children: <Widget>[
....
Expanded(
child: FutureBuilder(
future: getAllTasks(),
initialData: [],
builder: (context, snapshot) {
return createTasksListView(context, snapshot);
}),
),
],
);
return Scaffold(
body: cardsView,
);
}
}
Instead of being called once... my setData function is being called repeatedly.. How can I resolve this..please help
You're creating Future object on every rebuild of the widget. And since you're calling setState inside your setData method, it triggers a rebuild recursively.
To solve this problem you have to keep a reference to the Future object. And use that reference for the FutureBuilder then it can understand that it is the previously used one.
E.g:
Future<List<Task>> _tasks;
#override
void initState() {
_tasks = getAllTasks();
super.initState();
}
And in your widget tree use it like that:
Expanded(
child: FutureBuilder(
future: _tasks,
initialData: [],
builder: (context, snapshot) {
return createTasksListView(context, snapshot);
}),
),
The FutureBuilder widget that Flutter provides us to create widgets based on the state of some future, keeps re-firing that future every time a rebuild happens!
Every time we call setState, the FutureBuilder goes through its whole life-cycle again!
One option is Memoization:
Memoization is, in simple terms, caching the return value of a function, and reusing it when that function is called again.
Memoization is mostly used in functional languages, where functions are deterministic (they always return the same output for the same inputs), but we can use simple memoization for our problem here, to make sure the FutureBuilder always receives the same future instance.
To do that, we will use Dart’s AsyncMemoizer.
This memoizer does exactly what we want! It takes an asynchronous function, calls it the first time it is called, and caches its result. For all subsequent calls to the function, the memoizer returns the same previously calculated future.
Thus, to solve our problem, we start by creating an instance of AsyncMemoizer in our widget:
final AsyncMemoizer _memoizer = AsyncMemoizer();
Note: you shouldn’t instantiate the memoizer inside a StatelessWidget, because Flutter disposes of StatelessWidgets at every rebuild, which basically beats the purpose. You should instantiate it either in a StatefulWidget, or somewhere where it can persist.
Afterwards, we will modify our _fetchData function to use that memoizer:
_fetchData() {
return this._memoizer.runOnce(() async {
await Future.delayed(Duration(seconds: 2));
return 'REMOTE DATA';
});
}
Note: you must wrap inside runOnce() only the body, not the funciton call
Special thanks to AbdulRahman AlHamali.
You need to save the Future in the State because doing getAllTasks() is triggering the call on every build callback.
In the initState:
this.getAllTasksFuture = getAllTasks();
Then you would use this Future property in the FutureBuilder.