Riverpod StateNotifier and a Stream

938 Views Asked by At

I've got a Stream<UserProfile> being returned form a firebase service.

I'm using MVVM architecture and have a ProfileViewModel which is extended by a freezed state class:

class ProfileModel extends StateNotifier<ProfileState> {
  ProfileModel({
    required this.authService,
    required this.databaseService,
  }) : super(const ProfileState.loading());

  late AuthService authService;
  late FirestoreDatabase databaseService;

  Stream<UserProfile?> get userProfile {
    return databaseService.profileStream();
  }
}

The above results in the following view:

final profileModelProvider =
    StateNotifierProvider.autoDispose<ProfileModel, ProfileState>((ref) {
  final authService = ref.watch(authServiceProvider);

  final databaseService = ref.watch(databaseProvider)!;

  return ProfileModel(
      authService: authService, databaseService: databaseService);
});


class ProfilePageBuilder extends ConsumerWidget {
  const ProfilePageBuilder({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final state = ref.watch(profileModelProvider);
    final model = ref.read(profileModelProvider.notifier);
    final up = ref.read(userProfileProvider);

    return ProfilePage(
      onSubmit: () => {},
      name: up.value?.uid ?? "Empty",
      canSubmit: state.maybeWhen(
        canSubmit: () => true,
        success: () => true,
        orElse: () => false,
      ),
      isLoading: state.maybeWhen(
        loading: () => true,
        orElse: () => false,
      ),
      errorText: state.maybeWhen(
        error: (error) => error,
        orElse: () => null,
      ),
    );
  }
}

I would like to know the correct way (using riverpod) to pass the firebase stream to the UI without mixing up UI/BL without loosing functionality of real time data.

I was able to create a StreamProvider which referenced the profile model but it doesnt feel right.

final userProfileProvider = StreamProvider.autoDispose<UserProfile?>((ref) {
  return ref.watch(profileModelProvider.notifier).userProfile;
});

My alternative is to convert streams to futures within the view model and then update the state as the function runs.

I'm really quite stuck here, any help would be appreciated

1

There are 1 best solutions below

0
On

My guess is you want to

  • listen to a stream from Firebase
  • When the latest value changes, you want any dependencies to update
  • You only want the latest value of the stream.

INTRODUCING BehaviorSubject!

You'll need package:rxdart though you may already have it installed.


import 'package:rxdart/rxdart.dart';

@riverpod
BehaviorSubject<ProfileState> userProfileSubject(
    UserProfileSubjectRef ref) {
  
  final stream = ....;
  // Get the stream and wrap it in a BehaviorSubject
  return BehaviorSubject()..addStream(stream);
}

@riverpod
UserProfile? userProfile(
    UserProfileRef ref) {
  
  final behaviorSubject = ref.watch(userProfileSubjectProvider);
  // when the underlying stream updates,
  // invalidate self so we read the new value
  behaviorSubject.doOnData((newProfileState) { ref.invalidateSelf(); });

  // note that value could be null while stream
  // emits a value. You can wait for that
  // and convert this provider to return a Future<UserProfile>
  // or in the UI handle the null.
  // note that firebase could also have a null value.
  return behaviorSubject.value?.userProfile;
}