Skip to content

Commit 4011b8c

Browse files
authored
fix: Improve connection re-try mechanism (#857)
1 parent f2babcc commit 4011b8c

File tree

1 file changed

+41
-22
lines changed

1 file changed

+41
-22
lines changed

lib/providers/api_provider.dart

Lines changed: 41 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1+
import 'dart:async';
12
import 'dart:developer';
23
import 'dart:io';
34

45
import 'package:chopper/chopper.dart';
5-
import 'package:http/http.dart' as http;
66
import 'package:connectivity_plus/connectivity_plus.dart';
77
import 'package:flutter_riverpod/flutter_riverpod.dart';
8+
import 'package:http/http.dart' as http;
89
import 'package:punycoder/punycoder.dart';
910
import 'package:riverpod_annotation/riverpod_annotation.dart';
1011

@@ -74,6 +75,12 @@ class _TempJellyRequest implements Interceptor {
7475
}
7576
}
7677

78+
final int _maxRetries = 3;
79+
80+
bool _isConnectionError(Object e) {
81+
return e is IOException || e is TimeoutException;
82+
}
83+
7784
class JellyRequest implements Interceptor {
7885
JellyRequest(this.ref);
7986

@@ -82,31 +89,43 @@ class JellyRequest implements Interceptor {
8289
@override
8390
FutureOr<Response<BodyType>> intercept<BodyType>(Chain<BodyType> chain) async {
8491
final connectivityNotifier = ref.read(connectivityStatusProvider.notifier);
85-
String? serverUrl = ref.read(serverUrlProvider);
92+
final serverUrl = ref.read(serverUrlProvider);
8693

87-
try {
88-
if (serverUrl?.isEmpty == true || serverUrl == null) throw const HttpException("Failed to connect");
89-
90-
//Use current logged in user otherwise use the authProvider
91-
var loginModel = ref.read(userProvider)?.credentials ?? ref.read(authProvider).serverLoginModel?.tempCredentials;
92-
93-
if (loginModel == null) throw UnimplementedError();
94+
if (serverUrl == null || serverUrl.isEmpty) {
95+
throw const HttpException('No server URL provided');
96+
}
9497

95-
var headers = loginModel.header(ref);
96-
final Response<BodyType> response = await chain.proceed(
97-
applyHeaders(
98-
chain.request.copyWith(
99-
baseUri: Uri.parse(serverUrl),
100-
),
101-
headers),
102-
);
98+
// Use current logged in user otherwise use the authProvider
99+
final loginModel = ref.read(userProvider)?.credentials ?? ref.read(authProvider).serverLoginModel?.tempCredentials;
100+
if (loginModel == null) {
101+
throw UnimplementedError();
102+
}
103103

104-
connectivityNotifier.checkConnectivity();
105-
return response;
106-
} catch (e) {
107-
connectivityNotifier.onStateChange([ConnectivityResult.none]);
108-
throw Exception('Failed to make request\n$e');
104+
final headers = loginModel.header(ref);
105+
106+
for (var attempt = 0; attempt <= _maxRetries; attempt++) {
107+
try {
108+
final response = await chain.proceed(
109+
applyHeaders(
110+
chain.request.copyWith(baseUri: Uri.parse(serverUrl)),
111+
headers,
112+
),
113+
);
114+
115+
connectivityNotifier.checkConnectivity();
116+
return response;
117+
} catch (e) {
118+
if (!_isConnectionError(e) || attempt == _maxRetries) {
119+
connectivityNotifier.onStateChange([ConnectivityResult.none]);
120+
rethrow;
121+
}
122+
123+
final delay = Duration(milliseconds: 200 * (attempt + 1));
124+
log('Connection failed (attempt ${attempt + 1}/$_maxRetries), retrying in ${delay.inMilliseconds}ms: $e');
125+
await Future.delayed(delay);
126+
}
109127
}
128+
throw StateError('Unexpected state in JellyRequest.intercept');
110129
}
111130
}
112131

0 commit comments

Comments
 (0)