Why is RiverPod StreamProvider stuck in loading even though Stream.empty() should be returning when database is null

607

Stream.empty() never contains any data. If this is what your StreamProvider returns, the data callback will not be called. Most likely you'll get loading only.

In order to get a data callback, your stream should return at least one element (whether it's null or not).

Share:
607
Zelf
Author by

Zelf

Updated on December 27, 2022

Comments

  • Zelf
    Zelf over 1 year

    I'm using this great flutter/riverpod/firebase starter architecture. On my initial flutter screen, which imports top_level_providers.dart the loading parameter is all that is ever called.

    In my accountStreamProvider when the databaseProvider returns null Stream.empty() is emitted, which according to the docs "This is a stream which does nothing except sending a done event when it's listened to.".

    Why isn't the data parameter being called in the account.when section?

    root_screen.dart

    class AppStartupScreenRouter extends ConsumerWidget {
      const AppStartupScreenRouter({
        Key key,
      }) : super(key: key);
    
      @override
      Widget build(BuildContext context, ScopedReader watch) {
        final didCompleteOnboarding = watch(onboardingViewModelProvider.state);
        AsyncValue<Account> account = watch(accountStreamProvider);
    
        return account.when(
          data: (data) {
            if (data != null) {
              // evaluate the account info data and
              // return a screen widget
            } else if (didCompleteOnboarding) {
              // return a screen widget
            }
    
            return IntroScreen();
          },
          loading: () => LoadingScreen(),
          error: (err, stack) => EmptyContent(),
        );
      }
    }
    

    top_level_providers.dart

    final firebaseAuthProvider =
        Provider<FirebaseAuth>((ref) => FirebaseAuth.instance);
    
    final authStateChangesProvider = StreamProvider<User>(
        (ref) => ref.watch(firebaseAuthProvider).authStateChanges());
    
    final databaseProvider = Provider<FirestoreDatabase>((ref) {
      final auth = ref.watch(authStateChangesProvider);
    
      if (auth.data?.value?.uid != null) {
        return FirestoreDatabase(uid: auth.data?.value?.uid);
      }
    
      return null;
    });
    
    final accountStreamProvider = StreamProvider<Account>((ref) {
      final db = ref.watch(databaseProvider);
      return db != null ? db.accountStream() : Stream.empty();
    });
    

    firestore_database.dart

    class FirestoreDatabase {
      FirestoreDatabase({@required this.uid})
          : assert(
                uid != null, 'Cannot create FirestoreDatabase entry with null uid');
      final String uid;
    
      final _service = FirestoreService.instance;
    
      Future<void> setAccount(Account account) => _service.setData(
            path: FirestorePath.account(uid),
            data: account.toMap(),
          );
    
      Stream<Account> accountStream() => _service.documentStream(
            path: FirestorePath.account(uid),
            builder: (data, documentId) => Account.fromMap(data, uid),
          );
    
      Future<void> deleteAccount(Account account) =>
          _service.deleteData(path: FirestorePath.account(uid));
    }
    

    account.dart

    @immutable
    class Account extends Equatable {
      const Account({
        @required this.id,
        @required this.firstName,
        @required this.lastName,
      });
    
      final String id;
      final String firstName;
      final String lastName;
    
      @override
      List<Object> get props => [
            id,
            firstName,
            lastName,
          ];
    
      @override
      bool get stringify => true;
    
      factory Account.fromMap(Map<String, dynamic> data, String documentId) {
        if (data == null) {
          return null;
        }
    
        final firstName = data['firstName'] as String;
        final lastName = data['lastName'] as String;
    
        if (firstName == null || lastName == null) {
          return null;
        }
    
        return Account(
          id: documentId,
          firstName: firstName,
          lastName: lastName,
        );
      }
    
      Map<String, dynamic> toMap() {
        return {
          'firstName': firstName,
          'lastName': lastName,
        };
      }
    }