Skip to content

Commit eb613bc

Browse files
authored
feat(llc): Add classes for Dio Http client (#4)
* Add classes for Dio Http client * decrease minimum meta version * fix easy analysis warnings and add first tests * disable changelog requirement * downgrade build_runner * Formatting * decrease pana requirements * add codecov settings * Improve typing of http calls * Add tests for interceptors * Add test for network recovery
1 parent 61a5279 commit eb613bc

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

55 files changed

+3254
-124
lines changed

.github/workflows/pana.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,4 @@ jobs:
2222
uses: ./.github/actions/pana
2323
with:
2424
working_directory: packages/stream_core
25-
min_score: 140 # Missing 10 points for no example and 10 points for license
25+
min_score: 125 # Missing 10 points for no example, 10 points for license, and 10 points for `publish_to` in pubspec.

.github/workflows/pr_title.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ jobs:
2626
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
2727

2828
semantic_changelog_update:
29+
if: ${{ false }} # TODO: Enable after the first release
2930
needs: conventional_pr_title # Trigger after the [conventional_pr_title] completes
3031
runs-on: ubuntu-latest
3132
steps:

.github/workflows/stream_core_flutter_workflow.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ jobs:
4646

4747
- name: Dart Analyze
4848
run: |
49-
melos run analyze
49+
melos run analyze
5050
5151
- name: Check formatting
5252
run: |

analysis_options.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,3 +89,7 @@ linter:
8989

9090
# There are situations where we use default in enums on purpose
9191
no_default_cases: false
92+
93+
# Temporarily disabled to find more important issues
94+
public_member_api_docs: false
95+
avoid_print: false

codecov.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
coverage:
2+
status:
3+
project:
4+
default: # default is the status check's name, not default settings
5+
target: auto
6+
threshold: 5
7+
base: auto
8+
patch:
9+
default:
10+
target: 80%

melos.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ command:
1414
sdk: ^3.6.2
1515
# We are not using carat '^' syntax here because flutter don't follow semantic versioning.
1616
flutter: ">=3.27.4"
17+
dev_dependencies:
18+
build_runner: ^2.4.15
1719

1820
scripts:
1921
postclean:
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export 'api/connection_id_provider.dart';
2+
export 'api/http_client.dart';
3+
export 'api/stream_core_dio_error.dart';
4+
export 'api/system_environment.dart';
5+
export 'api/system_environment_manager.dart';
6+
export 'api/token_manager.dart';
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
// ignore_for_file: use_setters_to_change_properties
2+
3+
/// Provides the connection id of the websocket connection
4+
typedef ConnectionIdProvider = String? Function();
Lines changed: 300 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,300 @@
1+
import 'dart:async';
2+
3+
import 'package:dio/dio.dart';
4+
import 'package:meta/meta.dart';
5+
6+
import '../../stream_core.dart';
7+
import '../logger/stream_logger.dart';
8+
import 'interceptors/additional_headers_interceptor.dart';
9+
import 'interceptors/auth_interceptor.dart';
10+
import 'interceptors/connection_id_interceptor.dart';
11+
import 'interceptors/logging_interceptor.dart';
12+
13+
part 'http_client_options.dart';
14+
15+
const _tag = 'SC:CoreHttpClient';
16+
17+
/// This is where we configure the base url, headers,
18+
/// query parameters and convenient methods for http verbs with error parsing.
19+
class CoreHttpClient {
20+
/// [CoreHttpClient] constructor
21+
CoreHttpClient(
22+
this.apiKey, {
23+
Dio? dio,
24+
HttpClientOptions? options,
25+
TokenManager? tokenManager,
26+
ConnectionIdProvider? connectionIdProvider,
27+
required SystemEnvironmentManager systemEnvironmentManager,
28+
StreamLogger? logger,
29+
Iterable<Interceptor>? interceptors,
30+
HttpClientAdapter? httpClientAdapter,
31+
}) : _options = options ?? const HttpClientOptions(),
32+
httpClient = dio ?? Dio() {
33+
httpClient
34+
..options.baseUrl = _options.baseUrl
35+
..options.receiveTimeout = _options.receiveTimeout
36+
..options.connectTimeout = _options.connectTimeout
37+
..options.queryParameters = {
38+
'api_key': apiKey,
39+
..._options.queryParameters,
40+
}
41+
..options.headers = {
42+
'Content-Type': 'application/json',
43+
'Content-Encoding': 'application/gzip',
44+
..._options.headers,
45+
}
46+
..interceptors.addAll([
47+
AdditionalHeadersInterceptor(systemEnvironmentManager),
48+
if (tokenManager != null) AuthInterceptor(this, tokenManager),
49+
if (connectionIdProvider != null)
50+
ConnectionIdInterceptor(connectionIdProvider),
51+
...interceptors ??
52+
[
53+
// Add a default logging interceptor if no interceptors are
54+
// provided.
55+
if (logger != null)
56+
LoggingInterceptor(
57+
requestHeader: true,
58+
logPrint: (step, message) {
59+
switch (step) {
60+
case InterceptStep.request:
61+
return logger.log(
62+
Priority.info,
63+
_tag,
64+
message.toString,
65+
);
66+
case InterceptStep.response:
67+
return logger.log(
68+
Priority.info,
69+
_tag,
70+
message.toString,
71+
);
72+
case InterceptStep.error:
73+
return logger.log(
74+
Priority.error,
75+
_tag,
76+
message.toString,
77+
);
78+
}
79+
},
80+
),
81+
],
82+
]);
83+
if (httpClientAdapter != null) {
84+
httpClient.httpClientAdapter = httpClientAdapter;
85+
}
86+
}
87+
88+
/// Your project Stream Chat api key.
89+
/// Find your API keys here https://getstream.io/dashboard/
90+
final String apiKey;
91+
92+
/// Your project Stream Chat ClientOptions
93+
final HttpClientOptions _options;
94+
95+
/// [Dio] httpClient
96+
/// It's been chosen because it's easy to use
97+
/// and supports interesting features out of the box
98+
/// (Interceptors, Global configuration, FormData, File downloading etc.)
99+
@visibleForTesting
100+
final Dio httpClient;
101+
102+
/// Shuts down the [CoreHttpClient].
103+
///
104+
/// If [force] is `false` the [CoreHttpClient] will be kept alive
105+
/// until all active connections are done. If [force] is `true` any active
106+
/// connections will be closed to immediately release all resources. These
107+
/// closed connections will receive an error event to indicate that the client
108+
/// was shut down. In both cases trying to establish a new connection after
109+
/// calling [close] will throw an exception.
110+
void close({bool force = false}) => httpClient.close(force: force);
111+
112+
ClientException _parseError(DioException exception) {
113+
// locally thrown dio error
114+
if (exception is StreamDioException) return exception.exception;
115+
// real network request dio error
116+
return exception.toClientException();
117+
}
118+
119+
/// Handy method to make http GET request with error parsing.
120+
Future<Response<T>> get<T>(
121+
String path, {
122+
Map<String, Object?>? queryParameters,
123+
Map<String, Object?>? headers,
124+
ProgressCallback? onReceiveProgress,
125+
CancelToken? cancelToken,
126+
}) async {
127+
try {
128+
final response = await httpClient.get<T>(
129+
path,
130+
queryParameters: queryParameters,
131+
options: Options(headers: headers),
132+
onReceiveProgress: onReceiveProgress,
133+
cancelToken: cancelToken,
134+
);
135+
return response;
136+
} on DioException catch (error, stackTrace) {
137+
throw Error.throwWithStackTrace(_parseError(error), stackTrace);
138+
}
139+
}
140+
141+
/// Handy method to make http POST request with error parsing.
142+
Future<Response<T>> post<T>(
143+
String path, {
144+
Object? data,
145+
Map<String, Object?>? queryParameters,
146+
Map<String, Object?>? headers,
147+
ProgressCallback? onSendProgress,
148+
ProgressCallback? onReceiveProgress,
149+
CancelToken? cancelToken,
150+
}) async {
151+
try {
152+
final response = await httpClient.post<T>(
153+
path,
154+
queryParameters: queryParameters,
155+
data: data,
156+
options: Options(headers: headers),
157+
onSendProgress: onSendProgress,
158+
onReceiveProgress: onReceiveProgress,
159+
cancelToken: cancelToken,
160+
);
161+
return response;
162+
} on DioException catch (error, stackTrace) {
163+
throw Error.throwWithStackTrace(_parseError(error), stackTrace);
164+
}
165+
}
166+
167+
/// Handy method to make http DELETE request with error parsing.
168+
Future<Response<T>> delete<T>(
169+
String path, {
170+
Map<String, Object?>? queryParameters,
171+
Map<String, Object?>? headers,
172+
CancelToken? cancelToken,
173+
}) async {
174+
try {
175+
final response = await httpClient.delete<T>(
176+
path,
177+
queryParameters: queryParameters,
178+
options: Options(headers: headers),
179+
cancelToken: cancelToken,
180+
);
181+
return response;
182+
} on DioException catch (error, stackTrace) {
183+
throw Error.throwWithStackTrace(_parseError(error), stackTrace);
184+
}
185+
}
186+
187+
/// Handy method to make http PATCH request with error parsing.
188+
Future<Response<T>> patch<T>(
189+
String path, {
190+
Object? data,
191+
Map<String, Object?>? queryParameters,
192+
Map<String, Object?>? headers,
193+
ProgressCallback? onSendProgress,
194+
ProgressCallback? onReceiveProgress,
195+
CancelToken? cancelToken,
196+
}) async {
197+
try {
198+
final response = await httpClient.patch<T>(
199+
path,
200+
queryParameters: queryParameters,
201+
data: data,
202+
options: Options(headers: headers),
203+
onSendProgress: onSendProgress,
204+
onReceiveProgress: onReceiveProgress,
205+
cancelToken: cancelToken,
206+
);
207+
return response;
208+
} on DioException catch (error, stackTrace) {
209+
throw Error.throwWithStackTrace(_parseError(error), stackTrace);
210+
}
211+
}
212+
213+
/// Handy method to make http PUT request with error parsing.
214+
Future<Response<T>> put<T>(
215+
String path, {
216+
Object? data,
217+
Map<String, Object?>? queryParameters,
218+
Map<String, Object?>? headers,
219+
ProgressCallback? onSendProgress,
220+
ProgressCallback? onReceiveProgress,
221+
CancelToken? cancelToken,
222+
}) async {
223+
try {
224+
final response = await httpClient.put<T>(
225+
path,
226+
queryParameters: queryParameters,
227+
data: data,
228+
options: Options(headers: headers),
229+
onSendProgress: onSendProgress,
230+
onReceiveProgress: onReceiveProgress,
231+
cancelToken: cancelToken,
232+
);
233+
return response;
234+
} on DioException catch (error, stackTrace) {
235+
throw Error.throwWithStackTrace(_parseError(error), stackTrace);
236+
}
237+
}
238+
239+
/// Handy method to post files with error parsing.
240+
Future<Response<T>> postFile<T>(
241+
String path,
242+
MultipartFile file, {
243+
Map<String, Object?>? queryParameters,
244+
Map<String, Object?>? headers,
245+
ProgressCallback? onSendProgress,
246+
ProgressCallback? onReceiveProgress,
247+
CancelToken? cancelToken,
248+
}) async {
249+
final formData = FormData.fromMap({'file': file});
250+
final response = await post<T>(
251+
path,
252+
data: formData,
253+
queryParameters: queryParameters,
254+
headers: headers,
255+
onSendProgress: onSendProgress,
256+
onReceiveProgress: onReceiveProgress,
257+
cancelToken: cancelToken,
258+
);
259+
return response;
260+
}
261+
262+
/// Handy method to make generic http request with error parsing.
263+
Future<Response<T>> request<T>(
264+
String path, {
265+
Object? data,
266+
Map<String, Object?>? queryParameters,
267+
Options? options,
268+
ProgressCallback? onSendProgress,
269+
ProgressCallback? onReceiveProgress,
270+
CancelToken? cancelToken,
271+
}) async {
272+
try {
273+
final response = await httpClient.request<T>(
274+
path,
275+
data: data,
276+
queryParameters: queryParameters,
277+
options: options,
278+
onSendProgress: onSendProgress,
279+
onReceiveProgress: onReceiveProgress,
280+
cancelToken: cancelToken,
281+
);
282+
return response;
283+
} on DioException catch (error, stackTrace) {
284+
throw Error.throwWithStackTrace(_parseError(error), stackTrace);
285+
}
286+
}
287+
288+
/// Handy method to make http requests from [RequestOptions]
289+
/// with error parsing.
290+
Future<Response<T>> fetch<T>(
291+
RequestOptions requestOptions,
292+
) async {
293+
try {
294+
final response = await httpClient.fetch<T>(requestOptions);
295+
return response;
296+
} on DioException catch (error, stackTrace) {
297+
throw Error.throwWithStackTrace(_parseError(error), stackTrace);
298+
}
299+
}
300+
}

0 commit comments

Comments
 (0)