Santosh Kumar
Santosh Kumar

Reputation: 196

A ChangeNotifierProxyProvider was used after being disposed

I am retrying an api call if I get 401 response but I am not Abel to call and I am facing exception following is my retry code

final client = RetryClient(
      http.Client(),
      retries: 1,
      when: (response) {
        return response.statusCode == 401 ? true : false;
      },
      onRetry: (req, res, retryCount) {
        print('retry started');
        if (retryCount == 0 && res?.statusCode == 401) {
          Provider.of<Auth>(context, listen: false).restoreAccessToken();
          
        }
      },
    );

in the above code I am Abel to restore new access token but I am not Abel to retry api call following is my proxy provider I Am guessing error is coming from here

ChangeNotifierProxyProvider<Auth, ApiCalls>(
            create: (_) => ApiCalls(null),
            update: (context, auth, previous) => ApiCalls(auth.token)),

enter image description here

Upvotes: 1

Views: 536

Answers (2)

Mostafa Alazhariy
Mostafa Alazhariy

Reputation: 479

I've tried several and will share what I came up with.

I ran into the same issue mentioned in the question, I'll explain it again now.

This is my ChangeNotifierProxyProvider code:

ChangeNotifierProxyProvider<ProfileProvider, SplashProvider>(
          create: (context) => SplashProvider(splashRepo: SplashRepo(), profileProvider: ProfileProvider()),
          update: (context, profileProvider, _) => SplashProvider(splashRepo: SplashRepo(), profileProvider: profileProvider),
        ),

And when I execute the notifyListeners(); function from SplashProvider, the same error mentioned in the question pops up

Unhandled Exception: A SplashProvider was used after being disposed.

So the logical solution is to move the Provider above MaterialApp/Navigator as mentioned in this issue.

However my code is actually written this way! So how does this error appear? I don't understand why SplashProvider is being disposed? I did not even disposed my current screen yet!

So I've tried the following:

  1. change SplashProvider constructor from
SplashProvider({
    required this.splashRepo,
    required this.profileProvider,
  })

to:

SplashProvider({
    required this.splashRepo,
    required this.profileProvider,
  }) {
    debugPrint('SplashProvider constructor');
  }

To keep track of the number of objects being created from the same provider It turned out that more than one instance was created, and this is the output:

I/flutter ( 5370): SplashProvider constructor
I/flutter ( 5370): SplashProvider constructor
I/flutter ( 5370): SplashProvider constructor

So I modified the constructor by adding message:

final SplashRepo splashRepo;
  ProfileProvider? profileProvider;
  + final String? message;

  SplashProvider({
    required this.splashRepo,
    this.profileProvider,
    + this.message,
  }) {
    debugPrint('SplashProvider constructor: $message');
  }

Then changed ChangeNotifierProxyProvider by adding message to distinguish each of them:

ChangeNotifierProxyProvider<ProfileProvider, SplashProvider>(
          create: (context) => SplashProvider(message: 'create', splashRepo: SplashRepo(), profileProvider: ProfileProvider()),
          update: (context, profileProvider, _) => SplashProvider(message: 'update', splashRepo: SplashRepo(), profileProvider: profileProvider),
        ),

Then I wrote these two commands into SplashProvider to track if the provider was disposed or not, and also when notifyListeners was called, and for which object in them.

@override
  void dispose() {
    debugPrint('SplashProvider disposed...[$message]');
    super.dispose();
  }

  @override
  void notifyListeners() {
    debugPrint('notifyListeners in splash: [$message]');
    super.notifyListeners();
  }

And this is the output:

...
I/flutter ( 5370): SplashProvider constructor: create
I/flutter ( 5370): SplashProvider constructor: update
I/flutter ( 5370): notifyListeners in splash: [update]
...
I/flutter ( 5370): SplashProvider constructor: update
I/flutter ( 5370): SplashProvider disposed...[update]
I/flutter ( 5370): [🌎 Easy Localization] [DEBUG] Build
...
I/flutter ( 5370): notifyListeners in splash: [update]
E/flutter ( 5370): [ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: A SplashProvider was used after being disposed.
E/flutter ( 5370): Once you have called dispose() on a SplashProvider, it can no longer be used.
...

So it tries to call notifyListeners in the updated instance which had disposed already!

then it throws that error 😃

To solve this problem I tried changing the main ChangeNotifierProxyProvider command to:

ChangeNotifierProxyProvider<ProfileProvider, SplashProvider>(
          create: // same code...,
          update: (context, profileProvider, prev) => prev!..setProfileProvider(profileProvider),
        ),

And surely added this function in SplashProvider:

void setProfileProvider(ProfileProvider profile){
   this.profileProvider = profile;
  }

to set profileProvider.

Upon trying more than once, the problem was actually solved. 1. The error never appears again. 2. SplashProvider is not disposed. 3. Only one Provider instance is created.

But I don't know yet if this will affect the other Provider that I passed to SplashProvider and will it pass the same instance or will it create a new instance? I will experiment and if there is something unexpected I will amend my comment.

Upvotes: 2

Santosh Kumar
Santosh Kumar

Reputation: 196

here after a long try I got the solution following code solved the issue I had changed code in change notifier provider

ChangeNotifierProxyProvider<Auth, ApiCalls>(
            create: (_) => ApiCalls(),
            update: (context, auth, previous) =>
                previous!..updates(auth.token!)),

Upvotes: 0

Related Questions