Unhandled Exception: Bad state: Tried to use StateNotifier after `dispose` was called

509

Managed to solve the problem but api is getting called twice when the firestore document is updated.

final profileAsyncController =
    StateNotifierProvider<ProfileAsyncNotifier, AsyncValue<ProfileUser?>>(
        (ref) => ProfileAsyncNotifier(ref));

class ProfileAsyncNotifier extends StateNotifier<AsyncValue<ProfileUser?>> {
  ProfileAsyncNotifier(this.ref) : super(AsyncLoading()) {
    _init();
  }

  final Ref ref;

  void _init() async {
    final firestoreUser = ref.watch(firestoreUserChangeProvider);

    var profile = await ref.read(apiServiceProvider)!.getProfile();
    state = AsyncData(profile);

    final firestoreDb = ref.read(firestoreDatabaseProvider);
    firestoreUser.whenData((value) {
      if (value!.syncRequired!) {
        firestoreDb!.stopSync();
      }
    });
  }
}
Share:
509
Huzzi
Author by

Huzzi

Updated on January 03, 2023

Comments

  • Huzzi
    Huzzi over 1 year

    I'm using riverpod to manage my app states and struggling with the last provider. Basically, I'm using Firebase auth provider and have a Firestore document (FirestoreUser) for each user that matches on auth uid.

    For the app to function properly I need to do the following:

    • When a user is authenticated, I need retrieve a Firestore document (FirestoreUser) as a stream (document matched on auth uid).
    • Make an api call (not firestore) to retrieve ProfileUser model by passing uid as parameter which will be used throughout the app.
    • Firestore document has a field called sync_required, if this value is set to true then make api call and refresh the UI and also set the sync_required to false. The purpose of this firestore document is to keep the Api data in sync inside the app.
    • If the ProfileUser is updated within the app, the UI should reflect the changes and also call another api endpoint to update the source data.

    Here are my providers.

    final firebaseAuthProvider = Provider<FirebaseAuth>((ref) => FirebaseAuth.instance);

    final authStateChangeProvider = StreamProvider<User?>((ref) => ref.watch(firebaseAuthProvider).authStateChanges());

    final firestoreDatabaseProvider = Provider<FirestoreDatabase?>((ref) {
      final auth = ref.watch(authStateChangeProvider);
    
      if (auth.asData?.value?.uid != null) {
        return FirestoreDatabase(uid: auth.asData!.value!.uid);
      }
      return null;
    });
    
    final firestoreUserChangeProvider = StreamProvider<FirestoreUser?>((ref) {
      return ref.watch(firestoreDatabaseProvider)!.getFirestoreUser();
    });
    
    final apiServiceProvider = Provider<ApiService?>((ref) {
      final auth = ref.watch(authStateChangeProvider);
    
      if (auth.asData?.value?.uid != null) {
        return ApiService(uid: auth.asData!.value!.uid);
      }
      return null;
    });
    

    To maintain the state of ProfileUser from the api I'm using StateNotifier and it's not working properly.

    The error I'm getting is Unhandled Exception: Bad state: Tried to use ProfileUserStateNotifier after dispose was called.

    class ProfileUserStateNotifier extends StateNotifier<ProfileUser?> {
    
    ApiService? _apiService;
    
      ProfileUserStateNotifier(this._apiService) : super(new ProfileUser()) {
        load();
      }
    
      void load() async {
        var profileUser = await _apiService!.getProfile();
        state = profileUser;
      }
    }
    
    final profileNotifier =
        StateNotifierProvider<ProfileUserStateNotifier, ProfileUser?>((ref) {
      final firestoreUser = ref.watch(firestoreUserChangeProvider);
    
      final firestoreDb = ref.read(firestoreDatabaseProvider);
    
      firestoreUser.whenData((value) {
        if (value!.syncRequired!) {
          firestoreDb!.stopSync();
        }
      });
    
      return ProfileUserStateNotifier(ref.read(apiServiceProvider));
    });
    

    firestore_database.dart

    Stream<FirestoreUser> getFirestoreUser() {
        return ref.doc(uid).snapshots().map((value) => FirestoreUser.fromJson(value.data() as Map<dynamic, dynamic>));
      }
    

    api_service.dart

     Future<ProfileUser?> getProfile() async {
       //call api end point
      return profileUser;
    }
    

    How can I make sure that I have uid from the auth provider, FirestoreUser initiated and ProfileUser object before any other action take place?

    Also, I'm not sure if I'm using the StateNotifer properly in my home screen as I'm checking for null and displaying progress indicator. What if the api call returns null in that case my home screen will be stuck in progress indicator.

     @override
      Widget build(BuildContext context, WidgetRef ref) {
        return Scaffold(
          body: Consumer(
            builder: (context, watch, child) {
            var profile = ref.watch(profileNotifier);
            if (profile!.username == null) {
              return Scaffold(body: Center(child:CircularProgressIndicator()));
            } else {
              return Scaffold(body: Center(child: Text(profile.username!)));
            }
            
          }),
        );
      }
    

    Sorry, I'm new to Flutter and Riverpod so any code reference or link to tutorials to solve the issue I'm having would be highly appreciated.