This is probably a beginner question, but I want to assign a class variable the await of a Future function. I only know that you can use async functions in onPressed on buttons, but how can I run these at Widget build or even in initialization?
Whenever you're trying to build a Widget that depends on a future, you may want to use FutureBuilder Widget.
The FutureBuilder has its own build method, to which you can provide an initial value for the result of the future if you like.
class MyWidget extends StatefulWidget {
#override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: name_Of_Your_Future
initialData: value, //initial value for the future(ie: until it resolves)
builder: (BuildContext context, AsyncSnapshot snapshot) {
//...some UI building logic here...
//snapshot.data contains the result of your future,
//once it is resolved
}
}
Related
Below is a code mock-up for generic StatefulWidget that uses a FutureBuilder. I get the following error when FutureBuilder instantiates:
type '(User) => Future<List<User>>' is not a subtype of type '(dynamic) => Future<List<dynamic>>'
I suspect the error is the compiler wouldn't know that T and U are the same, so U is declared as dynamic while T as User? How would I change this code so the type used for the generic in the StatefulWidget is passed to State widget?
Widget build(BuildContext context) => TestWidget<User>();
class TestWidget<T> extends StatefulWidget {
final Future<List<T>> Function(T) myFunc = (_) => Future<List<T>>(null);
#override
_TestState<T> createState() => _TestState<T>();
}
class _TestState<U> extends State<TestWidget> {
#override
Widget build(BuildContext context) {
return FutureBuilder<List<U>>(
future: widget.myFunc(null),
builder: (context, snapshot) {
return Container();
});
}
}
For those looking for a solution, you can see how FutureBuilder solved this issue. Instead of writing a return type of _TestState<T> for the createState() method, you would write State<TestWidget<T>>.
State<TestWidget<T>> createState() => _TestState <T>();
I ended up effectively creating a second build() function in the StatefulWidget class that uses the T generic. It gets called from the build() in the State class. That way the State class doesn't care about the generics. I would have switched to a StatelessWidget with managed states, but I need to use AutomaticKeepAliveClientMixin, so had to stick with a StatefulWidget. My actual State class uses the mixin and has a bit more going on.
class TestWidget<T> extends StatefulWidget {
final Future<List<T>> Function(T) myFunc = (_) => Future<List<T>>(null);
Widget build(BuildContext context) {
return FutureBuilder<List<T>>(
future: myFunc(null),
builder: (context, snapshot) {
return Container();
});
}
#override
_TestState createState() => _TestState();
}
class _TestState extends State<TestWidget> {
#override
Widget build(BuildContext context) {
return widget.build(context);
}
}
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
I am building an app which uses an api and I am using the future builder to fetch the data but the problem is when the state changes it rebuilds and I want to prevent this from happen.
Thanks,
try using this :
class Example extends StatefulWidget {
#override
_ExampleState createState() => _ExampleState();
}
class _ExampleState extends State<Example> {
Future<response> future;
#override
void initState() {
future = _asyncmethodCall();
super.initState();
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: future,
builder: (context, snapshot) {
// create some layout here
},
);
}
Future<someResponse> _asyncmethodCall() async {
// async code here
}
}
similar question: How to deal with unwanted widget build?
I have written some simplified code where I want the condition of firestore data to be able to update a parent widget.
I get the error: setState() or markNeedsBuild() called during build.
How can I update the state of myVar from inside StreamBuilder? After updating myVar the StreamBuilder will not be accessed, instead only the text Waiting... should be displayed.
Edit: I updated myVar to be used as the document ID of Firestore. To clarify, I want to be able to update the document of the StreamBuilder from inside the StreamBuilder. In other words as the question says, update stateful widget from child StreamBuilder.
class MyWidget extends StatefulWidget {
MyWidget({Key key})
: super(key: key);
#override
_MyWidgetState createState() {
return _MyWidgetState();
}
}
class _MyWidgetState extends State<MyWidget> {
String myVar = "someID";
#override
Widget build(BuildContext context) {
if (myVar == null) {
// update myVar async, so that StreamBuilder will be re-rendered
asyncFunctUpdate(myVar); // edit: finally got it to work if doing setState from asyncFuncUpdate(myvar).then(()=>setState)
return Text("Waiting...");
}
else
return StreamBuilder<DocumentSnapshot>(
stream: Firestore.instance
.collection('myCollection')
.document(myVar)
.snapshots(),
builder: (context, orderSnapshot) {
if (!orderSnapshot.data.exists)
setState(() {
myVar = null;
});
else return Text(orderSnapshot);
});
}
}
Workaround:
In my case it was simpler to just return buttons from the StreamBuilder and setState as normally from onPressed functions.
Solution:
Instead of doing setState from an async function do it within the .then clause (see code above)
In general, you do not have to do what you are doing now. You can do return Text ("Waiting ..."); right away without calling setState. This is a better decision in your case. You respond to events coming from the stream and must return content according to the current state
return StreamBuilder<DocumentSnapshot>(
stream: Firestore.instance
.collection('myCollection')
.document('myDocument')
.snapshots(),
builder: (context, orderSnapshot) {
if (!orderSnapshot.data.exists)
return Text("Waiting...");
else return Text(orderSnapshot);
});
You can make the update async like putting it inside an anonymous async callback like
() async {
setState();
}();
OR
You can avoid having state in that particular use case like:
#override
Widget build(BuildContext context) {
return StreamBuilder<DocumentSnapshot>(
stream: Firestore.instance
.collection('myCollection')
.document('myDocument')
.snapshots(),
builder: (context, orderSnapshot) {
if (!orderSnapshot.data.exists) {
return Text("Waiting...");
} else {
return Text(orderSnapshot);
}
});
}
I have which cycles a heavy function X times. If I put this stream in a StreamBuilder, the Stream runs again and again forever, but I only need it to run once (do the X cycles) and the stop.
To solve this problem for future functions I used an AsyncMemoizer, but I cannot use it for stream functions.
How can I do it?
If you are sure, your widget should not be rebuilt, than try sth like this code below.
The _widget will be created once in initState, then the 'cached' widget will be returned in the build method.
class MyStreamWidget extends StatefulWidget {
#override
_MyStreamWidgetState createState() => _MyStreamWidgetState();
}
class _MyStreamWidgetState extends State<MyStreamWidget> {
StreamBuilder _widget;
// TODO your stream
var myStream;
#override
void initState() {
super.initState();
_widget = StreamBuilder(
stream: myStream,
builder: (context, snapshot) {
// TODO create widget
return Container();
})
}
#override
Widget build(BuildContext context) {
return _widget;
}
}
As RĂ©mi Rousselet suggested, the StreamBuilder should be used in a Widget Tree where the state is well managed. I was calling setState((){}) in the Stream which caused the UI to update every time, making the StreamBuilder rebuild so restarting the stream.