Skip to content

Commit

Permalink
refactor: interception logic to use base classes and be compatible wi…
Browse files Browse the repository at this point in the history
…th 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]>
  • Loading branch information
lukaskurz and CodingAleCR authored Nov 14, 2021
1 parent 2898af8 commit 135ba3c
Show file tree
Hide file tree
Showing 43 changed files with 1,331 additions and 1,260 deletions.
16 changes: 13 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,22 @@
# Changelog

## 2.0.0-beta.1

- ❗️🛠&nbsp;&nbsp;Changed: Renamed `Method` to use `HttpMethod` and refactored helper functions into extensions (`StringToMethod`, and `MethodToString`).
- ❗️🛠&nbsp;&nbsp;Changed: `InterceptorContract` to use `BaseRequest` and `BaseResponse` instead of custom models.
- ❗️🛠&nbsp;&nbsp;Removed: `RequestData` and `ResponseData` since the classes are no longer used.
-&nbsp;&nbsp;Added: Support for intercepting `Request`,`StreamedRequest` and `MultipartRequest`.
-&nbsp;&nbsp;Added: Support for intercepting `Response`,`StreamedResponse` and `MultipartRequest`.
-&nbsp;&nbsp;Added: Extensions for `BaseRequest`, `Request`,`StreamedRequest` and `MultipartRequest` that allows copying requests through a `copyWith` method.
-&nbsp;&nbsp;Added: Extensions for `BaseResponse`, `Response`,`StreamedResponse` and `IOStreamedResponse` that allows copying responses through a `copyWith` method.
- 📖&nbsp;&nbsp;Changed: **example** project to showcase updated APIs.
- 🚦&nbsp;&nbsp;Tests: Improved testing and documentation.

## 1.0.2

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

## 1.0.1
Expand Down
77 changes: 54 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,23 @@ This is a plugin that lets you intercept the different requests and responses fr

**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.**

- [Installation](#installation)
- [Features](#features)
- [Usage](#usage)
- [Building your own interceptor](#building-your-own-interceptor)
- [Using your interceptor](#using-your-interceptor)
- [Retrying requests](#retrying-requests)
- [Using self-signed certificates](#using-self-signed-certificates)
- [Having trouble? Fill an issue](#troubleshooting)
- [Roadmap](https://doc.clickup.com/p/h/82gtq-119/f552a826792c049)
- [Contribution](#contributions)
- [http_interceptor](#http_interceptor)
- [Quick Reference](#quick-reference)
- [Installation](#installation)
- [Features](#features)
- [Usage](#usage)
- [Building your own interceptor](#building-your-own-interceptor)
- [Using your interceptor](#using-your-interceptor)
- [Using interceptors with Client](#using-interceptors-with-client)
- [Using interceptors without Client](#using-interceptors-without-client)
- [Retrying requests](#retrying-requests)
- [Using self signed certificates](#using-self-signed-certificates)
- [InterceptedClient](#interceptedclient)
- [InterceptedHttp](#interceptedhttp)
- [Roadmap](#roadmap)
- [Troubleshooting](#troubleshooting)
- [Contributions](#contributions)
- [Contributors](#contributors)

## Installation

Expand Down Expand Up @@ -65,15 +72,15 @@ In order to implement `http_interceptor` you need to implement the `InterceptorC
```dart
class LoggingInterceptor implements InterceptorContract {
@override
Future<RequestData> interceptRequest({required RequestData data}) async {
print(data.toString());
return data;
Future<BaseRequest> interceptRequest({required BaseRequest request}) async {
print(request.toString());
return request;
}
@override
Future<ResponseData> interceptResponse({required ResponseData data}) async {
print(data.toString());
return data;
Future<BaseResponse> interceptResponse({required BaseResponse response}) async {
print(response.toString());
return response;
}
}
Expand All @@ -84,19 +91,43 @@ class LoggingInterceptor implements InterceptorContract {
```dart
class WeatherApiInterceptor implements InterceptorContract {
@override
Future<RequestData> interceptRequest({required RequestData data}) async {
Future<BaseRequest> interceptRequest({required BaseRequest request}) async {
try {
data.params['appid'] = OPEN_WEATHER_API_KEY;
data.params['units'] = 'metric';
data.headers["Content-Type"] = "application/json";
request.url.queryParameters['appid'] = OPEN_WEATHER_API_KEY;
request.url.queryParameters['units'] = 'metric';
request.headers[HttpHeaders.contentTypeHeader] = "application/json";
} catch (e) {
print(e);
}
return data;
return request;
}
@override
Future<ResponseData> interceptResponse({required ResponseData data}) async => data;
Future<BaseResponse> interceptResponse({required BaseResponse response}) async => response;
}
```

- You can also react to and modify specific types of requests and responses, such as `StreamedRequest`,`StreamedResponse`, or `MultipartRequest` :

```dart
class MultipartRequestInterceptor implements InterceptorContract {
@override
Future<BaseRequest> interceptRequest({required BaseRequest request}) async {
if(request is MultipartRequest){
request.fields['app_version'] = await PackageInfo.fromPlatform().version;
}
return request;
}
@override
Future<BaseResponse> interceptResponse({required BaseResponse response}) async {
if(response is StreamedResponse){
response.stream.asBroadcastStream().listen((data){
print(data);
});
}
return response;
}
}
```

Expand Down Expand Up @@ -181,7 +212,7 @@ Sometimes you need to retry a request due to different circumstances, an expired
```dart
class ExpiredTokenRetryPolicy extends RetryPolicy {
@override
Future<bool> shouldAttemptRetryOnResponse(ResponseData response) async {
Future<bool> shouldAttemptRetryOnResponse(BaseResponse response) async {
if (response.statusCode == 401) {
// Perform your token refresh here.
Expand Down
2 changes: 1 addition & 1 deletion example/ios/Flutter/AppFrameworkInfo.plist
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,6 @@
<key>CFBundleVersion</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>8.0</string>
<string>9.0</string>
</dict>
</plist>
4 changes: 2 additions & 2 deletions example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/shared_preferences/ios"

SPEC CHECKSUMS:
Flutter: 434fef37c0980e73bb6479ef766c45957d4b510c
Flutter: 50d75fe2f02b26cc09d224853bb45737f8b3214a
shared_preferences: af6bfa751691cdc24be3045c43ec037377ada40d

PODFILE CHECKSUM: aafe91acc616949ddb318b77800a7f51bffa2a4c

COCOAPODS: 1.10.1
COCOAPODS: 1.11.0
6 changes: 3 additions & 3 deletions example/ios/Runner.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
Expand Down Expand Up @@ -431,7 +431,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
Expand Down Expand Up @@ -480,7 +480,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
Expand Down
70 changes: 39 additions & 31 deletions example/lib/main.dart
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import 'dart:convert';
import 'dart:developer';
import 'dart:io';

import 'package:flutter/material.dart';
import 'package:http/src/base_request.dart';
import 'package:http/src/base_response.dart';
import 'package:http_interceptor/http_interceptor.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'credentials.dart'; // If you are going to run this example you need to replace the key.

import 'cities.dart'; // This is just a List of Maps that contains the suggested cities.
import 'credentials.dart'; // If you are going to run this example you need to replace the key.

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

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

cache.setString(appToken, OPEN_WEATHER_EXPIRED_API_KEY);
cache.setString(kOWApiToken, OPEN_WEATHER_EXPIRED_API_KEY);
}

@override
Expand Down Expand Up @@ -164,7 +168,7 @@ class WeatherSearch extends SearchDelegate<String?> {
builder: (context, snapshot) {
if (snapshot.hasError) {
return Center(
child: Text(snapshot.error as String),
child: Text(snapshot.error?.toString() ?? 'Error'),
);
}

Expand Down Expand Up @@ -274,7 +278,7 @@ class WeatherRepository {
// throw Exception("Error while fetching. \n ${response.body}");
// }
// } catch (e) {
// print(e);
// log(e);
// }
// return parsedWeather;
// }
Expand All @@ -297,7 +301,7 @@ class WeatherRepository {
} on FormatException {
return Future.error('Bad response format 👎');
} on Exception catch (error) {
print(error);
log(error.toString());
return Future.error('Unexpected error 😢');
}

Expand All @@ -307,41 +311,45 @@ class WeatherRepository {

class LoggerInterceptor implements InterceptorContract {
@override
Future<RequestData> interceptRequest({required RequestData data}) async {
print("----- Request -----");
print(data.toString());
return data;
Future<BaseRequest> interceptRequest({required BaseRequest request}) async {
log("----- Request -----");
log(request.toString());
return request;
}

@override
Future<ResponseData> interceptResponse({required ResponseData data}) async {
print("----- Response -----");
print(data.toString());
return data;
Future<BaseResponse> interceptResponse(
{required BaseResponse response}) async {
log("----- Response -----");
log('Err. Code: ${response.statusCode}');
log(response.toString());
return response;
}
}

const String appToken = "TOKEN";
const String kOWApiToken = "TOKEN";

class WeatherApiInterceptor implements InterceptorContract {
@override
Future<RequestData> interceptRequest({required RequestData data}) async {
try {
final cache = await SharedPreferences.getInstance();
Future<BaseRequest> interceptRequest({required BaseRequest request}) async {
final cache = await SharedPreferences.getInstance();

data.params['appid'] = cache.getString(appToken);
data.params['units'] = 'metric';
data.headers[HttpHeaders.contentTypeHeader] = "application/json";
} catch (e) {
print(e);
}
print(data.params);
return data;
final Map<String, String>? headers = Map.from(request.headers);
headers?[HttpHeaders.contentTypeHeader] = "application/json";

return request.copyWith(
url: request.url.addParameters({
'appid': cache.getString(kOWApiToken) ?? '',
'units': 'metric',
}),
headers: headers,
);
}

@override
Future<ResponseData> interceptResponse({required ResponseData data}) async =>
data;
Future<BaseResponse> interceptResponse(
{required BaseResponse response}) async =>
response;
}

class ExpiredTokenRetryPolicy extends RetryPolicy {
Expand All @@ -350,18 +358,18 @@ class ExpiredTokenRetryPolicy extends RetryPolicy {

@override
bool shouldAttemptRetryOnException(Exception reason) {
print(reason);
log(reason.toString());

return false;
}

@override
Future<bool> shouldAttemptRetryOnResponse(ResponseData response) async {
Future<bool> shouldAttemptRetryOnResponse(BaseResponse response) async {
if (response.statusCode == 401) {
print("Retrying request...");
log("Retrying request...");
final cache = await SharedPreferences.getInstance();

cache.setString(appToken, OPEN_WEATHER_API_KEY);
cache.setString(kOWApiToken, OPEN_WEATHER_API_KEY);

return true;
}
Expand Down
Loading

0 comments on commit 135ba3c

Please sign in to comment.