Debug and fix infinite widget rebuild loops in Flutter apps using Riverpod state management. Use when: (1) Logs show "RAPID REBUILD" warnings or 50+ rebuilds in seconds, (2) UI becomes unresponsive or shows same content repeatedly, (3) Bug only affects slower devices or poor network conditions, (4) Provider watching creates circular dependencies with router/URL state. Covers ref.watch() overuse, watch+listener redundancy, and transitive dependency chains.
Scanned 9/3/2026
Install to Claude Code
npx -y skills add divinevideo/divine-mobile --skill riverpod-infinite-rebuild-loop --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Riverpod Infinite Rebuild Loop?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/divinevideo-riverpod-infinite-rebuild-loop-divine-mobile)More formats (shields.io, HTML) on the badges page.
---
name: riverpod-infinite-rebuild-loop
description: |
Debug and fix infinite widget rebuild loops in Flutter apps using Riverpod state management.
Use when: (1) Logs show "RAPID REBUILD" warnings or 50+ rebuilds in seconds, (2) UI becomes
unresponsive or shows same content repeatedly, (3) Bug only affects slower devices or poor
network conditions, (4) Provider watching creates circular dependencies with router/URL state.
Covers ref.watch() overuse, watch+listener redundancy, and transitive dependency chains.
author: Claude Code
version: 1.0.0
date: 2026-02-02
---
# Riverpod Infinite Rebuild Loop
## Problem
Flutter widgets using Riverpod enter an infinite rebuild loop, causing:
- 50+ widget rebuilds in seconds
- UI becomes unresponsive
- Same content rendered repeatedly
- Bug manifests primarily on slower devices or poor network connections
## Context / Trigger Conditions
**Symptoms in logs:**
```
⚠️ RAPID REBUILD #54! Only 15ms since last build
⚠️ RAPID REBUILD DETECTED! Only 6ms since last build
```
**User-reported symptoms:**
- "Endless scroll loop" - same video/content keeps appearing
- "App freezes" or becomes unresponsive
- "Works on my phone but not on older devices"
**Trigger scenarios:**
1. Multiple async providers completing at staggered times
2. Provider that watches route/URL state AND updates URL in build
3. Using `ref.watch()` AND adding manual listeners to same provider
4. Transitive watches: Widget watches A, A watches B, Widget also watches B
## Root Cause Analysis
### Pattern 1: URL Update Feedback Loop
```dart
// BAD: Creates infinite loop
Widget build(BuildContext context) {
final pageContext = ref.watch(pageContextProvider); // Watches URL
final videos = ref.watch(videosProvider);
// Detect video moved position and "silently" update URL
if (currentVideoIndex != urlIndex) {
context.go('/home/$currentVideoIndex'); // URL change triggers rebuild!
}
}
```
**Loop:** Videos reorder → URL updated → pageContextProvider emits → rebuild → videos may reorder again → repeat
### Pattern 2: Watch + Listener Redundancy
```dart
// BAD: Double-subscribing causes double rebuilds
final cache = ref.watch(cacheProvider); // Watch triggers rebuild
cache.addListener(onCacheChanged); // Listener ALSO triggers action
```
### Pattern 3: Transitive Watch Dependencies
```dart
// BAD: Double-watching same source
Widget build() {
ref.watch(pageContextProvider); // Watch #1
ref.watch(derivedProvider); // derivedProvider ALSO watches pageContextProvider!
}
```
### Pattern 4: Staggered Async Provider Loading
```dart
// PROBLEMATIC on slow devices: Each watch triggers rebuild when provider completes
final a = ref.watch(asyncProviderA); // Completes at T=100ms → rebuild
final b = ref.watch(asyncProviderB); // Completes at T=200ms → rebuild
final c = ref.watch(asyncProviderC); // Completes at T=350ms → rebuild
final d = ref.watch(asyncProviderD); // Completes at T=500ms → rebuild
// On fast devices: all complete ~simultaneously, 1-2 rebuilds
// On slow devices: staggered completion, 4+ rebuilds
```
## Solution
### Step 1: Audit `ref.watch()` Usage
For each `ref.watch()` in build methods, ask:
- Does this provider change frequently?
- Do I need to REBUILD when it changes, or just REACT?
- Am I also manually listening to this provider?
**Riverpod Methods:**
| Method | Behavior |
|--------|----------|
| `ref.watch()` | Subscribe + **REBUILD** on change |
| `ref.read()` | Read once, **NO rebuild** |
| `ref.listen()` | Subscribe + callback, **NO rebuild** |
### Step 2: Convert Unnecessary Watches
```dart
// BEFORE: Rebuilds on every change
final videoService = ref.watch(videoServiceProvider);
// AFTER: Read once, use listener for reactions
final videoService = ref.read(videoServiceProvider);
ref.listen(videoServiceProvider, (prev, next) {
// React to changes without rebuilding
if (next.hasNewVideos) refreshUI();
});
```
### Step 3: Remove Watch + Listener Redundancy
```dart
// BEFORE: Double-subscription
final cache = ref.watch(cacheProvider);
cache.addListener(onCacheChanged);
// AFTER: Choose one approach
// Option A: Just watch (if rebuild is needed)
final cache = ref.watch(cacheProvider);
// Option B: Read + listen (if rebuild not needed)
final cache = ref.read(cacheProvider);
ref.listen(cacheProvider, (_, __) => onCacheChanged());
```
### Step 4: Break URL Update Loops
```dart
// BEFORE: URL update in build causes loop
if (currentVideoIndex != urlIndex) {
context.go('/home/$currentVideoIndex');
}
// AFTER: Don't update URL on content reorder
// Just track position with PageController, not URL
// OR use content ID in URL instead of index: /home/video/abc123
```
### Step 5: Batch Initial Load
```dart
// BEFORE: Watch each async provider (N rebuilds on slow devices)
final a = ref.watch(asyncA);
final b = ref.watch(asyncB);
// AFTER: Create combined provider that waits for all
@riverpod
Future<CombinedState> combinedState(Ref ref) async {
final a = await ref.watch(asyncA.future);
final b = await ref.watch(asyncB.future);
return CombinedState(a, b);
}
// Widget watches only the combined provider (1 rebuild)
```
## Verification
After fixes:
1. Run app on slow device or use network throttling
2. Check logs for rebuild warnings - should see ≤5 rebuilds on startup
3. Scroll/navigate and verify UI remains responsive
4. No "RAPID REBUILD" warnings in logs
## Example: Full Fix
**Before (problematic):**
```dart
class _HomeScreenState extends ConsumerState<HomeScreen> {
@override
Widget build(BuildContext context) {
final pageContext = ref.watch(pageContextProvider);
final videos = ref.watch(videosProvider); // Also watches pageContextProvider internally!
// URL update loop
if (videos.currentIndex != pageContext.index) {
WidgetsBinding.instance.addPostFrameCallback((_) {
context.go('/home/${videos.currentIndex}');
});
}
return PageView(...);
}
}
```
**After (fixed):**
```dart
class _HomeScreenState extends ConsumerState<HomeScreen> {
@override
Widget build(BuildContext context) {
// Only watch videos, read page context
final pageContext = ref.read(pageContextProvider).requireValue;
final videos = ref.watch(videosProvider);
// Don't update URL on reorder - track with PageController only
// URL only changes on explicit user navigation
return PageView(...);
}
}
```
## Notes
- This bug is **timing-dependent** - may not reproduce on fast devices
- Test on physical devices with network throttling to catch issues
- Add rebuild detection logging during development:
```dart
static int _buildCount = 0;
static DateTime? _lastBuild;
@override
Widget build(BuildContext context) {
final now = DateTime.now();
if (_lastBuild != null && now.difference(_lastBuild!).inMilliseconds < 100) {
Log.warning('RAPID REBUILD #${++_buildCount}!');
}
_lastBuild = now;
// ...
}
```
## References
- [Riverpod: Reading Providers](https://riverpod.dev/docs/concepts/reading)
- [Riverpod: ref.watch vs ref.read vs ref.listen](https://riverpod.dev/docs/essentials/combining_providers)
- [Flutter DevTools: Identify Rebuilds](https://docs.flutter.dev/tools/devtools/performance)
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!