Guido Lippi
Guido Lippi

Reputation: 33

How to combine Riverpod StreamProvider in Flutter?

I'm stuck in trying to combine two Riverpod StreamProvider. The goal is to perform a query to Firestore for fetching data, based on other data coming from Firestore, like this:

  1. Fetch user data from firestore document
  2. Use that data to perform another query

This is my database provider to share FirestoreDatabase class instance:

final databaseProvider = Provider<FirestoreDatabase>((ref) {
  final auth = ref.watch(authStateChangesProvider);

  if (auth.data?.value?.uid != null) {
    return FirestoreDatabase(uid: auth.data!.value!.uid);
  }
  throw UnimplementedError();
});

Then I've created a StreamProvider to fetch user's data

final userProvider = StreamProvider<UserModel>((ref) {
  final database = ref.watch(databaseProvider);
  return database.getUser();
});

Now I need to create the last provider, but I can't make it works. This is what I write since now:

final nodesProvider = StreamProvider<List<Node>>((ref) {
  final database = ref.watch(databaseProvider);
  final user = ref.watch(userProvider);

  //... some code to await user data fetching

  // user.nodes contains the list I need to pass in order to perform the query
  return database.getNodes(user.nodes);
});

Someone could help me or give me a hint? Thanks in advance

Upvotes: 2

Views: 3150

Answers (1)

Nolence
Nolence

Reputation: 2274

From personal experience, I recommend to stay away from StreamProviders unless it's only one level deep (no streams that rely on other stream).

Instead, Riverpod has a .last provider that exposes the last value in a stream. Calling ref.watch will let you listen to it and any updates.

final databaseProvider = FutureProvider<FirestoreDatabase>((ref) async {
  final auth = await ref.watch(authStateChangesProvider.last);

  if (auth.data?.value?.uid != null) {
    return FirestoreDatabase(uid: auth.data!.value!.uid);
  }
  throw StateError('auth uid is null so no database could be created');
});

final nodesProvider = FutureProvider<List<Node>>((ref) async {
  // you can Future.wait these if you want
  final database = await ref.watch(databaseProvider);
  final user = await ref.watch(userProvider.last);

  return database.getNodes(user.nodes);
});

I think that would work although I might have got the syntax wrong a bit (writing this in SO text area). On the other hand, there are ways to listen to streams in streams but I've never had luck with it. Here's what I think that would look like.

final databaseProvider = StreamProvider<FirestoreDatabase?>((ref) async* {
  // Or any loading value really... Idk if I'd actually do this
  yield null;

  for await (final authState in ref.watch(authStateChangesProvider.stream)) {
    if (auth.data?.value?.uid != null) {
      yield FirestoreDatabase(uid: auth.data!.value!.uid);
    }
  }
});

final nodesProvider = StreamProvider<List<Node>?>((ref) async* {
  // [Edit] You could probably use combineStream from rxDart here
  for await (final database in await ref.watch(databaseProvider.stream)) {
    if (database != null) {
      for await (final user in ref.watch(userProvider.stream)) {
        yield database.getNodes(user.nodes);
      }
    }
  }
});

Upvotes: 2

Related Questions