7

I'm getting this error in my StateNotifiers when one hits the back button on their webpage. I've isolated it to happening where the longRunningAPI request is below.

Exception has occurred.
"Error: Bad state: Tried to use RunListNotifier after `dispose` was called.

and I have code like this.

final runListController = StateNotifierProvider.autoDispose
    .family<RunListNotifier, AsyncValue<List<Run>>, RunListParameter>(
        (ref, param) {
  return RunListNotifier(read: ref.read, param: param);
});

class RunListNotifier extends StateNotifier<AsyncValue<List<Run>>> {
  RunListNotifier({required this.read, required this.param})
      : super(AsyncLoading()) {
    fetchViaAPI(param);
  }

  final Reader read;
  final RunListParameter param;
  void fetchViaAPI(RunListParameter param) async {
    state = AsyncLoading();
    try {
      List<Run> stuff = await read(apiProvider).longRunningAPI(param: param);
      state = AsyncData(stuff);
    } catch (e) {
      state = AsyncError(e);
    }
  }
}

is it safe to simply do something like this in the catch?

    } catch (e) {
      if (e.runtimeType.toString() == 'StateError') {
         // ignore the error
      } else {
         state = AsyncError(e);
      }
    }

foobar8675
  • 1,215
  • 3
  • 16
  • 26

1 Answers1

6

I believe you could solve this problem by checking mounted before setting the state after your API call like so:

List<Run> stuff = await read(apiProvider).longRunningAPI(param: param);
if (!mounted) return;
state = AsyncData(stuff);

This simply checks if dispose was called and if so, don't attempt to modify the state.

Another resource that could be useful is adding a cancelToken to your API call and canceling if the provider is disposed.

final longRunningApi = FutureProvider.autoDispose.family<List<Run>, RunListParameter>((ref, param) async {
  final cancelToken = CancelToken();
  ref.onDispose(cancelToken.cancel);

  final api = await ref.watch(apiProvider);
  final res = await api.longRunningApi(param, cancelToken);
  
  ref.maintainState = true;
  return res;
});

Then you'd have to add the cancelToken to your actual request. A great example of this in the marvel example project by the author of Riverpod can be found here.

Alex Hartford
  • 5,110
  • 2
  • 19
  • 36