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?
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
FutureBuilder should be the right tool for the job, you have to add a Future to wait for though, for example:
and then wait for it like this:
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
: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 benull
(which happens while the data is being loaded).Now in your
build
method, handle thisnull
by rendering aCircularProgressIndicator
: