The Offline Mode & Resilient Caching system (#87) enables uninterrupted navigation and portfolio inspection across RealizeAlpha when network connectivity is degraded or absent. By persisting key brokerage states, quotes, positions, and AI trade signals locally with automatic synchronization on reconnect, traders maintain uninterrupted situational awareness regardless of cellular or Wi-Fi conditions.
OfflineCacheService):
PortfolioSnapshot structures encapsulating accounts, portfolios, equity, cash, buying power, stock positions (InstrumentPosition), and option aggregate positions (OptionAggregatePosition).SharedPreferences).OfflineSyncService):
ChangeNotifier providing real-time network reachability detection, offline simulation flags for automated testing, and synchronization lifecycle tracking (isOffline, isSyncing, isShowingCachedData).registerReconnectCallback) that refresh stale data immediately when network reachability returns.triggerSync() with built-in debouncing and loading state propagation.OfflineStatusBanner):
HomeWidget, TradeSignalsPage, and SearchWidget.HomeWidget): Automatically saves fresh portfolio snapshots whenever network data is received; restores the last known cached snapshot if network calls fail or when launching without an active connection.TradeSignalsProvider): Caches incoming signal streams; restores local trade signals seamlessly if Firestore streaming fails or network drops.SearchWidget): Gracefully falls back to cached watchlist items and search results without crashing or displaying empty blank views.flowchart TD
subgraph Remote["Cloud & Brokerage APIs"]
API[Robinhood / Schwab / Firestore]
end
subgraph State["Service Layer"]
SyncService[OfflineSyncService<br/>- isOffline<br/>- isShowingCachedData<br/>- isSyncing]
CacheService[OfflineCacheService<br/>- savePortfolioSnapshot<br/>- loadPortfolioSnapshot<br/>- isDataStale]
end
subgraph UI["UI Presentation"]
Banner[OfflineStatusBanner]
Home[HomeWidget]
Signals[TradeSignalsPage]
Search[SearchWidget]
end
API -->|Live Data| Home
API -->|Live Signals| Signals
Home -->|On Success: Save| CacheService
Signals -->|On Stream: Save| CacheService
Home -.->|On Network Error: Load| CacheService
Signals -.->|On Stream Error: Load| CacheService
SyncService -->|Status / Timestamps| Banner
Banner -->|User Taps Retry| SyncService
SyncService -->|On Reconnect| Home
SyncService -->|On Reconnect| Signals
await OfflineCacheService.savePortfolioSnapshot(
accounts: accountStore.items,
portfolios: portfolioStore.items,
stockPositions: stockStore.items,
optionPositions: optionStore.items,
);
final snapshot = await OfflineCacheService.loadPortfolioSnapshot();
if (snapshot != null) {
// Populate local stores with cached data
}
@override
void initState() {
super.initState();
final syncService = Provider.of<OfflineSyncService>(context, listen: false);
syncService.registerReconnectCallback(_handleReconnectSync);
}
Future<void> _handleReconnectSync() async {
await _refreshData();
}
@override
void dispose() {
final syncService = Provider.of<OfflineSyncService>(context, listen: false);
syncService.unregisterReconnectCallback(_handleReconnectSync);
super.dispose();
}
// Inside any CustomScrollView slivers list:
OfflineStatusBanner(
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
onRetry: () => _refreshData(),
)
The offline mode functionality is validated through comprehensive unit and widget tests:
test/offline_cache_service_test.dart: Serialization, deserialization, stale evaluation thresholds, relative formatting, and cache clearing.test/offline_sync_service_test.dart: Online/offline status transitions, reconnect callback execution, unregistering callbacks, and error message tracking.test/offline_status_banner_widget_test.dart: Widget rendering across offline, cached snapshot, stale data, and syncing states.