Riverpod giving a bad state exception when one hits back button on webpage

Viewed 1433

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);
      }
    }

1 Answers

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.

Related