Fix flickering issues due to reconnecting loop

This commit is contained in:
2026-03-02 22:09:29 +08:00
parent 5713581992
commit 7115e2df05
8 changed files with 171 additions and 45 deletions
+27 -2
View File
@@ -109,6 +109,7 @@ class StreamRecoveryWrapper<T> {
int _recoveryAttempts = 0;
Timer? _pollingTimer;
Timer? _recoveryTimer;
Timer? _stabilityTimer;
StreamSubscription<List<Map<String, dynamic>>>? _realtimeSub;
StreamController<StreamRecoveryResult<T>>? _controller;
bool _disposed = false;
@@ -159,8 +160,28 @@ class StreamRecoveryWrapper<T> {
void _onRealtimeData(List<Map<String, dynamic>> rows) {
if (_disposed) return;
// Successful data — reset recovery counters.
_recoveryAttempts = 0;
// When recovering, don't reset _recoveryAttempts immediately.
// Supabase streams emit an initial REST fetch before the realtime
// channel is established. If the channel keeps failing, resetting
// on that REST data creates an infinite loop (data → reset → subscribe
// → fail → data → reset …). Instead, start a stability timer — only
// reset after staying connected without errors for 30 seconds.
if (_recoveryAttempts > 0) {
_stabilityTimer?.cancel();
_stabilityTimer = Timer(const Duration(seconds: 30), () {
if (!_disposed) {
_recoveryAttempts = 0;
debugPrint(
'StreamRecoveryWrapper[$channelName]: '
'connection stable for 30s — recovery counter reset',
);
}
});
} else {
_recoveryAttempts = 0;
}
_setStatus(StreamConnectionStatus.connected);
_emit(
StreamRecoveryResult<T>(
@@ -174,6 +195,9 @@ class StreamRecoveryWrapper<T> {
void _onRealtimeError(Object error, [StackTrace? stack]) {
if (_disposed) return;
// Cancel any stability timer — the connection is not stable.
_stabilityTimer?.cancel();
final isRateLimit = _isRateLimitError(error);
final isTimeout = _isTimeoutError(error);
final tag = isRateLimit
@@ -337,6 +361,7 @@ class StreamRecoveryWrapper<T> {
_disposed = true;
_pollingTimer?.cancel();
_recoveryTimer?.cancel();
_stabilityTimer?.cancel();
_realtimeSub?.cancel();
// Ensure the channel is removed from the recovering set when the
// wrapper is torn down (e.g. provider disposed during navigation).