skip to Main Content

I am making a Flutter and Firebase app. I want to show data from the database, store it in a list and then display it on the screen.

This code is working well, but when my screen loads, for half of a second the screen is red and it says an error message, and then the normal screen shows with the right data that should be displayed. How can I make some kind of loading screen or wait that everything loads as it should?

enter image description here

I have a Late list of favorite genres late List favGenres;

On initialization, I am calling the Future async method to retrieve genres about this user from database:

  @override
  void initState() {
    super.initState();

    getFavoriteGenres().then((value) {
      print("value $value"); // value [techno, trash]
      setState(() {
        widget.favGenres = value;
      });
    });
  }

I get all genre names in a list by following the async method. User’s favorite genres are a reference to genre documents from other collections in Firestore.

Future<List<dynamic>> getFavoriteGenres() async {
List list = [];

final userDoc = await FirebaseFirestore.instance
    .collection('users')
    .doc(FirebaseAuth.instance.currentUser?.uid)
    .get(); 

final favoriteGenresRefs =
    List<DocumentReference>.from(
        userDoc.get('fav_genres') ?? []); 

for (var ref in favoriteGenresRefs) {
  var snapshot = await ref.get();
  if (snapshot.exists) {
    var genresData = snapshot.data();
    if (genresData != null) {
      var mapData = genresData as Map<String, dynamic>;
      var name = mapData['name'];
      list.add(name);
    }
  }
}
return list; // list of all genre names for logged user

}

And I have a list of details that I send to my custom widget which is some kind of Row widget with icon and text:

List<Map<String, dynamic>> userMoreDetailsList = [
  {"icon": Icons.info_outline1, "text": description},
  {"icon": Icons.info_outline2, "text": favSomething},
  {"icon": Icons.info_outline3, "text": widget.favGenres.join}, // I want to connect all genre names into 1 big string
  {"icon": Icons.info_outline4, "text": favSomething2}
];

My screen code:

return Scaffold(
  body: SafeArea(
    child: SingleChildScrollView(
      child: Column(
        children: [
          InfoCustom( // my custom component
              itemsList: userMoreDetailsList,
              textTitle: "More details about you:"),
        ],
      ),
    ),
  ),
);

}

I tried with StreamBuilder and FutureBuilder but it didn’t work because data isn’t directly in widgets, but first saved in a userMoreDetailsList list and then forwarded as a prop in my custom component.

2

Answers


  1. FutureBuilder should be the right tool for the job, you have to add a Future to wait for though, for example:

      late final Future<void> initialization;
    
      @override
      void initState() {
        super.initState();
        initialization = Future.microtask(() async {
          widget.favGenres = await getFavoriteGenres();
        });
      }
    

    and then wait for it like this:

    return Scaffold(
      body: SafeArea(
        child: FutureBuilder(
          future: initialization,
          builder: (context, snapshot) {
            if (snapshot.hasError) {
              return Center(child: Text("Error: ${snapshot.error}"));
            }
    
            if (snapshot.connectionState != ConnectionState.done) {
              return CircularProgressIndicator();
            }
    
            return SingleChildScrollView(
              child: Column(
                children: [
                  InfoCustom(
                      // my custom component
                      itemsList: userMoreDetailsList,
                      textTitle: "More details about you:"),
                ],
              ),
            );
          },
        ),
      ),
    );
    
    Login or Signup to reply.
  2. When you mark a field as late you’re making a promise that you will give that field a value before you ever read from it.

    And while you do give the field a value later, that value is loaded asynchronously and the build method of your widget tries to read the value before it is loaded from the database.

    This means you’re breaking the promise your code made, so you get an error indicating that.


    The simplest fix is to start off with an empty list instead of null:

    List favGenres = []
    

    Doing this means that your UI will show an empty list while the data is being loaded.


    Alternatively (and preferred in my book) is to show a "loading…" type UI while the data is being loaded.

    For this, remove the late marking from the field, and instead make it optional – correctly indicating that it can be null (which happens while the data is being loaded).

    List? favGenres;
    

    Now in your build method, handle this null by rendering a CircularProgressIndicator:

    favGenres == null
      ? CircularProgressIndicator()
      : Text(widget.favGenres.join)
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search