0

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

Tushar Patil
  • 748
  • 4
  • 13
  • Can you please add your main and other widgets code that used `Feed` widget to the question? – Tanuj Jun 27 '20 at 17:15

2 Answers2

2

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;
}
EdwynZN
  • 4,895
  • 2
  • 12
  • 15
  • I tried Future.microtask(() => getGlobalPosts(postNotifier)); but it is not working – Tushar Patil Jun 28 '20 at 07:51
  • Perhaps the problem is that the forEach is async but you should use the method Future.forEach so the loop ends before calling postNotifier.postList = _postList; (which is empty at that moment because the futures inside the forEach haven't ended yet) and when doing hot reload the futures completed already and postNotifier.postList has the reference of _postList and now it's not empty, but that's just a theory – EdwynZN Jun 29 '20 at 06:27
  • Yes postNotifier.postList = _postList is called before I finish copying email and display name, how can I get it right? I didn't understanf Future.forEach – Tushar Patil Jun 29 '20 at 09:12
  • Future.forEach receives a iterable as first parameter and then it performs a future action in all of them (just as regular forEach) in the second parameter, try the updated code to see if that fixes the problem – EdwynZN Jun 29 '20 at 15:25
0

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);
Jitesh Mohite
  • 31,138
  • 12
  • 157
  • 147