riverpod_timeline 1.1.0
riverpod_timeline: ^1.1.0 copied to clipboard
A Riverpod debugging and state-inspection package that records every provider update, tracks dependencies, visualizes state changes, and supports time-travel debugging.
riverpod_timeline #
Debug and inspect Riverpod state with zero app logic changes. Records every provider update, tracks dependencies, visualizes state changes, and supports time-travel debugging.
Features: Observer | Timeline Viewer | Dependency Graph | Performance Dashboard | Time Travel | Export/Import | Floating Overlay
Safety: Auto-disabled in release builds (opt-in available)
Screenshots #
| Timeline Viewer | Dependency Graph |
|---|---|
| [Timeline] | [Graph] |
| Performance Dashboard | Time Travel Debugging |
|---|---|
| [Performance] | [Time Travel] |
| Export / Import |
|---|
| [Export] |
Requirements #
- Flutter
>=3.22.0 - Riverpod
^3.0.0 - Dart
^3.12.1
Works with all Riverpod provider types: Provider, NotifierProvider, AsyncNotifierProvider, FutureProvider, StreamProvider, StreamNotifierProvider.
Install #
dependencies:
flutter_riverpod: ^3.0.0
riverpod_timeline: ^1.0.0
Quick Start #
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_timeline/riverpod_timeline.dart';
final _navigatorKey = GlobalKey<NavigatorState>();
void main() {
// 1. Initialize (before runApp)
RiverpodTimeline.initialize();
runApp(
// 2. RiverpodTimelineScope wraps ProviderScope (adds observer + overlay)
RiverpodTimelineScope(
navigatorKey: _navigatorKey,
child: MaterialApp(
navigatorKey: _navigatorKey,
home: const MyApp(),
),
),
);
}
That's it. A floating debug button appears on screen. Tap it to open the full debug panel (Timeline, Graph, Metrics).
Configuration #
RiverpodTimeline.initialize(
/// Max events kept in memory. Oldest are pruned when exceeded.
maxEvents: 10000,
/// Track rebuild counts, update frequency, and durations.
enablePerformanceTracking: true,
/// Record parent/child provider dependency relationships.
enableDependencyTracking: true,
/// Force-enable in release builds (default: off — auto-disabled).
enableReleaseMode: false,
/// Capture stack traces with each event (expensive; debugging only).
captureStackTraces: false,
/// Print events to the debug console via [debugPrint].
enableLogging: false,
/// Show the floating debug overlay automatically (default: true).
/// You can always show/hide it manually regardless of this setting.
enableOverlay: true,
);
Override overlay at runtime #
// Show/hide the floating button (no need to reinitialize)
RiverpodTimelineOverlay.setVisible(false);
RiverpodTimelineOverlay.setVisible(true);
RiverpodTimelineOverlay.toggleVisibility();
// Remove the overlay entirely
RiverpodTimelineOverlay.disable();
// Re-add it
RiverpodTimelineOverlay.enable(
timelineController: tl.controller,
graphController: tl.graphController,
metricsCollector: tl.metricsCollector,
context: context,
);
Manual setup (without RiverpodTimelineScope) #
If you prefer not to use RiverpodTimelineScope, wire things up yourself:
void main() {
RiverpodTimeline.initialize(enableOverlay: false); // no auto-overlay
runApp(
ProviderScope(
observers: [RiverpodTimeline.instance.observer],
child: MaterialApp(
navigatorKey: _navigatorKey,
home: const MyApp(),
),
),
);
}
// Access the panel programmatically:
final tl = RiverpodTimeline.instance;
Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => ProviderTimelineView(controller: tl.controller),
),
);
// Or use individual widgets:
// ProviderTimelineView(controller: tl.controller)
// ProviderDependencyGraph(controller: tl.graphController)
// RiverpodPerformanceDashboard(collector: tl.metricsCollector)
Time Travel Debugging #
final tl = RiverpodTimeline.instance;
tl.replay(); // Play all recorded events
tl.pause(); // Pause (maintains position)
tl.resumeReplay(); // Resume from current position
tl.stepForward(); // Next event
tl.stepBackward(); // Previous event
// Lower-level control:
final ttc = tl.timeTravelController;
ttc.goToEvent(5); // Jump to index
ttc.goToStart();
ttc.goToEnd();
ttc.replaySpeedMs = 200; // ms between events (50-5000)
ttc.state; // ReplayState.playing | .paused | .stopped
Export & Import #
final tl = RiverpodTimeline.instance;
// Export
final Map<String, dynamic> data = tl.exportJson();
final String json = tl.exportJsonString();
// Import
tl.importJson(data);
tl.importJsonString(json);
Includes: all events, provider metadata, dependency graph, performance metrics, and config.
Production Safety #
The timeline auto-disables in release builds (zero overhead, no code changes).
// Force-enable for internal/dogfooding builds:
RiverpodTimeline.initialize(
enableReleaseMode: true,
);
When disabled:
- No events recorded
- No memory allocated
ProviderObservermethods are no-ops
Memory #
maxEvents(default 10,000) limits memory usage- Oldest events pruned automatically
- All data is in-memory; no disk I/O during recording
Architecture #
lib/
├── riverpod_timeline.dart # Barrel export
└── src/
├── config.dart # RiverpodTimelineConfig
├── riverpod_timeline_api.dart # RiverpodTimeline (singleton)
├── models/ # Data models
│ ├── event_type.dart # created / updated / disposed / refreshed / error
│ ├── timeline_event.dart # Single event with value snapshots
│ ├── provider_info.dart # Provider metadata
│ ├── dependency_node.dart # Graph node + cycle detection
│ └── performance_metrics.dart # Per-provider stats
├── observer/timeline_observer.dart # ProviderObserver
├── storage/timeline_storage.dart # In-memory ring buffer
├── timeline/timeline_controller.dart # Query & filter
├── graph/dependency_graph.dart # Dependency graph controller
├── replay/time_travel_controller.dart # Playback engine
├── metrics/metrics_collector.dart # Performance data
├── widgets/ # Flutter UI
│ ├── timeline_viewer.dart # ProviderTimelineView
│ ├── dependency_graph_widget.dart # ProviderDependencyGraph
│ ├── performance_dashboard.dart # RiverpodPerformanceDashboard
│ └── event_detail_widget.dart # Expandable event card
├── overlay/debug_overlay.dart # Floating debug button
└── export/timeline_exporter.dart # JSON export/import
Data Flow #
ProviderContainer ──> TimelineObserver ──> TimelineStorage
│ │
DependencyGraph TimelineController
Controller │
│ Widgets
MetricsCollector │
│ TimeTravelController
PerformanceSummary (replay engine)
API Reference #
| Method | Returns | Description |
|---|---|---|
RiverpodTimeline.initialize(...) |
RiverpodTimeline |
Init before runApp |
RiverpodTimeline.instance |
RiverpodTimeline |
Singleton accessor |
RiverpodTimeline.reset() |
void |
Clear singleton (tests) |
tl.config |
RiverpodTimelineConfig |
Current config |
tl.observer |
TimelineObserver |
Pass to ProviderScope |
tl.controller |
TimelineController |
Query/filter events |
tl.graphController |
DependencyGraphController |
Dependency inspection |
tl.timeTravelController |
TimeTravelController |
Replay engine |
tl.metricsCollector |
MetricsCollector |
Performance data |
tl.isEnabled |
bool |
Whether engine is active |
Example App #
cd example && flutter run
Showcases: multiple provider types, timeline viewer, dependency graph, performance dashboard, export, and the developer overlay.
Testing #
flutter test
flutter test --coverage
Covers: serialization, storage, dependency graph, time travel, metrics, export/import, and singleton lifecycle.
Contributing #
- Fork the repo
- Create a branch:
git checkout -b feature/amazing-feature - Commit:
git commit -m 'Add amazing feature' - Push:
git push origin feature/amazing-feature - Open a Pull Request
Guidelines: maintain 90%+ coverage, match existing code style, zero analyzer warnings, no new dependencies without discussion.
License #
MIT — see LICENSE
Copyright (c) 2026