Fix Riverpod "Cannot use Ref or modify other providers inside life-cycles/selectors" crash in @riverpod provider bodies. Use when: (1) Crash during provider disposal with this exact error message, (2) Using keepAlive() with timer-based disposal, (3) Async callbacks (.then, .catchError) try to use ref.read() or ref.invalidateSelf(), (4) Error occurs after ref.onCancel/onResume/onDispose callbacks fire. Solution: wrap ref operations in try-catch or avoid using ref in async callbacks entirely.
Scanned 5/27/2026
Install via CLI
openskills install divinevideo/divine-mobile---
name: riverpod-ref-in-provider-lifecycle
description: |
Fix Riverpod "Cannot use Ref or modify other providers inside life-cycles/selectors" crash
in @riverpod provider bodies. Use when: (1) Crash during provider disposal with this exact
error message, (2) Using keepAlive() with timer-based disposal, (3) Async callbacks (.then,
.catchError) try to use ref.read() or ref.invalidateSelf(), (4) Error occurs after
ref.onCancel/onResume/onDispose callbacks fire. Solution: wrap ref operations in try-catch
or avoid using ref in async callbacks entirely.
author: Claude Code
version: 1.0.0
date: 2026-02-01
---
# Riverpod ref Usage in Provider Lifecycle Callbacks
## Problem
When using `ref.keepAlive()` with timer-based auto-disposal in `@riverpod` providers,
async callbacks (`.then()`, `.catchError()`) that execute during or after disposal
will crash when they try to use `ref.read()`, `ref.invalidateSelf()`, or any other
ref operation.
## Context / Trigger Conditions
**Error message:**
```
'package:riverpod/src/core/ref.dart': Failed assertion: line 216 pos 7:
'_debugCallbackStack == 0': Cannot use Ref or modify other providers inside life-cycles/selectors.
```
**Typical scenario:**
1. Provider uses `ref.keepAlive()` with a timer in `ref.onCancel()`:
```dart
final link = ref.keepAlive();
ref.onCancel(() {
Timer(Duration(seconds: 15), () {
link.close(); // Triggers disposal
});
});
```
2. Provider has async callbacks that use `ref`:
```dart
someAsyncOperation().then((_) {
if (ref.mounted) { // THIS CHECK IS NOT ENOUGH!
ref.invalidateSelf();
}
});
```
3. Timer fires while async callback is pending
4. CRASH - `ref.mounted` returns `true` but using `ref` is still forbidden
**Key insight:** `ref.mounted` returns `true` during lifecycle callbacks, but
using `ref` operations is still forbidden. This is counter-intuitive but by design.
## Solution
### Option 1: Wrap ref operations in try-catch (Recommended)
```dart
someAsyncOperation().then((_) {
try {
ref.read(someProvider.notifier).update(/*...*/);
} catch (e) {
Log.debug('Provider likely disposed: $e');
}
});
```
### Option 2: Avoid ref operations in async callbacks entirely
Instead of:
```dart
// BAD - uses ref in async callback
openVineVideoCache.removeCorruptedVideo(videoId).then((_) {
if (ref.mounted) {
ref.invalidateSelf(); // CRASH!
}
});
```
Do:
```dart
// GOOD - fire-and-forget without ref
unawaited(
openVineVideoCache.removeCorruptedVideo(videoId).then((_) {
Log.info('Cache removed'); // No ref usage
}),
);
// Let user retry manually or provider recreates on next access
```
### Option 3: Capture provider state synchronously first
```dart
// Read state BEFORE async operation
final notifier = ref.read(someProvider.notifier);
// Use captured reference in callback (not ref)
someAsyncOperation().then((_) {
notifier.doSomething(); // Uses captured reference, not ref
});
```
## Anti-Pattern: ref.mounted Check
```dart
// THIS DOES NOT WORK!
someAsyncOperation().then((_) {
if (ref.mounted) { // Returns true during lifecycle!
ref.invalidateSelf(); // Still crashes!
}
});
```
The `ref.mounted` check is insufficient because:
1. Timer fires, `link.close()` called
2. Riverpod enters disposal lifecycle (callback stack > 0)
3. `ref.mounted` check passes (still returns `true`!)
4. `ref.invalidateSelf()` called
5. CRASH - ref operations forbidden during lifecycle callbacks
## Verification
1. Rapidly scroll through content that uses the provider
2. Navigate away and back while providers are active
3. Let the keepAlive timers fire naturally (wait 15+ seconds after scrolling)
4. Check Crashlytics/console for the lifecycle assertion error
5. Error should no longer occur
## Example
### Before (causes crash):
```dart
@riverpod
VideoPlayerController videoController(Ref ref, String videoId) {
final link = ref.keepAlive();
ref.onCancel(() {
Timer(Duration(seconds: 15), () => link.close());
});
final controller = VideoPlayerController.networkUrl(url);
controller.initialize().catchError((error) {
// CRASH! This callback may run during disposal
if (ref.mounted) {
ref.invalidateSelf(); // Assertion failure!
}
});
return controller;
}
```
### After (safe):
```dart
@riverpod
VideoPlayerController videoController(Ref ref, String videoId) {
final link = ref.keepAlive();
ref.onCancel(() {
Timer(Duration(seconds: 15), () => link.close());
});
final controller = VideoPlayerController.networkUrl(url);
controller.initialize().catchError((error) {
// Safe - wrapped in try-catch
try {
ref.read(fallbackProvider.notifier).state = newValue;
} catch (e) {
Log.debug('Provider disposed during error handling: $e');
}
// Don't invalidateSelf - let provider recreate on next access
});
return controller;
}
```
## Notes
- This issue is specific to `@riverpod` provider bodies, not widget dispose()
- The related skill `riverpod-ref-read-in-dispose` covers widget lifecycle issues
- Consider whether `ref.invalidateSelf()` is even necessary - often the provider
will be recreated naturally on next access
- For truly critical cleanup, use `ref.onDispose()` which runs synchronously
before the lifecycle callback stack check
- This bug is particularly common with video players, image loaders, and other
resources that use keepAlive() with timer-based disposal
## Related Skills
- `riverpod-ref-read-in-dispose`: For widget dispose() ref.read() issues
- `flutter-dispose-timer-test-failure`: For timer-related test failures
## References
- [Riverpod ref.keepAlive](https://riverpod.dev/docs/concepts/modifiers/auto_dispose#refkeepalive)
- [Riverpod lifecycle callbacks](https://riverpod.dev/docs/concepts/provider_lifecycles)
No comments yet. Be the first to comment!