How do I prevent Flutter FutureBuilder from firing early? - flutter

I'm using the following FutureBuilder to handle fetching 'squad' info from a Firebase database, but the Future is saying it's done before I can process all the data form the database:
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: _getUserSquads(),
builder: (ctx, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(
child: CircularProgressIndicator(),
);
} else {
if (_userSquads == null) {...} else {
print(snapshot.connectionState);
return Text(_userSquads.length.toString());
}
}
},
);
... the following two functions are the functions I need to be completed before the FutureBuilder is done:
Future<void> _getUserSquads() async {
print('1');
final squadIdsResult = await _userSquadsRef.once();
print('2');
if (squadIdsResult.value == null) {
print('3');
return;
}
print('4');
_userSquadIds = squadIdsResult.value;
print('5');
final test = await _test();
print('6');
}
Future<void> _test() {
print('7');
_userSquadIds.forEach((key, value) async {
print('itter');
final result = await _squadsRef.child(key).once();
_userSquads.add(result.value);
print(result.value);
print(_userSquads);
});
print('8');
print('9');
}
The two print statements print(result.value) and print(_useraSquads) don't execute until after the Future's connection state is done:
I/flutter (29217): 2
I/flutter (29217): 4
I/flutter (29217): 5
I/flutter (29217): 7
I/flutter (29217): itter
I/flutter (29217): 8
I/flutter (29217): 9
I/flutter (29217): 6
I/flutter (29217): ConnectionState.done
I/flutter (29217): {squadName: SAFTS}
I/flutter (29217): [{squadName: SAFTS}]
It seems like the problem is in the _test() function, but I've tried a hundred different ways to write this, and I can't figure out how to make sure that the code is done fetching the data from the database in the forEach block before the Future is set to done.

Your _userSquadIds's foreach is creating issue. If you want to make it async the you can use Future.forEach.
Change following code.
_userSquadIds.forEach((key, value) async {
print('itter');
final result = await _squadsRef.child(key).once();
_userSquads.add(result.value);
print(result.value);
print(_userSquads);
});
With Following one.
await Future.forEach(_userSquadIds, (key,value) async {
print('itter');
final result = await _squadsRef.child(key).once();
_userSquads.add(result.value);
print(result.value);
print(_userSquads);
});

Related

how to perform a single execution of a future?

What I am trying to do is that by starting the app, make a request to a server and all information is saved in the database, for that I use a FutureBuilder that does the whole process, once finished it starts the application as normal.
The problem is that the application executes my future synchronization more than twice, causing errors with the insert to database.
the following code is a basic example of what i am trying to do and the result i am getting.
main.dart
void main() => runApp(MyMaterialApp());
class MyMaterialApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
print('Running MyMaterialApp');
return MaterialApp(
home: SplashLoad(),
);
}
}
class SplashLoad extends StatefulWidget {
#override
_SplashLoadState createState() => _SplashLoadState();
}
class _SplashLoadState extends State<SplashLoad> {
final apiSimulation = new ApiSimulation();
#override
Widget build(BuildContext context) {
print('SplashScreen');
return Scaffold(
body: Container(
child: Center(
child: FutureBuilder(
future: apiSimulation.sincronizacion(),
builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
if (snapshot.hasData) {
return Text('load finished data:${snapshot.data}');
}
return CircularProgressIndicator();
},
),
),
),
);
}
}
ApiSimulation
class ApiSimulation {
int number = 0;
Future<int> synchronization()async{
print('INIT SYNCHRONIZATION');
await Future.delayed(Duration(seconds: 2));
final data = await _getData();
return data;
}
Future<int> _getData() async{
number++;
print('running getData$number');
await Future.delayed(Duration(seconds: 2));
return number;
}
}
console result
I/flutter (32404): Running MyMaterialApp
I/flutter (32404): SplashScreen
I/flutter (32404): INIT SYNCHRONIZATION
I/flutter (32404): Running MyMaterialApp
I/flutter (32404): SplashScreen
I/flutter (32404): INIT SYNCHRONIZATION
I/flutter (32404): running getData1
I/flutter (32404): running getData2
Sometimes reaching 4 in the value of the number
This is what the document says
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.
class _SplashLoadState extends State<SplashLoad> {
late final Future simFuture;
#override
void initState() {
super.initState();
simFuture= ApiSimulation().sincronizacion(); //initiate your future here
}
Within your future builder use
FutureBuilder(
future: simFuture,
builder: (ctx,snap){..},
)

Flutter FutureBuilder doesn't update

I know there is already a similar question, but i couldn't really solve mine from its answers. So I have a FutureBuilder which won't update when the data is loaded and I cant figure out why. The way I understand it, after the 2 second delay the Widget should be rebuild ("Data Arrived" should be printed). Why is this not happening?
import 'package:flutter/material.dart';
class NewQRPage extends StatefulWidget {
#override
_NewQRPageState createState() => _NewQRPageState();
}
class _NewQRPageState extends State<NewQRPage> {
Future link;
#override
void initState() {
link = getLink();
super.initState();
}
getLink() async{
Future.delayed(Duration(milliseconds: 2000),(){
print("Data Returned");
return "hello";
});
}
#override
Widget build(BuildContext context) {
return Container(
child: FutureBuilder(
future: link,
builder: (context, snapshot) {
if(snapshot.hasData){
print("Data Arrived");
return Text(snapshot.data);
}
else if(snapshot.hasError){
print("Error");
return Text("Error");
}
else {
print("No Data");
return Text("No Data");
}
},
),
);
}
}
And the Console Output is:
Performing hot restart...
Syncing files to device LYA L29...
Restarted application in 1.044ms.
I/flutter (22206): onStartCalled
I/flutter (22206): No Data
I/flutter (22206): No Data
I/flutter (22206): No Data
I/flutter (22206): Data Returned
You shouldn't return "Hello" in your lamda i.e (){}
Try
Future<String> getLink() async{
await Future.delayed(Duration(milliseconds: 2000));
print("Data Returned");
return "hello";
}

StreamBuilder<FileSystemEntity>(dirty, state: _StreamBuilderBaseState<FileSystemEntity, AsyncSnapshot<FileSystemEntity>>#25d1b)

I have got an error while working with Stream builders.
In my code, I used if(snapshot.data != null) { list.add(snapshot.data); } but I m getting a msg that add was called on null. I even added print('') statement to check if snapshot.data is null, but it is working fine with print() too. So I m not at all understanding how to solve this error. Someone please respond. Thanks in advance.
Here is my code
class Wrapper extends StatefulWidget {
#override
_WrapperState createState() => _WrapperState();
}
class _WrapperState extends State<Wrapper> {
Stream<FileSystemEntity> mystream;
var _directory;
void getfilesdata() async
{
_directory = await getApplicationDocumentsDirectory();
await Directory('${_directory.path}/data').create(recursive: true);
mystream = Directory('${_directory.path}/data').list().map((data) => data);
await Directory('${_directory.path}/documents').create(recursive: true);
}
#override
void initState() {
super.initState();
getfilesdata();
}
#override
Widget build(BuildContext context) {
List<FileSystemEntity> alldata;
return StreamBuilder<FileSystemEntity>(
stream: mystream,
builder: (context, snapshot) {
if(!snapshot.hasData)
{
print('No data yet');
return Loading();
}
else
{
if(snapshot.data != null && !snapshot.hasError)
{
print(snapshot.data);
alldata.add(snapshot.data);
}
if(snapshot.connectionState==ConnectionState.done)
{
return HomeScreen(alldata: alldata);
}
else return Loading();
}
}
);
}
}
And this is the result:
Performing hot restart...
Syncing files to device ZUK Z2132...
Restarted application in 1,734ms.
I/flutter (11840): No data yet
I/flutter (11840): No data yet
I/flutter (11840): File: '/data/user/0/com.example.easyscan/app_flutter/data/data[0]'
════════ Exception caught by widgets library ═══════════════════════════════════════════════════════
The following NoSuchMethodError was thrown building StreamBuilder<FileSystemEntity>(dirty, state: _StreamBuilderBaseState<FileSystemEntity, AsyncSnapshot<FileSystemEntity>>#25d1b):
The method 'add' was called on null.
Receiver: null
Tried calling: add(Instance of '_File')
The relevant error-causing widget was:
StreamBuilder<FileSystemEntity> file:///home/praneeth/AndroidStudioProjects/easyscan/lib/wrapper.dart:40:12
When the exception was thrown, this was the stack:
#0 Object.noSuchMethod (dart:core-patch/object_patch.dart:53:5)
#1 _WrapperState.build.<anonymous closure> (package:easyscan/wrapper.dart:53:25)
#2 StreamBuilder.build (package:flutter/src/widgets/async.dart:509:81)
#3 _StreamBuilderBaseState.build (package:flutter/src/widgets/async.dart:127:48)
#4 StatefulElement.build (package:flutter/src/widgets/framework.dart:4619:28)
...
════════════════════════════════════════════════════════════════════════════════════════════════════
Your error is because allData is null so you cannot call the add() method.
You can declare your variable as follow:
List<FileSystemEntity> alldata = [];
By doing this allData will be initialized as an empty list and you will be able to call its methods.

How to handle navigation after Firebase google login using provider?

I'm creating a simple app where user can be authenticated using firebase google_sigin. I have created AuthChecker widget which checks the authentication and returns login page, where user can login with google. After when the login in complete I want to go back to AuthChecker and show homepage.
Following code below is my implementation which gives context error
From the main.dart file the AuthChecker widget is called:
class AuthChecker extends StatefulWidget {
#override
_AuthCheckerState createState() => _AuthCheckerState();
}
class _AuthCheckerState extends State<AuthChecker> {
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: getCurrentUser(),
builder: (context, snapshot){
if(snapshot.connectionState == ConnectionState.done){
//got response
FirebaseUser user = snapshot.data;
LoginStateProvider prov = Provider.of<LoginStateProvider>(context);
if(user == null){
//not loggedin pls login
return LoginPage();
}else{
//already logged in
print("Already logged in");
prov.updateUserState(user);
return Home();
}
}
},
);
}
}
The login page contains the signin button:
Widget signInButton(BuildContext context) {
return OutlineButton(
onPressed: () {
signInWithGoogle().whenComplete(() {
Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(
builder: (context)=>AuthChecker();
),
ModalRoute.withName('/auth_check')
);
},
}
The provider class looks like:
class LoginStateProvider with ChangeNotifier{
String uid;
String name;
String email;
String profilePhoto;
LoginStateProvider({
this.uid,
this.name,
this.email,
this.profilePhoto,
});
void updateUserState(FirebaseUser user){
this.uid = user.uid;
this.name = user.displayName;
this.email = user.email;
this.profilePhoto = user.photoUrl;
}
}
I got following error:
I/flutter ( 9551): * Ensure the Provider<LoginStateProvider> is an ancestor to this FutureBuilder<FirebaseUser>
I/flutter ( 9551): Widget
I/flutter ( 9551): * Provide types to Provider<LoginStateProvider>
I/flutter ( 9551): * Provide types to Consumer<LoginStateProvider>
I/flutter ( 9551): * Provide types to Provider.of<LoginStateProvider>()
I/flutter ( 9551): * Always use package imports. Ex: `import 'package:my_app/my_code.dart';
I/flutter ( 9551): * Ensure the correct `context` is being used.
I/flutter ( 9551):
I/flutter ( 9551): If none of these solutions work, please file a bug at:
I/flutter ( 9551): https://github.com/rrousselGit/provider/issues
you could try and do this:
Widget signInButton(BuildContext context) {
return OutlineButton(
onPressed: () {
signInWithGoogle().whenComplete(() {
FirebaseUser user = snapshot.data;
if (user == null) {
//Route to login
} else {
//route to somewhere
}
},
}
and create a splash screen with your logo and a async function that runs at initState that checks if user == null and route to login if needed or to homepage if already logged in.
there's a nice example here: Firebase Auth state check in Flutter
I'm not sure if it helps or if that was really your question, sorry if I misunderstood u

dismiss database data with listview

now i made the code like this
FutureBuilder(
future: getData2(),
builder: (context, snapshot) {
if (snapshot.hasData) {
List data = snapshot.data;
return ListView.builder(
shrinkWrap: true,
itemCount: data.length,
itemBuilder: (context, index) {
final item = data[index];
return Dismissible(
key: Key(item['loc3']),
onDismissed: (direction) {
setState(() async {
await openData2().then((value) {
value.delete(
'test2',
where: 'loc3 = ?',
whereArgs: ['loc3'],
);
});
});
},
child: ListTile(
title: Text(item['loc3']),
),
);
},
);
but when i dismiss one item
there's some note in console 'Another exception was thrown: setState() callback argument returned a Future.'
in setState i don't return any variable why they say like this?
and when i first delete one item there is more detail error
The following assertion was thrown while notifying listeners for AnimationController:
I/flutter ( 6018): setState() callback argument returned a Future.
I/flutter ( 6018): The setState() method on PositionedTilesState#6d110 was called with a closure or method that
I/flutter ( 6018): returned a Future. Maybe it is marked as "async".
I/flutter ( 6018): Instead of performing asynchronous work inside a call to setState(), first execute the work (without
I/flutter ( 6018): updating the widget state), and then synchronously update the state inside a call to setState().
I/flutter ( 6018): When the exception was thrown, this was the stack:
I think the 1 problem is that you have an async function inside setState. Change it to this:
...
onDismissed: (direction) async {
await openData2().then((value) {
value.delete(
'test2',
where: 'loc3 = ?',
whereArgs: ['loc3'],
);
});
setState(() {});
},
'''
or this (more readable):
...
onDismissed: (direction) {
Future<void> delete() async {
await openData2().then((value) {
value.delete(
'test2',
where: 'loc3 = ?',
whereArgs: ['loc3'],
);
});
}
setState(() {
delete();
});
},
'''
The point is, you can't have async operations performed inside setState.