I am debugging a piece of code that creates a user in Firebase, and then waits for a document to be created in Firestore by our backend systems. As I scale the process to create more users, it times out and I discovered it is due to this code:
final cred = await FirebaseAuth.instance.signInAnonymously();
final user = cred.user!;
Future.delayed(const Duration(seconds: 15);
final doc = await FirebaseFirestore.instance.doc(user.uid).getDoc();
The 15 second time-out in here is to ensure the document has been created before we try to read it. This may take some time, but usually is much faster than the 15 second limit we use here.
Is there a way to wait for the document without using such a hard-coded time-out?
Hard-coded delays such as this lead to all kinds of unnecessary delays as you try to scale out your code base.
A better approach is to use Firestore’s built-in realtime listeners, which allow you wait exactly until the document has been created – and no longer.
In its simplest form, a realtime listener looks like this:
final uid = FirebaseAuth.instance.currentUser!.uid;
final ref = FirebaseFirestore.instance.doc('users/${uid}')
ref.snapshots().listen((snapshot) {
if (snapshot.exists) {
debugPrint(snapshot.data());
}
});
This prints the data as soon as the document is created.
To wire this up in a UI, you can use a StreamBuilder
like this:
StreamBuilder<DocumentSnapshot>(
stream: ref.snapshots(),
builder: (_, snapshot) {
if (snapshot.hasError) return Text('Error listening document: ${snapshot.error}');
if (!snapshot.hasData) return Text('No user doc');
final doc = snapshot.data!;
return Text('Current doc: ${doc.data()}');
}
)
In the use-case here, we want to use await
to wait for the document to appear. This means that we need to convert from the snapshots()
Stream
into a Future
, which we can do with this helper function:
Future<DocumentSnapshot> waitForDocument(DocumentReference ref) {
final completer = new Completer<DocumentSnapshot>();
StreamSubscription? sub;
try {
sub = ref.snapshots().listen((snapshot) {
if (snapshot.exists) {
completer.complete(snapshot);
sub!.cancel();
}
});
}
catch (e) {
completer.completeError(e);
}
return completer.future;
}
In here:
- The
Completer
is an object that allows you to build your ownFuture
. - We need to cancel the listener once the document has been created, so we track the subscription (in
sub
).
Now with this, we can wait for our document to appear without a hard-coded delay:
final cred = await FirebaseAuth.instance.signInAnonymously();
final user = cred.user!;
final doc = await waitForDocument(FirebaseFirestore.instance.doc(user.uid));
This is missing the 15s timeout that the original code had. If you want to add that, you’ll need to implement a timeout.
You can find a working copy of this code and a (much shorter) explanation on https://zapp.run/edit/firestore-wait-for-doc-creation-zx5k06w7x5l0.