91 lines
2.8 KiB
Dart
91 lines
2.8 KiB
Dart
import 'dart:async';
|
|
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import 'package:supabase_flutter/supabase_flutter.dart';
|
|
|
|
import 'supabase_provider.dart';
|
|
|
|
final realtimeControllerProvider = ChangeNotifierProvider<RealtimeController>((
|
|
ref,
|
|
) {
|
|
final client = ref.watch(supabaseClientProvider);
|
|
final controller = RealtimeController(client);
|
|
ref.onDispose(controller.dispose);
|
|
return controller;
|
|
});
|
|
|
|
/// Simplified realtime controller for app-lifecycle awareness.
|
|
/// Individual streams now handle their own recovery via [StreamRecoveryWrapper].
|
|
/// This controller only coordinates:
|
|
/// - App lifecycle transitions (background/foreground)
|
|
/// - Auth token refreshes
|
|
/// - Global connection state notification (for UI indicators)
|
|
class RealtimeController extends ChangeNotifier {
|
|
final SupabaseClient _client;
|
|
bool _disposed = false;
|
|
|
|
/// Global flag: true if any stream is recovering; used for subtle UI indicator.
|
|
bool isAnyStreamRecovering = false;
|
|
|
|
RealtimeController(this._client) {
|
|
_init();
|
|
}
|
|
|
|
void _init() {
|
|
try {
|
|
// Listen for auth changes; ensure tokens are fresh for realtime.
|
|
// Individual streams will handle their own reconnection.
|
|
_client.auth.onAuthStateChange.listen((data) {
|
|
final event = data.event;
|
|
// Only refresh token on existing session refreshes, not immediately after sign-in
|
|
// (sign-in already provides a fresh token)
|
|
if (event == AuthChangeEvent.tokenRefreshed) {
|
|
_ensureTokenFresh();
|
|
}
|
|
});
|
|
} catch (e) {
|
|
debugPrint('RealtimeController._init error: $e');
|
|
}
|
|
}
|
|
|
|
/// Ensure auth token is fresh for upcoming realtime operations.
|
|
/// This is called after token refresh events, not immediately after sign-in.
|
|
Future<void> _ensureTokenFresh() async {
|
|
if (_disposed) return;
|
|
try {
|
|
// Defensive: only refresh if the method exists (SDK version compatibility)
|
|
final authDynamic = _client.auth as dynamic;
|
|
if (authDynamic.refreshSession != null) {
|
|
await authDynamic.refreshSession?.call();
|
|
}
|
|
} catch (e) {
|
|
debugPrint('RealtimeController: token refresh failed: $e');
|
|
}
|
|
}
|
|
|
|
/// Notify that a stream is starting recovery. Used for global UI indicator.
|
|
void markStreamRecovering() {
|
|
if (!isAnyStreamRecovering) {
|
|
isAnyStreamRecovering = true;
|
|
notifyListeners();
|
|
}
|
|
}
|
|
|
|
/// Notify that stream recovery completed. If all streams recovered, update state.
|
|
void markStreamRecovered() {
|
|
// In practice, individual streams notify their own status via statusChanges.
|
|
// This is kept for potential future global coordination.
|
|
if (isAnyStreamRecovering) {
|
|
isAnyStreamRecovering = false;
|
|
notifyListeners();
|
|
}
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_disposed = true;
|
|
super.dispose();
|
|
}
|
|
}
|