import 'dart:async'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; import 'supabase_provider.dart'; final realtimeControllerProvider = ChangeNotifierProvider(( ref, ) { final client = ref.watch(supabaseClientProvider); final controller = RealtimeController(client); ref.onDispose(controller.dispose); return controller; }); /// A lightweight controller that attempts to recover the Supabase Realtime /// connection when the app returns to the foreground or when auth tokens /// are refreshed. class RealtimeController extends ChangeNotifier { RealtimeController(this._client) { _init(); } final SupabaseClient _client; bool isConnecting = false; bool _disposed = false; void _init() { try { // Listen for auth changes and try to recover the realtime connection _client.auth.onAuthStateChange.listen((data) { final event = data.event; if (event == AuthChangeEvent.tokenRefreshed || event == AuthChangeEvent.signedIn) { recoverConnection(); } }); } catch (_) {} } /// Try to reconnect the realtime client using a small exponential backoff. Future recoverConnection() async { if (_disposed) return; if (isConnecting) return; isConnecting = true; notifyListeners(); try { int attempt = 0; int maxAttempts = 4; int delaySeconds = 1; while (attempt < maxAttempts && !_disposed) { attempt++; try { // Best-effort disconnect then connect so the realtime client picks // up any refreshed tokens. try { // Try to refresh session/token if the SDK supports it. Use dynamic // to avoid depending on a specific SDK version symbol. try { await (_client.auth as dynamic).refreshSession?.call(); } catch (_) {} // Best-effort disconnect then connect so the realtime client picks // up any refreshed tokens. The realtime connect/disconnect are // marked internal by the SDK; suppress the lint here since this // is a deliberate best-effort recovery. // ignore: invalid_use_of_internal_member _client.realtime.disconnect(); } catch (_) {} await Future.delayed(const Duration(milliseconds: 300)); try { // ignore: invalid_use_of_internal_member _client.realtime.connect(); } catch (_) {} // Give the socket a moment to stabilise. await Future.delayed(const Duration(seconds: 1)); // Exit early; we don't have a reliable sync API for connection // state across all platforms, so treat this as a best-effort // resurrection. break; } catch (_) { await Future.delayed(Duration(seconds: delaySeconds)); delaySeconds = delaySeconds * 2; } } } finally { if (!_disposed) { isConnecting = false; notifyListeners(); } } } @override void dispose() { _disposed = true; super.dispose(); } }