0

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

PowerMan2015
  • 1,307
  • 5
  • 19
  • 40

1 Answers1

0

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;
}
Madushan
  • 6,977
  • 31
  • 79