Not long ago I saw the Dio package of Ivy Code, after groping, change it, use it well. Some fixes have been made for some of the invalid articles in the previous Avi Code boss article
Why do we have to wrap it up?
Token interception, error interception, same error handling, same cache, information encapsulation (false, true)
Cookies??? Roll came
Forget cookie, bye
Global initialization, passing in parameters
Dio initialization, passing baseUrl, connectTimeout, receiveTimeout, Options, Header interceptor, etc. Dio initialization allows us to pass in some configuration
Dio initialization configuration
Options, the latest version of Dio already uses requestOptions, merge, now uses copyWith. See below for details
If you want to waste a complete plan
You can refer to the flutter + GetX video app developed by using this solution. Those who have a star can enjoy some star.
Project address Github address apk download Panbaidu extract code: 3EV2
Initialize the
Interceptors can be passed in at initialization or by hand. For example, HERE I define four interceptors. The first one is used to add context-type: json to a global request. The second is the global error-handling interceptor, which is covered below. Cache interceptor, which handles interface cache data globally, retry interceptor (I haven’t used it yet)
class Http {
static final Http _instance = Http._internal();
// The singleton uses the Http class,
factory Http() => _instance;
static late final Dio dio;
CancelToken _cancelToken = new CancelToken();
Http._internal() {
// BaseOptions, Options, and RequestOptions can be configured with increasing priorities, and parameters can be overwritten based on priorities
BaseOptions options = new BaseOptions();
dio = Dio(options);
// Add request interceptor
dio.interceptors.add(RequestInterceptor());
// Add an error interceptor
dio.interceptors.add(ErrorInterceptor());
// // Add cache interceptor
dio.interceptors.add(NetCacheInterceptor());
// // Add a Retry interceptor
dio.interceptors.add(
RetryOnConnectionChangeInterceptor(
requestRetrier: DioConnectivityRequestRetrier(
dio: dio,
connectivity: Connectivity(),
),
),
);
// In debug mode, packet capture is required, so we use proxy and disable HTTPS certificate verification
// if (PROXY_ENABLE) {
// (dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate =
// (client) {
// client.findProxy = (uri) {
// return "PROXY $PROXY_IP:$PROXY_PORT";
/ /};
The // // agent tool will provide a self-signed certificate for packet capture, which will fail certificate verification, so we disable certificate verification
// client.badCertificateCallback =
// (X509Certificate cert, String host, int port) => true;
/ /};
// }
}
///Initialize the public property
///
/// [baseUrl] Indicates the address prefix
/// [connectTimeout] Indicates the connection timeout time
/// [receiveTimeout] Indicates the time limit for receiving a packet
/// [interceptors] The base interceptor
void init({
String? baseUrl,
int connectTimeout = 1500.int receiveTimeout = 1500.Map<String.String>? headers,
List<Interceptor>? interceptors,
}) {
dio.options = dio.options.copyWith(
baseUrl: baseUrl,
connectTimeout: connectTimeout,
receiveTimeout: receiveTimeout,
headers: headers ?? const{});// Interceptors can be passed in when initializing HTTP classes
if(interceptors ! =null && interceptors.isNotEmpty) {
dio.interceptors..addAll(interceptors);
}
}
/ / close the dio
void cancelRequests({required CancelToken token}) {
_cancelToken.cancel("cancelled");
}
// Add authentication
// Read the local configuration
Map<String.dynamic>? getAuthorizationHeader() {
Map<String.dynamic>? headers;
// from getx or sputils
// String accessToken = Global.accessToken;
String accessToken = "";
if(accessToken ! =null) {
headers = {
'Authorization': 'Bearer $accessToken'}; }return headers;
}
Future get(
String path, {
Map<String.dynamic>? params,
Options? options,
CancelToken? cancelToken,
bool refresh = false.boolnoCache = ! CACHE_ENABLE,String? cacheKey,
bool cacheDisk = false,})async {
Options requestOptions = options ?? Options();
requestOptions = requestOptions.copyWith(
extra: {
"refresh": refresh,
"noCache": noCache,
"cacheKey": cacheKey,
"cacheDisk": cacheDisk,
},
);
Map<String.dynamic>? _authorization = getAuthorizationHeader();
if(_authorization ! =null) {
requestOptions = requestOptions.copyWith(headers: _authorization);
}
Response response;
response = await dio.get(
path,
queryParameters: params,
options: requestOptions,
cancelToken: cancelToken ?? _cancelToken,
);
return response.data;
}
Future post(
String path, {
Map<String.dynamic>? params,
data,
Options? options,
CancelToken? cancelToken,
}) async {
Options requestOptions = options ?? Options();
Map<String.dynamic>? _authorization = getAuthorizationHeader();
if(_authorization ! =null) {
requestOptions = requestOptions.copyWith(headers: _authorization);
}
var response = await dio.post(
path,
data: data,
queryParameters: params,
options: requestOptions,
cancelToken: cancelToken ?? _cancelToken,
);
return response.data;
}
Future put(
String path, {
data,
Map<String.dynamic>? params,
Options? options,
CancelToken? cancelToken,
}) async {
Options requestOptions = options ?? Options();
Map<String.dynamic>? _authorization = getAuthorizationHeader();
if(_authorization ! =null) {
requestOptions = requestOptions.copyWith(headers: _authorization);
}
var response = await dio.put(
path,
data: data,
queryParameters: params,
options: requestOptions,
cancelToken: cancelToken ?? _cancelToken,
);
return response.data;
}
Future patch(
String path, {
data,
Map<String.dynamic>? params,
Options? options,
CancelToken? cancelToken,
}) async {
Options requestOptions = options ?? Options();
Map<String.dynamic>? _authorization = getAuthorizationHeader();
if(_authorization ! =null) {
requestOptions = requestOptions.copyWith(headers: _authorization);
}
var response = await dio.patch(
path,
data: data,
queryParameters: params,
options: requestOptions,
cancelToken: cancelToken ?? _cancelToken,
);
return response.data;
}
Future delete(
String path, {
data,
Map<String.dynamic>? params,
Options? options,
CancelToken? cancelToken,
}) async {
Options requestOptions = options ?? Options();
Map<String.dynamic>? _authorization = getAuthorizationHeader();
if(_authorization ! =null) {
requestOptions = requestOptions.copyWith(headers: _authorization);
}
var response = await dio.delete(
path,
data: data,
queryParameters: params,
options: requestOptions,
cancelToken: cancelToken ?? _cancelToken,
);
returnresponse.data; }}Copy the code
Dio interceptor
Let’s take a look at interceptors. Here’s an example of handling interceptors
// Here is an example of a soket error that I wrote separately, since dio does not allow message contents to be modified by default, I can only customize one to use
class MyDioSocketException extends SocketException {
late String message;
MyDioSocketException(
message, {
osError,
address,
port,
}) : super(
message,
osError: osError,
address: address,
port: port,
);
}
/// Error handling interceptor
class ErrorInterceptor extends Interceptor {
// Whether there is a network
Future<bool> isConnected() async {
var connectivityResult = await (Connectivity().checkConnectivity());
returnconnectivityResult ! = ConnectivityResult.none; }@override
Future<void> onError(DioError err, ErrorInterceptorHandler errCb) async {
// Create a custom socket instance, since dio-native instances, message is read-only
// I added it separately, because the default dio Err instance lacks the error message when there is no network
// Here I do the manual processing, to process the hand, the effect, see the picture below, you can see
if (err.error isSocketException) { err.error = MyDioSocketException( err.message, osError: err.error? .osError, address: err.error? .address, port: err.error? .port, ); }// Dio default error instance, if there is no network, can only get an unknown error, can not accurately know whether there is no network
if (err.type == DioErrorType.other) {
bool isConnectNetWork = await isConnected();
if(! isConnectNetWork && err.erroris MyDioSocketException) {
err.error.message = "Current network unavailable, please check your network."; }}// Error unified processing
AppException appException = AppException.create(err);
// Error message
debugPrint('DioError===: ${appException.toString()}');
err.error = appException;
return super.onError(err, errCb); }}Copy the code
As you can see from the above code, the ErrorInterceptor class inherits from the Interceptor class, and can re-state onRequest, onResponse, and onError. Finally, return super.onError passes the err instance to the superclass.
Unified error message packaging handling
Imagine if you had a project that had a dozen or so status codes, and each of them needed to convert the code into a text message, because sometimes you needed to give the user a hint. For example: connection timeout, request failure, network error, etc. The following is uniform error handling
AppException.dart
import 'package:dio/dio.dart';
/// Custom exception
class AppException implements Exception {
final String _message;
final int _code;
AppException(
this._code,
this._message,
);
String toString() {
return "$_code$_message";
}
String getMessage() {
return _message;
}
factory AppException.create(DioError error) {
switch (error.type) {
case DioErrorType.cancel:
{
return BadRequestException(- 1."Request cancellation");
}
case DioErrorType.connectTimeout:
{
return BadRequestException(- 1."Connection timed out");
}
case DioErrorType.sendTimeout:
{
return BadRequestException(- 1."Request timed out");
}
case DioErrorType.receiveTimeout:
{
return BadRequestException(- 1."Response timeout");
}
case DioErrorType.response:
{
try {
int?errCode = error.response! .statusCode;// String errMsg = error.response.statusMessage;
// return ErrorEntity(code: errCode, message: errMsg);
switch (errCode) {
case 400:
{
returnBadRequestException(errCode! ."Request syntax error");
}
case 401:
{
returnUnauthorisedException(errCode! ."No access");
}
case 403:
{
returnUnauthorisedException(errCode! ."Server refused to execute");
}
case 404:
{
returnUnauthorisedException(errCode! ."Unable to connect to server");
}
case 405:
{
returnUnauthorisedException(errCode! ."Request method prohibited");
}
case 500:
{
returnUnauthorisedException(errCode! ."Server internal error");
}
case 502:
{
returnUnauthorisedException(errCode! ."Invalid request");
}
case 503:
{
returnUnauthorisedException(errCode! ."Server is down.");
}
case 505:
{
returnUnauthorisedException(errCode! ."HTTP request not supported");
}
default:
{
// return ErrorEntity(code: errCode, message: "unknown error ");
returnAppException(errCode! , error.response! .statusMessage!) ; }}}on Exception catch (_) {
return AppException(- 1."Unknown error"); }}default:
{
return AppException(- 1, error.error.message); }}}}/// Request error
class BadRequestException extends AppException {
BadRequestException(int code, String message) : super(code, message);
}
/// Unauthenticated exception
class UnauthorisedException extends AppException {
UnauthorisedException(int code, String message) : super(code, message);
}
Copy the code
When you use it, use it like this,
Future<ApiResponse<Feed>> getFeedData(url) async {
try {
dynamic response = await HttpUtils.get(url);
// print(response);
Feed data = Feed.fromJson(response);
return ApiResponse.completed(data);
} on DioError catch (e) {
print(e);
// If there is an error request, use AppException to handle the error object
// After processing, you can, for example, play a toast and prompt the user,
// Popup toast is called in the following method
return ApiResponse.error(e.error);
}
}
Future<void> _refresh() async {
ApiResponse<Feed> swiperResponse = await getFeedData(initPageUrl);
// After processing, we can get two states, status.pleted and status.error
/ / look here
if (swiperResponse.status == Status.COMPLETED) {
// Successful code, do whatever you want
}else if (swiperResponse.status == Status.ERROR) {
// Failed code can be given a toast to the user
// For example, I'm prompting the user here
/ / use the exception! .getMessage(); Get the text message of the error object, is our interceptor after processing prompt text, non-English, get this, prompt to the user is not sweet?? Look at the image effect below
String errMsg = swiperResponse.exception!.getMessage();
publicToast(errMsg);
}
}
Copy the code
The hint here is that the code added to the custom ERR interceptor cannot be supplemented for DIO whether there is no network
Disk cache data, interceptor
Disk cache interface data. First we encapsulate a SpUtil class, sputils. Dart
class SpUtil {
SpUtil._internal();
static final SpUtil _instance = SpUtil._internal();
factory SpUtil() {
return _instance;
}
SharedPreferences? prefs;
Future<void> init() async {
prefs = await SharedPreferences.getInstance();
}
Future<bool> setJSON(String key, dynamic jsonVal) {
String jsonString = jsonEncode(jsonVal);
returnprefs! .setString(key, jsonString); }dynamic getJSON(String key) {
String?jsonString = prefs? .getString(key);return jsonString == null ? null : jsonDecode(jsonString);
}
Future<bool> setBool(String key, bool val) {
returnprefs! .setBool(key, val); }bool? getBool(String key) {
returnprefs! .getBool(key); } Future<bool> remove(String key) {
return prefs!.remove(key);
}
}
Copy the code
Cache interceptor
const int CACHE_MAXAGE = 86400000;
const int CACHE_MAXCOUNT = 1000;
const bool CACHE_ENABLE = false;
class CacheObject {
CacheObject(this.response)
: timeStamp = DateTime.now().millisecondsSinceEpoch;
Response response;
int timeStamp;
@override
bool operator ==(other) {
return response.hashCode == other.hashCode;
}
@override
int get hashCode => response.realUri.hashCode;
}
class NetCacheInterceptor extends Interceptor {
// To ensure that the iterator order is consistent with the object insertion time order, we use LinkedHashMap
var cache = LinkedHashMap<String, CacheObject>();
@override
void onRequest(
RequestOptions options,
RequestInterceptorHandler requestCb,
) async {
if(! CACHE_ENABLE) {return super.onRequest(options, requestCb);
}
// refresh indicates whether the cache is refreshed
bool refresh = options.extra["refresh"] = =true;
// Whether to cache the disk
bool cacheDisk = options.extra["cacheDisk"] = =true;
// If refresh, delete related cache first
if (refresh) {
// Delete the memory cache with the same URI
delete(options.uri.toString());
// Delete the disk cache
if (cacheDisk) {
await SpUtil().remove(options.uri.toString());
}
return;
}
// get requests to enable caching
if (options.extra["noCache"] != true &&
options.method.toLowerCase() == 'get') {
String key = options.extra["cacheKey"]???? options.uri.toString();// Strategy 1 memory cache first, 2 disk cache second
// 1 Memory cache
var ob = cache[key];
if(ob ! =null) {
// If the cache is not expired, the cache contents are returned
if ((DateTime.now().millisecondsSinceEpoch - ob.timeStamp) / 1000 <
CACHE_MAXAGE) {
return;
} else {
// If it has expired, delete the cache and continue to request the servercache.remove(key); }}// 2 Disk cache
if (cacheDisk) {
var cacheData = SpUtil().getJSON(key);
if(cacheData ! =null) {
return; }}}return super.onRequest(options, requestCb);
}
@override
void onResponse(
Response response, ResponseInterceptorHandler responseCb) async {
// If caching is enabled, the returned result is saved to the cache
if (CACHE_ENABLE) {
await _saveCache(response);
}
return super.onResponse(response, responseCb);
}
Future<void> _saveCache(Response object) async {
RequestOptions options = object.requestOptions;
// Only get requests are cached
if (options.extra["noCache"] != true &&
options.method.toLowerCase() == "get") {
// Policy: write cache for both memory and disk
/ / the cache key
String key = options.extra["cacheKey"]???? options.uri.toString();// Disk cache
if (options.extra["cacheDisk"] = =true) {
await SpUtil().setJSON(key, object.data);
}
// Memory cache
// If the number of caches exceeds the maximum number, the earliest record is removed first
if(cache.length == CACHE_MAXCOUNT) { cache.remove(cache[cache.keys.first]); } cache[key] = CacheObject(object); }}void delete(Stringkey) { cache.remove(key); }}Copy the code
Began to encapsulate
class HttpUtils {
static void init({
required String baseUrl,
int connectTimeout = 1500.int receiveTimeout = 1500.List<Interceptor>? interceptors,
}) {
Http().init(
baseUrl: baseUrl,
connectTimeout: connectTimeout,
receiveTimeout: receiveTimeout,
interceptors: interceptors,
);
}
static void cancelRequests({required CancelToken token}) {
Http().cancelRequests(token: token);
}
static Future get(
String path, {
Map<String.dynamic>? params,
Options? options,
CancelToken? cancelToken,
bool refresh = false.boolnoCache = ! CACHE_ENABLE,String? cacheKey,
bool cacheDisk = false,})async {
return await Http().get(
path,
params: params,
options: options,
cancelToken: cancelToken,
refresh: refresh,
noCache: noCache,
cacheKey: cacheKey,
);
}
static Future post(
String path, {
data,
Map<String.dynamic>? params,
Options? options,
CancelToken? cancelToken,
}) async {
return await Http().post(
path,
data: data,
params: params,
options: options,
cancelToken: cancelToken,
);
}
static Future put(
String path, {
data,
Map<String.dynamic>? params,
Options? options,
CancelToken? cancelToken,
}) async {
return await Http().put(
path,
data: data,
params: params,
options: options,
cancelToken: cancelToken,
);
}
static Future patch(
String path, {
data,
Map<String.dynamic>? params,
Options? options,
CancelToken? cancelToken,
}) async {
return await Http().patch(
path,
data: data,
params: params,
options: options,
cancelToken: cancelToken,
);
}
static Future delete(
String path, {
data,
Map<String.dynamic>? params,
Options? options,
CancelToken? cancelToken,
}) async {
return awaitHttp().delete( path, data: data, params: params, options: options, cancelToken: cancelToken, ); }}Copy the code
Inject, initialize
The main. The dart. Here is my personal example
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
// debugPaintSizeEnabled = true;
await initStore();
runApp(MyApp());
}
Future<void> initStore() async {
// Initialize the local storage class
await SpUtil().init();
// Initialize the Request class
HttpUtils.init(
baseUrl: Api.baseUrl,
);
// Global getx global injection,
await Get.putAsync(() => HistoryService().init());
print("Global injection");
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return GetMaterialApp(
debugShowCheckedModeBanner: false, initialRoute: PageRoutes.INIT_ROUTER, getPages: PageRoutes.routes, ); }}Copy the code
Use encapsulated examples
// Define a function that returns the future apiResponse to get the status of status
Future<ApiResponse<Feed>> getFeedData(url) async {
try {
dynamic response = await HttpUtils.get(url);
// print(response);
Feed data = Feed.fromJson(response);
return ApiResponse.completed(data);
} on DioError catch (e) {
print(e);
return ApiResponse.error(e.error);
}
}
Future<void> _refresh() async {
ApiResponse<Feed> swiperResponse = await getFeedData(initPageUrl);
if(! mounted) {return;
}
// Use status.pleted to determine success
if(swiperResponse.status == Status.COMPLETED) { setState(() { nextPageUrl = swiperResponse.data! .nextPageUrl; _swiperList = []; _swiperList.addAll(swiperResponse.data! .issueList! [0]! .itemList!) ; _itemList = []; });// Pull new, list
await _loading();
// Use status.ERROR to check for failure
} else if (swiperResponse.status == Status.ERROR) {
setState(() {
stateCode = 2;
});
// Error, we can call getMessage() to get the error message. Prompt to users (Friendly prompt after Chinese translation)
StringerrMsg = swiperResponse.exception! .getMessage(); publicToast(errMsg);print("Error occurred at position home bottomBar1 swiper, URL:${initPageUrl}");
print(swiperResponse.exception); }}Copy the code
If you want to waste a complete plan
You can refer to the flutter + GetX video app developed by using this solution. Those who have a star can enjoy some star.
Project address Github address apk download Panbaidu extract code: 3EV2