0

I am having trouble with this future builder and can not seem to find the solution. I'm trying to create a List of documents("posts) mapped to a custom class I created("BlogPosts") but when attempting to use it I get the following error.

error:

LateInitializationError: Field 'posts' has not been initialized.

the stack trace tells me the error is happening inside the builder but I have already initialized 'posts' in the future

future builder:

late User user;
late Userdata userdata;
CollectionReference usersRef = FirebaseFirestore.instance.collection('users');
CollectionReference postsRef = FirebaseFirestore.instance.collection('posts');
late List<BlogPost> posts;

FutureBuilder(
          future: Future.wait([
            usersRef.doc(user.uid).get().then((doc) {
            userdata = Userdata.fromDocument(doc);
            }),
            postsRef.get().then((val) {
              posts = val.docs.map((doc) => BlogPost.fromDocument(doc)).toList();
            })
    ]),
          builder: (context, snap) {
            print(posts);
            if(snap.connectionState == ConnectionState.done) {
              return ListView.builder(
                physics: BouncingScrollPhysics(),
                  itemCount: 3,
                  itemBuilder: (context, index) {
                  return Text(userdata.displayName);
                  }
              );
            } else {
              return Center(
                child: Text('Loading')
              );
            }
          }
        )

full code:

late User user;
late Userdata userdata;
CollectionReference usersRef = FirebaseFirestore.instance.collection('users');
CollectionReference postsRef = FirebaseFirestore.instance.collection('posts');

class HomeScreen extends StatefulWidget {
  const HomeScreen({Key? key}) : super(key: key);

  @override
  _HomeScreenState createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  final auth = FirebaseAuth.instance;
  late List<BlogPost> posts;

  @override
  Widget build(BuildContext context) {
    user = auth.currentUser!;
    Color getColor(Set<MaterialState> states) {
      const Set<MaterialState> interactiveStates = <MaterialState>{
        MaterialState.pressed,
        MaterialState.hovered,
        MaterialState.focused,
      };
      if (states.any(interactiveStates.contains)) {
        return Colors.blue.shade900;
      }
      return Colors.blue.shade600;
    }
    return Scaffold(
      floatingActionButton: FloatingActionButton(
        backgroundColor: Colors.blue,
        child: Icon(
          Icons.add,
          color: Colors.white,
        ),
        onPressed: (){
          Navigator.push(context, MaterialPageRoute(builder: (_) => Upload()));
        },
      ),
        appBar: AppBar(
          backgroundColor: Colors.grey[600],
          actions: [
            TextButton(
              child: Text(
                'Logout',
                style: TextStyle(color: Colors.black),
              ),
              onPressed: () {
                auth.signOut();
                Navigator.of(context).pushReplacement(MaterialPageRoute(builder: (context) => LoginScreen()));
              },
              style: ButtonStyle(
                  backgroundColor: MaterialStateProperty.resolveWith(getColor)
              ),
            ),
          ],
        ),
        // body: Center(
        //   child: Text(user.uid)
        // ),

        body: FutureBuilder(
          future: Future.wait([
            usersRef.doc(user.uid).get().then((doc) {
            userdata = Userdata.fromDocument(doc);
            }),
            postsRef.get().then((val) {
              posts = val.docs.map((doc) => BlogPost.fromDocument(doc)).toList();
            })
    ]),
          builder: (context, snap) {
            print(posts);
            if(snap.connectionState == ConnectionState.done) {
              return ListView.builder(
                physics: BouncingScrollPhysics(),
                  itemCount: 3,
                  itemBuilder: (context, index) {
                  return Text(userdata.displayName);
                  }
              );
            } else {
              return Center(
                child: Text('Loading')
              );
            }
          }
        )
    );
  }
}

class Userdata {

  final String uid;
  final String email;
  final String displayName;


  Userdata(this.uid, this.email,  this.displayName);

  factory Userdata.fromDocument(DocumentSnapshot doc) {
    return Userdata(
        doc['uid'],
        doc['email'],
        doc['displayName']
    );
  }
}

class BlogPost {
  String displayName;
  String postmsg;
  String postId;
  String UserId;

  BlogPost(this.displayName, this.postmsg, this.postId, this.UserId);

  factory BlogPost.fromDocument(DocumentSnapshot doc) {
    return BlogPost(
        doc['displayName'],
        doc['postmsg'],
        doc['postId'],
        doc['UserId']
    );
  }
RyanBuss01
  • 203
  • 1
  • 11

2 Answers2

0

I suspect the print statement here is causing the error:

builder: (context, snap) {
  print(posts);
  ...

Your builder gets invoked many times, and initially the Future will actually not yet have read the data from Firestore.

You'll want to only print when the connection is done:

builder: (context, snap) {
  if(snap.connectionState == ConnectionState.done) {
    print(posts);
    ...

But the way you're dealing with the Future also seems quite off. Just putting Future.wait() around the code doesn't suddenly make all asynchronous calls in there work.

You will need to pass an array of Futures to Future.wait(), and then use the results of those futures from the snap AsyncSnapshot in your builder body:

FutureBuilder(
  future: Future.wait([
    usersRef.doc(user.uid).get().then((doc) => Userdata.fromDocument(doc)),
    postsRef.get().then((val) => val.docs.map((doc) => BlogPost.fromDocument(doc)).toList()))
  ]),
  builder: (context, snap) {
    if(snap.connectionState == ConnectionState.done) {
      if (snap.data[1] != null) print(snap.data[1]);
      ...

So here the future property of the builder gets two futures, and snap holds the two results from the database. When snap.connectionState is done, you can access snap.data[0] and snap.data[1] to get the results.

Also see:

Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
  • I moved it inside and I'm still getting the same error – RyanBuss01 Jun 25 '21 at 14:35
  • snap.data[0] gives me an error: "The method '[]' can't be unconditionally invoked because the receiver can be 'null'" when I add a null check or a "?" I am told the operator isn't defined – RyanBuss01 Jun 25 '21 at 15:51
  • Hmm... you might want to print `snap.data` in that case to see what its type and value is. – Frank van Puffelen Jun 25 '21 at 15:59
  • It's just returning null. I made sure that it was connecting to firebase with a test document and it worked fine so I'm not sure why this code isn't getting the posts data – RyanBuss01 Jun 29 '21 at 00:19
0

I figured out my problem, as out my code was fine the entire time I just had a typo in my fromDocument my userId string was doc['UserId'] and was uploaded as 'userId' just one misplaced Uppercase.

Thanks so much for the help

RyanBuss01
  • 203
  • 1
  • 11