Reputation: 75
In the profile page of my app, I want to save a future list of objects from a firebase collection to a variable (myRecipes), using an async/await function. Depending on the outcome list, I want to display different widgets (using ifHasRecipes()) - if the list turns out to be null or empty, I want to display a text widget, otherwise I want to display the objects in the list using a listview builder (FavoritesHomePage class).
class Profile extends StatefulWidget {
final FirebaseAuth _auth = FirebaseAuth.instance;
@override
_ProfileState createState() => _ProfileState();
}
class _ProfileState extends State<Profile> {
List<Recipe> myRecipes;
Future<List<Recipe>> getUserRecipes(UserData userData) async {
return myRecipes = await DatabaseService().findUserRecipes(userData.uid);
}
Widget ifHasRecipes() {
if (myRecipes != null && myRecipes != []) {
return FavoritesHomePage(
recipes: myRecipes, scrollDirection: Axis.vertical, title: 'Your recipes',);
} else {
return Text('You have no favorites yet');
}
}
@override
Widget build(BuildContext context) {
final user = Provider.of<User>(context);
return StreamBuilder<UserData>(
stream: DatabaseService(uid: user.uid).userData,
builder: (context, snapshot) {
if (snapshot.hasData) {
UserData userData = snapshot.data;
getUserRecipes(userData);
return Scaffold(
body: SafeArea(
child: Column(
children: <Widget>[
//widgets using userData
ifHasRecipes(),
],
),
),
);
} else {
return Scaffold(
body: Center(
child: SpinKitRipple(),),
);
}
});
}
}
How can I make this code synchronized? I want to run the getUserRecipes() and when it's done, return different widgets depending on the outcome.
If I do a hot reload, the code "works" as I want it to, but sometimes when I navigate to this profile page via my pageview widget, the async/await function returning the variable myRecipes isn't done before the ifHasRecipes() is built, and then myRecipes is null (even though it shouldn't be)... Hope this isn't too confusing, sorry.
Upvotes: 1
Views: 591
Reputation: 416
In this case you can use a FutureBuilder
, with this one you will have different states, just like the StreamBuilder
, and you can show different widgets depending on the state, until the Future is resolved and you have the data.
I've done a little bit of refactoring to your code to make it work with the FutureBuilder, also I've changed it to Stateless, in this case it will display a CircularProgressIndicator
until the Future is resolved, it will also handle errors and the lack of data.
class Profile extends StatelessWidget {
const Profile({Key key}) : super(key: key);
Future<List<Recipe>> getUserRecipes(UserData userData) async {
return await DatabaseService().findUserRecipes(userData.uid);
}
Widget ifHasRecipes(List<Recipe> myRecipes) {
if (myRecipes != null && myRecipes != []) {
return FavoritesHomePage(
recipes: myRecipes,
scrollDirection: Axis.vertical,
title: 'Your recipes',
);
} else {
return Text('You have no favorites yet');
}
}
@override
Widget build(BuildContext context) {
final user = Provider.of<User>(context);
return StreamBuilder<UserData>(
stream: DatabaseService(uid: user.uid).userData,
builder: (context, snapshot) {
if (snapshot.hasData) {
return Scaffold(
body: SafeArea(
child: FutureBuilder(
future: getUserRecipes(snapshot.data),
builder: (context, futureSnapshot) {
if (futureSnapshot.hasError)
return Text('Error: ${futureSnapshot.error}');
switch (futureSnapshot.connectionState) {
case ConnectionState.none:
return Center(child: CircularProgressIndicator());
case ConnectionState.waiting:
return Center(child: CircularProgressIndicator());
case ConnectionState.active:
return Center(child: CircularProgressIndicator());
case ConnectionState.done:{
if (futureSnapshot.hasData) {
List<Recipe> myRecipes = futureSnapshot.data;
return Column(
children: <Widget>[
//widgets using userData
ifHasRecipes(myRecipes),
],
);
}
return Text('There\'s no available data.');
}
}
return null;
},
),
),
);
} else {
return Scaffold(
body: Center(
child: SpinKitRipple(),
),
);
}
},
);
}
}
Upvotes: 1
Reputation: 3842
If I understand the code correctly, the solution is to rebuild the widget when the future is resolved by adding setState((){});
to the getUserRecipes()
method :
Future<void> getUserRecipes(UserData userData) async {
myRecipes = await DatabaseService().findUserRecipes(userData.uid);
setState((){});
}
(You don't have to return the value if you're assigning it to the state, but rather access it directly.)
By the way, you can use the ternary operator (or just regular conditions) to do conditional UI. Put this instead of ifHasRecipes(),
:
(myRecipes != null && myRecipes != []) ?
FavoritesHomePage(
recipes: myRecipes, scrollDirection: Axis.vertical, title: 'Your recipes',)
: Text('You have no favorites yet')
If you get an error with this, increase your minimum SDK version to 2.6.0 in the pubspec.yaml
Upvotes: 0