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.".
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,
};
}
}
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).