Skip to content

Commit 135ba3c

Browse files
refactor: interception logic to use base classes and be compatible with all request types (#98)
* Implement interception all request types * update readme * fix: unmodifiable body, example usage, unused imports * fix: minor fixes and improvements * fix: code cleanup, better docs, extension based, renaming and more Co-authored-by: Alejandro Ulate <[email protected]>
1 parent 2898af8 commit 135ba3c

Some content is hidden

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

43 files changed

+1331
-1260
lines changed

CHANGELOG.md

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,22 @@
11
# Changelog
22

3+
## 2.0.0-beta.1
4+
5+
- ❗️🛠&nbsp;&nbsp;Changed: Renamed `Method` to use `HttpMethod` and refactored helper functions into extensions (`StringToMethod`, and `MethodToString`).
6+
- ❗️🛠&nbsp;&nbsp;Changed: `InterceptorContract` to use `BaseRequest` and `BaseResponse` instead of custom models.
7+
- ❗️🛠&nbsp;&nbsp;Removed: `RequestData` and `ResponseData` since the classes are no longer used.
8+
-&nbsp;&nbsp;Added: Support for intercepting `Request`,`StreamedRequest` and `MultipartRequest`.
9+
-&nbsp;&nbsp;Added: Support for intercepting `Response`,`StreamedResponse` and `MultipartRequest`.
10+
-&nbsp;&nbsp;Added: Extensions for `BaseRequest`, `Request`,`StreamedRequest` and `MultipartRequest` that allows copying requests through a `copyWith` method.
11+
-&nbsp;&nbsp;Added: Extensions for `BaseResponse`, `Response`,`StreamedResponse` and `IOStreamedResponse` that allows copying responses through a `copyWith` method.
12+
- 📖&nbsp;&nbsp;Changed: **example** project to showcase updated APIs.
13+
- 🚦&nbsp;&nbsp;Tests: Improved testing and documentation.
14+
315
## 1.0.2
416

517
- 📖&nbsp;&nbsp;Changed: example project to showcase `RetryPolicy` usage.
618
- 🐞&nbsp;&nbsp;Fixed: `parameters` were missing in requests of type `POST`, `PUT`, `PATCH`, and `DELETE`.
7-
- 🐞&nbsp;&nbsp;Fixed: `int` or other non-string parameters are not being added to request. Thanks to @
8-
Contributor
9-
meysammahfouzi
19+
- 🐞&nbsp;&nbsp;Fixed: `int` or other non-string parameters are not being added to request. Thanks to @meysammahfouzi
1020
- 🐞&nbsp;&nbsp;Fixed: `body` is not sent in delete requests despite being accepted as parameter. Thanks to @MaciejZuk
1121

1222
## 1.0.1

README.md

Lines changed: 54 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,23 @@ This is a plugin that lets you intercept the different requests and responses fr
2020

2121
**Already using `http_interceptor`? Check out the [1.0.0 migration guide](./guides/migration_guide_1.0.0.md) for quick reference on the changes made and how to migrate your code.**
2222

23-
- [Installation](#installation)
24-
- [Features](#features)
25-
- [Usage](#usage)
26-
- [Building your own interceptor](#building-your-own-interceptor)
27-
- [Using your interceptor](#using-your-interceptor)
28-
- [Retrying requests](#retrying-requests)
29-
- [Using self-signed certificates](#using-self-signed-certificates)
30-
- [Having trouble? Fill an issue](#troubleshooting)
31-
- [Roadmap](https://doc.clickup.com/p/h/82gtq-119/f552a826792c049)
32-
- [Contribution](#contributions)
23+
- [http_interceptor](#http_interceptor)
24+
- [Quick Reference](#quick-reference)
25+
- [Installation](#installation)
26+
- [Features](#features)
27+
- [Usage](#usage)
28+
- [Building your own interceptor](#building-your-own-interceptor)
29+
- [Using your interceptor](#using-your-interceptor)
30+
- [Using interceptors with Client](#using-interceptors-with-client)
31+
- [Using interceptors without Client](#using-interceptors-without-client)
32+
- [Retrying requests](#retrying-requests)
33+
- [Using self signed certificates](#using-self-signed-certificates)
34+
- [InterceptedClient](#interceptedclient)
35+
- [InterceptedHttp](#interceptedhttp)
36+
- [Roadmap](#roadmap)
37+
- [Troubleshooting](#troubleshooting)
38+
- [Contributions](#contributions)
39+
- [Contributors](#contributors)
3340

3441
## Installation
3542

@@ -65,15 +72,15 @@ In order to implement `http_interceptor` you need to implement the `InterceptorC
6572
```dart
6673
class LoggingInterceptor implements InterceptorContract {
6774
@override
68-
Future<RequestData> interceptRequest({required RequestData data}) async {
69-
print(data.toString());
70-
return data;
75+
Future<BaseRequest> interceptRequest({required BaseRequest request}) async {
76+
print(request.toString());
77+
return request;
7178
}
7279
7380
@override
74-
Future<ResponseData> interceptResponse({required ResponseData data}) async {
75-
print(data.toString());
76-
return data;
81+
Future<BaseResponse> interceptResponse({required BaseResponse response}) async {
82+
print(response.toString());
83+
return response;
7784
}
7885
7986
}
@@ -84,19 +91,43 @@ class LoggingInterceptor implements InterceptorContract {
8491
```dart
8592
class WeatherApiInterceptor implements InterceptorContract {
8693
@override
87-
Future<RequestData> interceptRequest({required RequestData data}) async {
94+
Future<BaseRequest> interceptRequest({required BaseRequest request}) async {
8895
try {
89-
data.params['appid'] = OPEN_WEATHER_API_KEY;
90-
data.params['units'] = 'metric';
91-
data.headers["Content-Type"] = "application/json";
96+
request.url.queryParameters['appid'] = OPEN_WEATHER_API_KEY;
97+
request.url.queryParameters['units'] = 'metric';
98+
request.headers[HttpHeaders.contentTypeHeader] = "application/json";
9299
} catch (e) {
93100
print(e);
94101
}
95-
return data;
102+
return request;
96103
}
97104
98105
@override
99-
Future<ResponseData> interceptResponse({required ResponseData data}) async => data;
106+
Future<BaseResponse> interceptResponse({required BaseResponse response}) async => response;
107+
}
108+
```
109+
110+
- You can also react to and modify specific types of requests and responses, such as `StreamedRequest`,`StreamedResponse`, or `MultipartRequest` :
111+
112+
```dart
113+
class MultipartRequestInterceptor implements InterceptorContract {
114+
@override
115+
Future<BaseRequest> interceptRequest({required BaseRequest request}) async {
116+
if(request is MultipartRequest){
117+
request.fields['app_version'] = await PackageInfo.fromPlatform().version;
118+
}
119+
return request;
120+
}
121+
122+
@override
123+
Future<BaseResponse> interceptResponse({required BaseResponse response}) async {
124+
if(response is StreamedResponse){
125+
response.stream.asBroadcastStream().listen((data){
126+
print(data);
127+
});
128+
}
129+
return response;
130+
}
100131
}
101132
```
102133

@@ -181,7 +212,7 @@ Sometimes you need to retry a request due to different circumstances, an expired
181212
```dart
182213
class ExpiredTokenRetryPolicy extends RetryPolicy {
183214
@override
184-
Future<bool> shouldAttemptRetryOnResponse(ResponseData response) async {
215+
Future<bool> shouldAttemptRetryOnResponse(BaseResponse response) async {
185216
if (response.statusCode == 401) {
186217
// Perform your token refresh here.
187218

example/ios/Flutter/AppFrameworkInfo.plist

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,6 @@
2121
<key>CFBundleVersion</key>
2222
<string>1.0</string>
2323
<key>MinimumOSVersion</key>
24-
<string>8.0</string>
24+
<string>9.0</string>
2525
</dict>
2626
</plist>

example/ios/Podfile.lock

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@ EXTERNAL SOURCES:
1414
:path: ".symlinks/plugins/shared_preferences/ios"
1515

1616
SPEC CHECKSUMS:
17-
Flutter: 434fef37c0980e73bb6479ef766c45957d4b510c
17+
Flutter: 50d75fe2f02b26cc09d224853bb45737f8b3214a
1818
shared_preferences: af6bfa751691cdc24be3045c43ec037377ada40d
1919

2020
PODFILE CHECKSUM: aafe91acc616949ddb318b77800a7f51bffa2a4c
2121

22-
COCOAPODS: 1.10.1
22+
COCOAPODS: 1.11.0

example/ios/Runner.xcodeproj/project.pbxproj

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -349,7 +349,7 @@
349349
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
350350
GCC_WARN_UNUSED_FUNCTION = YES;
351351
GCC_WARN_UNUSED_VARIABLE = YES;
352-
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
352+
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
353353
MTL_ENABLE_DEBUG_INFO = NO;
354354
SDKROOT = iphoneos;
355355
SUPPORTED_PLATFORMS = iphoneos;
@@ -431,7 +431,7 @@
431431
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
432432
GCC_WARN_UNUSED_FUNCTION = YES;
433433
GCC_WARN_UNUSED_VARIABLE = YES;
434-
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
434+
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
435435
MTL_ENABLE_DEBUG_INFO = YES;
436436
ONLY_ACTIVE_ARCH = YES;
437437
SDKROOT = iphoneos;
@@ -480,7 +480,7 @@
480480
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
481481
GCC_WARN_UNUSED_FUNCTION = YES;
482482
GCC_WARN_UNUSED_VARIABLE = YES;
483-
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
483+
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
484484
MTL_ENABLE_DEBUG_INFO = NO;
485485
SDKROOT = iphoneos;
486486
SUPPORTED_PLATFORMS = iphoneos;

example/lib/main.dart

Lines changed: 39 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
import 'dart:convert';
2+
import 'dart:developer';
23
import 'dart:io';
34

45
import 'package:flutter/material.dart';
6+
import 'package:http/src/base_request.dart';
7+
import 'package:http/src/base_response.dart';
58
import 'package:http_interceptor/http_interceptor.dart';
69
import 'package:shared_preferences/shared_preferences.dart';
7-
import 'credentials.dart'; // If you are going to run this example you need to replace the key.
10+
811
import 'cities.dart'; // This is just a List of Maps that contains the suggested cities.
12+
import 'credentials.dart'; // If you are going to run this example you need to replace the key.
913

1014
void main() => runApp(MyApp());
1115

@@ -44,7 +48,7 @@ class _HomeScreenState extends State<HomeScreen> {
4448
Future<void> clearStorageForDemoPurposes() async {
4549
final cache = await SharedPreferences.getInstance();
4650

47-
cache.setString(appToken, OPEN_WEATHER_EXPIRED_API_KEY);
51+
cache.setString(kOWApiToken, OPEN_WEATHER_EXPIRED_API_KEY);
4852
}
4953

5054
@override
@@ -164,7 +168,7 @@ class WeatherSearch extends SearchDelegate<String?> {
164168
builder: (context, snapshot) {
165169
if (snapshot.hasError) {
166170
return Center(
167-
child: Text(snapshot.error as String),
171+
child: Text(snapshot.error?.toString() ?? 'Error'),
168172
);
169173
}
170174

@@ -274,7 +278,7 @@ class WeatherRepository {
274278
// throw Exception("Error while fetching. \n ${response.body}");
275279
// }
276280
// } catch (e) {
277-
// print(e);
281+
// log(e);
278282
// }
279283
// return parsedWeather;
280284
// }
@@ -297,7 +301,7 @@ class WeatherRepository {
297301
} on FormatException {
298302
return Future.error('Bad response format 👎');
299303
} on Exception catch (error) {
300-
print(error);
304+
log(error.toString());
301305
return Future.error('Unexpected error 😢');
302306
}
303307

@@ -307,41 +311,45 @@ class WeatherRepository {
307311

308312
class LoggerInterceptor implements InterceptorContract {
309313
@override
310-
Future<RequestData> interceptRequest({required RequestData data}) async {
311-
print("----- Request -----");
312-
print(data.toString());
313-
return data;
314+
Future<BaseRequest> interceptRequest({required BaseRequest request}) async {
315+
log("----- Request -----");
316+
log(request.toString());
317+
return request;
314318
}
315319

316320
@override
317-
Future<ResponseData> interceptResponse({required ResponseData data}) async {
318-
print("----- Response -----");
319-
print(data.toString());
320-
return data;
321+
Future<BaseResponse> interceptResponse(
322+
{required BaseResponse response}) async {
323+
log("----- Response -----");
324+
log('Err. Code: ${response.statusCode}');
325+
log(response.toString());
326+
return response;
321327
}
322328
}
323329

324-
const String appToken = "TOKEN";
330+
const String kOWApiToken = "TOKEN";
325331

326332
class WeatherApiInterceptor implements InterceptorContract {
327333
@override
328-
Future<RequestData> interceptRequest({required RequestData data}) async {
329-
try {
330-
final cache = await SharedPreferences.getInstance();
334+
Future<BaseRequest> interceptRequest({required BaseRequest request}) async {
335+
final cache = await SharedPreferences.getInstance();
331336

332-
data.params['appid'] = cache.getString(appToken);
333-
data.params['units'] = 'metric';
334-
data.headers[HttpHeaders.contentTypeHeader] = "application/json";
335-
} catch (e) {
336-
print(e);
337-
}
338-
print(data.params);
339-
return data;
337+
final Map<String, String>? headers = Map.from(request.headers);
338+
headers?[HttpHeaders.contentTypeHeader] = "application/json";
339+
340+
return request.copyWith(
341+
url: request.url.addParameters({
342+
'appid': cache.getString(kOWApiToken) ?? '',
343+
'units': 'metric',
344+
}),
345+
headers: headers,
346+
);
340347
}
341348

342349
@override
343-
Future<ResponseData> interceptResponse({required ResponseData data}) async =>
344-
data;
350+
Future<BaseResponse> interceptResponse(
351+
{required BaseResponse response}) async =>
352+
response;
345353
}
346354

347355
class ExpiredTokenRetryPolicy extends RetryPolicy {
@@ -350,18 +358,18 @@ class ExpiredTokenRetryPolicy extends RetryPolicy {
350358

351359
@override
352360
bool shouldAttemptRetryOnException(Exception reason) {
353-
print(reason);
361+
log(reason.toString());
354362

355363
return false;
356364
}
357365

358366
@override
359-
Future<bool> shouldAttemptRetryOnResponse(ResponseData response) async {
367+
Future<bool> shouldAttemptRetryOnResponse(BaseResponse response) async {
360368
if (response.statusCode == 401) {
361-
print("Retrying request...");
369+
log("Retrying request...");
362370
final cache = await SharedPreferences.getInstance();
363371

364-
cache.setString(appToken, OPEN_WEATHER_API_KEY);
372+
cache.setString(kOWApiToken, OPEN_WEATHER_API_KEY);
365373

366374
return true;
367375
}

0 commit comments

Comments
 (0)