Riverpod ref.listen Must Be in build() for ConsumerStatefulWidget
SkillMediaFix "ref.listen can only be used within the build method of a ConsumerWidget" assertion error in Flutter Riverpod 3.x. Use when: (1) ConsumerStatefulWidget crashes with "Failed assertion: line 492 pos 7: 'debugDoingBuild'" in flutter_riverpod consumer.dart, (2) ref.listen is called inside initState, addPostFrameCallback, or any lifecycle method other than build(), (3) All widget tests fail with this assertion from scheduler callback. Applies to flutter_riverpod 3.0+ with ConsumerStatefulWidget.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Riverpod ref.listen Must Be in build() for ConsumerStatefulWidget skill
What this skill tells your AI
The instructions your AI receives, as published by divinevideo/divine-mobile in .agents/skills/riverpod-ref-listen-build-only/SKILL.md and read by ahel’s review.
Problem
In Riverpod 3.x, calling ref.listen inside initState(), addPostFrameCallback, or
any method other than build() causes an assertion failure:
ref.listen can only be used within the build method of a ConsumerWidget
'package:flutter_riverpod/src/core/consumer.dart':
Failed assertion: line 492 pos 7: 'debugDoingBuild'
This typically manifests when trying to reactively watch a provider value that may change after the widget is first built (e.g., waiting for an async operation to complete).
Context / Trigger Conditions
- Using
ConsumerStatefulWidgetwithflutter_riverpod: ^3.0.0 - Calling
ref.listen(provider, callback)insideinitState()or a post-frame callback - All widget tests fail with the assertion error from
ConsumerStatefulElement.listen - The error occurs during
pumpWidgetorpumpAndSettlein tests - Stack trace shows
SchedulerBinding._invokeFrameCallback->ConsumerStatefulElement.listen
Solution
Move ref.listen into the build() method. Use a guard flag to ensure side effects
only trigger once.
Before (broken):
class _MyScreenState extends ConsumerState<MyScreen> {
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
// THIS WILL CRASH in Riverpod 3.x
ref.listen(
myProvider.select((s) => s.someValue),
(previous, next) {
if (previous == null && next != null) {
_doSomething(next);
}
},
);
});
}
}
After (fixed):
class _MyScreenState extends ConsumerState<MyScreen> {
bool _actionAttempted = false;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
if (!mounted) return;
// Immediate check for already-available data
final value = ref.read(myProvider).someValue;
if (value != null) _tryDoSomething(value);
});
}
void _tryDoSomething(SomeType? value) {
if (value == null || _actionAttempted) return;
_actionAttempted = true;
_doSomething(value);
}
@override
Widget build(BuildContext context) {
// ref.listen is safe here - Riverpod auto-manages the subscription
ref.listen(
myProvider.select((s) => s.someValue),
(previous, next) {
if (previous == null && next != null) {
_tryDoSomething(next);
}
},
);
return // ... widget tree
}
}
Key points:
ref.listeninbuild()is auto-managed by Riverpod (no manual disposal needed)- It re-registers each build but Riverpod handles deduplication
- Use a guard flag (
_actionAttempted) to prevent side effects from firing multiple times - Keep the immediate check in
initState's post-frame callback for when data is already available ref.readis fine ininitState/callbacks; onlyref.listenandref.watchare restricted
Verification
- All widget tests pass without assertion errors
- The listener fires correctly when the watched value changes
- Side effects only trigger once (verify with a counter or log)
Notes
ref.watchis also restricted tobuild()onlyref.readcan be used anywhere (initState, callbacks, dispose, etc.)- In older Riverpod versions (< 3.0),
ref.listenwas less restricted - If you need a listener outside
build(), useref.listenManual()which returns aProviderSubscriptionthat must be manually disposed indispose() - This constraint exists because Riverpod needs the widget's element context to properly manage subscription lifecycle
References
Signals
- GitHub stars
- 264
- Forks
- 55
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
riverpod-ref-listen-build-only- Source
- github.com/divinevideo/divine-mobile