Reputation: 758
I'm using provider for state management for my app, and I'm running into a problem, provider doesn't rebuild by ListView where I want the results
Here is my feed.dart
class Feed extends StatefulWidget {
@override
_FeedState createState() => _FeedState();
}
class _FeedState extends State<Feed> {
@override
void initState() {
PostNotifier postNotifier =
Provider.of<PostNotifier>(context, listen: false);
getGlobalPosts(postNotifier);
super.initState();
}
@override
Widget build(BuildContext context) {
AuthNotifier authNotifier =
Provider.of<AuthNotifier>(context, listen: false);
PostNotifier notifier = Provider.of<PostNotifier>(context);
return Scaffold(
body: Padding(
padding: EdgeInsets.only(left: 10, right: 10, top: 80),
child: Column(
children: <Widget>[
Expanded(
child: (notifier.postList.isEmpty) ? Center(child: CircularProgressIndicator(),) :
ListView.builder(
shrinkWrap: true,
itemBuilder: (context, index) {
return PostTile(
userName: notifier.postList[index].userName,
userDp: notifier.postList[index].userDp,
imgSrc: notifier.postList[index].imageUrl,
);
},
physics: ScrollPhysics(),
itemCount: notifier.postList.length,
),
),
],
),
),
);
}
}
class PostTile extends StatelessWidget {
final String imgSrc;
final String userName;
final String userDp;
PostTile(
{@required this.userName, @required this.userDp, @required this.imgSrc});
@override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
Padding(
padding: EdgeInsets.symmetric(horizontal: 20),
child: Row(
children: <Widget>[
CircleAvatar(
backgroundImage: NetworkImage(
"https://cdn0.iconfinder.com/data/icons/users-android-l-lollipop-icon-pack/24/user-128.png")
),
FlatButton(
child: Text(userName),
),
Expanded(
child: Container(),
),
RaisedButton(
child: Text(
'Follow',
style: TextStyle(color: Colors.white),
),
color: Colors.blue,
onPressed: () {},
)
],
),
),
SizedBox(
height: 20,
),
Image.network(imgSrc),
SizedBox(
height: 20,
),
Padding(
padding: EdgeInsets.symmetric(horizontal: 20),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
LikeButton(),
LikeButton(
likeBuilder: (bool isLiked) {
return Icon(
Icons.bookmark,
color: isLiked ? Colors.deepPurpleAccent : Colors.grey,
size: 30,
);
},
)
],
),
)
],
);
}
}
and my getGlobalPosts function - I get my posts from firebase and the user info too
getGlobalPosts(PostNotifier postNotifier) async {
QuerySnapshot snapshot = await Firestore.instance.collection('Posts').getDocuments();
FirebaseUser firebaseUser = await FirebaseAuth
.instance.currentUser()
.catchError((e) => print(e));
List<Post> _postList = [];
snapshot.documents.forEach((document) async {
if (firebaseUser.email != document.data["email"]) {
Post post = Post.fromMap(document.data);
//TODO: Use this to get user
await post.user.get().then((value) {
post.userName = value.data['displayName'];
post.userDp = value.data['profilePicture'];
print(post.userDp);
}).whenComplete(() {
_postList.add(post);
// print(_postList[0].userName);
print('Success');
});
} else {
print('Failed');
}
});
postNotifier.postList = _postList;
}
PostNotifier -
class PostNotifier with ChangeNotifier {
List<Post> _postList = [];
Post _currentPost;
List<Post> get postList => _postList;
Post get currentPost => _currentPost;
set postList(List<Post> postList) {
_postList = postList;
notifyListeners();
}
set currentPost(Post post) {
_currentPost = post;
notifyListeners();
}
}
I'm receiving the data but my listview doesn't show up until I hot reload, Only CircularProgress indicator is shown
Upvotes: 0
Views: 736
Reputation: 5601
By reading the Provider documentation
A typical situation where this happens is when starting an http request, where the future is stored inside the notifier:
initState() {
super.initState();
context.read<MyNotifier>().fetchSomething();
}
This is not allowed, because the modification is immediate.
Which means that some widgets may build before the mutation, while other widgets will build after the mutation. This could cause inconsistencies in your UI and is therefore not allowed.
Perhaps the Future completes a bit before the build method is called, so the recomendation (not the best practice, but it works) is to use a microtask to complete the future at the end of the frame
Future.microtask(() => getGlobalPosts(postNotifier););
UPDATE
Try using Future.forEach instead of only forEach, using Iterable.forEach doesn't guarantee that it awaits until the forEach inner actions end (inside the forEach you use async/await
to perform a future but outside the forEach method doesn't know it is a future and you cannot use await snapshot.documents.forEach(...)
because the method is of type void)
getGlobalPosts(PostNotifier postNotifier) async {
QuerySnapshot snapshot = await Firestore.instance.collection('Posts').getDocuments();
FirebaseUser firebaseUser = await FirebaseAuth
.instance.currentUser()
.catchError((e) => print(e));
List<Post> _postList = [];
//now you can await to the forEach to end before moving on to the next line
await Future.forEach(snapshot.documents, (document) async {
if (firebaseUser.email != document.data["email"]) {
Post post = Post.fromMap(document.data);
var user = await post.user.get();
post.userName = user .data['displayName'];
post.userDp = user .data['profilePicture'];
print(post.userDp);
_postList.add(post);
print('Success');
} else print('Failed')
});
//all of the iterations of the forEach should have ended by now and _postList should have all the posts added
postNotifier.postList = _postList;
}
Upvotes: 2
Reputation: 34180
Remove below code from initState()
, as it reloading in build widgets tree also, It seems multiple declaration issue.
PostNotifier postNotifier =
Provider.of<PostNotifier>(context, listen: false);
Upvotes: 0