Flutter PageView + URL Routing Reorder Detection Loop
SkillMediaFix infinite rebuild loop in Flutter when using PageView with URL-based routing (GoRouter) and reactive state management (Riverpod/BLoC). Use when: (1) RAPID REBUILD warnings appear in console (#20+), (2) "setState() or markNeedsBuild() called during build" errors from ValueListenableBuilder, (3) Video feed or list-based PageView is frozen and won't swipe, (4) Overlay or UI elements flicker between visible/invisible rapidly, (5) Logs show item "moved from index X to Y" repeatedly. Caused by tracking items by ID and updating URL when reactive list reorders.
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 Flutter PageView + URL Routing Reorder Detection Loop skill
What this skill tells your AI
The instructions your AI receives, as published by divinevideo/divine-mobile in .agents/skills/flutter-pageview-url-routing-reorder-loop/SKILL.md and read by ahel’s review.
Problem
When a PageView is synced bidirectionally with URL-based routing (e.g., GoRouter /home/:index)
and the data source is a reactive provider (Riverpod, BLoC stream), attempting to track the
currently-viewed item by its ID and update the URL when the item moves to a different index
in the list creates an infinite feedback loop.
Context / Trigger Conditions
Symptoms:
- Console shows
RAPID REBUILD #42!warnings (build count growing rapidly) setState() or markNeedsBuild() called during builderrors fromValueListenableBuilder- PageView is frozen - user swipes but page bounces back to same position
- UI overlay (social buttons, controls) flickers between visible and invisible
- Logs show repeated pattern:
Video X moved from index 4 -> 3, updating URL
Architecture that triggers this:
PageView.builderwithonPageChangedupdating URL viacontext.go('/feed/$index')- URL-derived index synced back to PageController via
jumpToPage()during build - Reactive data source (Riverpod provider, stream) that can re-emit with reordered items
- Item tracking:
_currentItemIdcompared against list to detect "moves"
Example of the anti-pattern:
// IN BUILD METHOD - creates feedback loop!
if (_currentVideoStableId != null && videos.isNotEmpty) {
final currentVideoIndex = videos.indexWhere(
(v) => v.stableId == _currentVideoStableId,
);
if (currentVideoIndex != -1 && currentVideoIndex != urlIndex) {
// This triggers URL update -> rebuild -> PageController sync ->
// onPageChanged -> URL update -> INFINITE LOOP
context.go('/home/$currentVideoIndex');
}
}
Solution
Remove item-tracking reorder detection entirely. The PageController should be the sole source of truth for which page the user is viewing.
Step 1: Remove tracking state
// REMOVE these fields:
// String? _currentVideoStableId;
// bool _urlUpdateScheduled = false;
Step 2: Remove reorder detection block
Remove any code in build() that:
- Searches for a tracked item ID in the current list
- Compares found index against URL index
- Schedules URL updates when indices differ
Step 3: Keep only one-way sync patterns
User swipe -> URL (one-way):
onPageChanged: (newIndex) {
if (newIndex != urlIndex) {
context.go('/home/$newIndex');
}
}
External navigation -> PageController (one-way):
if (urlIndex != _lastUrlIndex) {
_lastUrlIndex = urlIndex;
controller.jumpToPage(urlIndex);
}
These two paths don't create loops because:
- User swipe: URL updates, next build sees matching urlIndex -> no sync needed
- External nav: URL changes, sync fires, onPageChanged fires but
newIndex == urlIndex-> no URL update
The Feedback Loop Explained
┌─ Reactive provider re-emits (new data from server) ─┐
│ │
▼ │
Build runs with new video list │
│ │
▼ │
Reorder detection: "Video X moved from idx 4 to 3" │
│ │
▼ │
Schedule URL update: context.go('/home/3') │
│ │
▼ │
Build runs: urlIndex=3, PageController at page 4 │
│ │
▼ │
Sync: jumpToPage(3) │
│ │
▼ │
onPageChanged(3) fires → context.go('/home/3') │
│ │
▼ │
But PageController was at 4, which showed different │
video → _currentVideoStableId mismatches → detects │
"move" again → URL update to /home/4 │
│ │
└───────── INFINITE LOOP ──────────────────────────────┘
Verification
After removing reorder detection:
- No more
RAPID REBUILDwarnings in console - No
setState() called during builderrors - Swiping between pages works smoothly
- UI overlays stay visible on the active page
- No "moved from index X to Y" log spam
Notes
-
Why reorder detection seems needed: When a reactive list reorders (e.g., new items prepended), the user's current page index points to a different item. However, trying to "follow" the item creates worse UX (infinite loop) than staying on the same page index.
-
Alternative if position preservation is critical: Instead of URL-based reorder detection, stabilize the list order. Either:
- Don't reorder while the user is actively viewing (batch updates)
- Use a stable sort that preserves relative positions of existing items
- Only prepend/append new items, never reorder existing ones
-
Related pattern:
flutter-pageview-implicit-scrolling-snapbackaddresses a different PageView issue (snap-back on state rebuild) but can co-occur with this loop. -
Framework-agnostic: While this was discovered with GoRouter + Riverpod, the same pattern applies to any PageView + URL routing + reactive state combination (Navigator 2.0, auto_route, BLoC streams, etc.)
Signals
- GitHub stars
- 264
- Forks
- 55
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
flutter-pageview-url-routing-reorder-loop- Source
- github.com/divinevideo/divine-mobile