SetState in Future-function - asynchronous

I like to have an autosync for my future function. I tried a setstate but it did not work properly. Do you have an idea? Happy about suggestions.
Future<AlgoliaQuerySnapshot> queryFunc() async{
AlgoliaQuery query = algolia.instance.index('groups').setAroundLatLng('51.5078845,7.4702625');
Future<AlgoliaQuerySnapshot> snap = query.getObjects();
return snap;}

This code is an example for how to build a widget that waits for your async code.
Widget mywidget = new FutureBuilder(
future: queryFunc(),
builder: (BuildContext context, AsyncSnapshot<AlgoliaQuerySnapshot> snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.active:
case ConnectionState.waiting:
return Text("not loaded yet");
case ConnectionState.done:
if (snapshot.hasError)
return Text('Error: ${snapshot.error}');
return Text(snapshot.data.foo); // success - build whatever UI elements you need
}
return null;
});
Also read the official docs.

Related

Why is print(snapshot.hasData) returning true?

I must be misunderstanding the hasData method for a QuerySnaphot. In my StreamBuilder I want to return a widget informing the user there are no items in the collection queried. I've deleted the collection in Firestore so there's definitely no data there. But when I run the following code:
StreamBuilder<QuerySnapshot>(
stream: Firestore.instance
.collection('Events')
.where("bandId", isEqualTo: identifier)
.snapshots(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (!snapshot.hasData) {
print('code here is being executed 1');// This gets executed
return Text('helllllp');
} else {
print('Code here is being executed2'); //And this gets executed
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return new Text('Loading...');
default:
return new ListView(
children:
snapshot.data.documents.map((DocumentSnapshot document) {
return CustomCard(
event: document['event'],
location: document['location'],
service: document['service'],
date: document['date'].toDate(),
);
}).toList(),
);
}
}
},
),
All I want to do is return a widget informing user if the snapshot is empty. For example Text('You have no messages')
The problem here is that snapshots() will also return a QuerySnapshot when the query returns no documents. Thus, you could expand your condition like this:
if (!snapshot.hasData || snapshot.data.documents.isEmpty) {
return Text('You have no messages.');
} else {
...
}
Although, realistically you should not return You have no messages when snapshot.data is null because it is null before the query is completed. Hence, I would go for something like this:
if (!snapshot.hasData) {
return Text('Loading...');
}
if (snapshot.data.documents.isEmpty) {
return Text('You have no messages.');
}
return ListView(..);
This ignores error handling, however, that can also be added.
Notice that snapshot.hasData is an alternative to determining connection state using snapshot.connectionState.

Flutter Nested Stream builder -- last stream is getting null before showing data

I am trying to make an app where I need a nested stream builder. The Stream builder looks something like this. but the widgets are built before the last stream is loaded so I get error calling getter null,
StreamBuilder(
stream: some_stream,
builder: (context, data){
return StreamBuilder(
stream: some_stream,
builder: (context, data){
return StreamBuilder(
stream: some_stream,
builder: (context, data){
return someWidget;
}
);
}
);
}
);
It is entirely possible that StreamBuilder.builder will be called while the Stream has no data, hasn't connected to the Stream yet or the value is null.
It is up to you to make sure that you handle all these cases.
To make sure that the initial value is never null, you can set inialData.
Future<String> someFutureString = Future.value('initial data seeded');
new StreamBuilder<String>(
initialData: await someFutureString,
builder: (ctx, snapshot) { /* ... */ }
);
This is bad practice though. It is better to build such builder that consider the state of the snapshot it works with. Building the widget tree should be quick. Imagine having to wait 3 seconds to the initialData. Your widget tree building will be blocked by at the first await.
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
wrapInMaterialApp(Widget widget) => MaterialApp(
home: widget,
);
main() {
testWidgets('can await initial data', (WidgetTester tester) async {
final initialData = Future<String>.value('initial value');
final stream = Stream.fromIterable(['first']);
final sb = StreamBuilder<String>(
initialData: await initialData,
builder: (ctx, snapshot) {
return Text('${snapshot.data}');
},
);
await tester.pumpWidget(wrapInMaterialApp(sb));
// Verify that initial data is present
expect(find.text('initial value'), findsOneWidget);
});
testWidgets('can return subtree if there is data', (WidgetTester tester) async {
final stream = Stream.fromIterable(['first']);
final sb = StreamBuilder<String>(
stream: stream,
builder: (ctx, snapshot) {
if (snapshot.hasData) {
return Text('${snapshot.data}');
} else
return Container();
},
);
var wrappedWidget = wrapInMaterialApp(sb);
await tester.pumpWidget(wrappedWidget);
expect(find.byType(Container), findsOneWidget);
expect(find.text('first'), findsNothing);
await tester.pump();
expect(find.byType(Container), findsNothing);
expect(find.text('first'), findsOneWidget);
});
}
Other things that can help you determine what Widget your builder should return is ConnectionState, accessible through snapshot.connectionState.
Cheers!
You should always approach the structure for the StreamBuilder like,
StreamBuilder(
stream: some_stream,
builder: (context, data){
return StreamBuilder(
stream: some_stream2,
builder: (context, data){
if(data.hasError) {
return Text("Error Occured!!");
} else if(data.hasData) {
return StreamBuilder(
stream: some_stream,
builder: (context, data){
if (data.hasError){
return Text("Error Occured!!");
} else if (data.hasData) {
return someWidget;
}else {
return CircularProgressIndicator();
}
}
);
} else {
return CircularProgressIndicator();
}
}
);
}
);
This will save you from errors for most of the time.

the builder is repeated many times when i want to print data from firebase

I have problem with flutter and firebase.
I want to print data from firebase in flutter interface which contains a form, the data printed successfully but when i want to write in the text-field the loading case don't let me, all the builder will be repeating many times and quickly.
It well print
loading after form loading after the form ..
And i can't write, so how to solve this problem please?
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: AppBar(
title: Text("Profile"),
elevation: 1.5,
),
body: StreamBuilder<DocumentSnapshot>(
stream: Firestore.instance
.collection('users')
.document(widget.user.uid)
.snapshots(),
builder:
(BuildContext context, AsyncSnapshot<DocumentSnapshot> snapshot) {
if (snapshot.hasError) {
return Text('Error: ${snapshot.error}');
}
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return Text('Loading.. ');
break;
default:
return checkRole(snapshot.data);
break;
}
},
));
}
Use FutureBuilder becuase you are trying to fecth a document,
FutureBuilder(future: Firestore.instance.collection('users').document(widget.user.uid).get(),
builder:(BuildContext context, AsyncSnapshot<DocumentSnapshot> snapshot){
if(snapshot.hasData){
return Text('${snapshot.data.data}');
}
return Text('Loading.. ');
});
i soleve this problem by adding snapshot.hasdata
builder:
(BuildContext context, AsyncSnapshot<DocumentSnapshot> snapshot) {
if (snapshot.connectionState == ConnectionState.done ||
snapshot.hasData) {
return checkRole(snapshot.data);
} else {
return new Center(child: new Text('loading'));
}

How to use nested FutureBuilders and make sure one future is called after another?

To avoid my FutureBuilder being called again and again (see this), I have the habbit of calling my futures in initState and it has always worked fine until I had to use nested ones.
Basically I want the second future to get called only after connectionState of 1st one becomes done. Any help?
Here is my code to explain further-
FutureBuilder(
future: _future1,
builder: (BuildContext context, AsyncSnapshot snapshot) {
return snapshot.connectionState != ConnectionState.done
? CircularProgressIndicator()
: snapshot.data != 200
? SomeWidget()
: FutureBuilder(
future: _future2,
builder: (BuildContext context, AsyncSnapshot snapshot) {
return snapshot?.connectionState == ConnectionState.done
? Text('')
: CircularProgressIndicator();
});
},
);
So basically, I don't want _future2 to get called at all if the data received from _future1 isn't 200. In other words, I want to decide whether _future2should be called or not on basis of result of _future1.
In initState, I do this -
_future1 = func.then(checkCode);
And then I wrote another function to decide my widget depending on the code I receive -
checkCode(int statusCode) {
if (statusCode == 200) {
_future2Func();
widgetToNavigate = Text('');
} else {
widgetToNavigate = SomeWidget();
}}
So finally, my build code looks like this -
Widget build(BuildContext context) {
return FutureBuilder(
future: _future1,
builder: (BuildContext context, AsyncSnapshot snapshot) {
return snapshot.connectionState != ConnectionState.done
? CircularProgressIndicator()
: widgetToNavigate;
},
);}

Flutter Future:A build function returned null

I want to retrieve data from firebase to future builder.I use this function for that.
Future getAllData() async {
ReseviorDataModel resModel;
DatabaseReference ref = FirebaseDatabase.instance.reference();
DataSnapshot childed =await
ref.child("Reservoir/${widget.placeName}").once();
Map<dynamic, dynamic> values;
values = childed.value;
resModel = ReseviorDataModel.customConstrcutor(values["sensor1"],
values["sensor2"], values["sensor3"]);
return resModel;
}
I invoke this function inside my future builder.
child: FutureBuilder(
future: getAllData(),
builder: (context, snapshot) {
Center(child: Text(snapshot.data));
}
but it keeps throwing this "A build function returned null." error .i cant figure out what is the problem here
To clear things up here, your builder always has to return a widget to display. You can never return null, as the error message describes.
The problem in this case was that the OP didn't return anything from the builder, so just adding a return worked out fine.
Some things to keep in mind when using FutureBuilder. Always check the snapshot.hasData property and return a UI accordingly. This will prevent cases where the snapshot.data is null causing a new error to be thrown in your Widget taking in the null.
Example:
child: FutureBuilder(
future: getAllData(),
builder: (context, snapshot) {
if(!snapshot.hasData) {
// show loading while waiting for real data
return CircularProgressIndicator();
}
return Center(child: Text(snapshot.data));
}

Resources