Riverpod: Is there a correct way to read a StreamProvider within another StreamProvider?

Alex Hartford picture Alex Hartford · Jul 14, 2020 · Viewed 7.8k times · Source

I have been attempting to create streams to Firestore documents using the uid obtained from my auth Provider:

class AuthService {
  ...

  static final provider = StreamProvider.autoDispose((ref) => FirebaseAuth.instance.onAuthStateChanged);
  ...
}

However, I am struggling to actually create a StreamProvider dependent on the value from the auth Provider.

class User {
  ...
  static final provider = StreamProvider((ref) {
    final stream = ref.read(AuthService.provider);
    // Returns AsyncValue<Stream<User>> instead of desired AsyncValue<User>
    return stream.map((auth) => Service.user.stream(auth.uid));
  });
  ...
}

I also tried using Computed to return the uid or the stream itself but you cannot read a Computed from a Provider (which makes sense in retrospect).

This question is the most relevant on this topic but it is dealing with Provider, not Riverpod.

P.S. Can a Riverpod tag be created?

Edit:

The answer isn't working quite right. The await for loop is only ever triggering once, whereas a listener catches all events.

static final provider = StreamProvider((ref) async* {
  final stream = ref.read(AuthService.provider);
  print('userProvider init');

  stream.listen((auth) {
    print('LISTENED: ${auth?.uid}');
  });

  await for (final auth in stream) {
    print('uid: ${auth?.uid}');
    yield* Service.user.stream(auth?.uid);
  }
});

This code yields the following on login:

userProvider init
LISTENED: <redacted UID>
uid: <redacted UID>

And then on logout:

LISTENED: null

Where I would expect to see uid: null as well, which would update the stream, but upon any more auth events, only the listener is triggered and no events are caught by the await for loop.

Interestingly, using the flutter inspector, the value emitted by the auth provider never changes, either:

AutoDisposeStreamProvider<FirebaseUser>#95f11: AsyncValue<FirebaseUser>.data(value: FirebaseUser(Instance of 'PlatformUser'))

persists through login/logout events, which could explain this behavior, but I am not sure what to do to fix it.

Any ideas? I have been stuck on this for a while and can't correct the issue.

Answer

R&#233;mi Rousselet picture Rémi Rousselet · Jul 14, 2020

The problem is, your provider doesn't create a Stream<User> but a Stream<Stream<User>>

As part of 0.6.0-dev, you can use ref.watch to easily combine streams:

class User {
  ...
  static final provider = StreamProvider((ref) {
    final auth = ref.watch(AuthService.provider);
    return Service.user.stream(auth.uid);
  });
  ...
}