Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”.

The preface

Flutter is a single-threaded framework that uses Future + Dio to implement network encapsulation. It still occupies UI threads and can cause page operations to lag when invoked frequently.

Treatment scheme

Use BasicMessageChannel + Retrofit to encapsulate the native layer network plug-in and enable the child thread to request the network without using the UI thread.

use

Add reference

retrofit_plugin:
    git:
      url: https://github.com/liyufengrex/retrofit_plugin.git
      ref: 0.01.
Copy the code

Network configuration initialization:

void main() {
  NetWorkConfigBuild.init(); ///Initialize the network configurationrunApp(... Omit...). ; }Copy the code
import 'package:retrofit_plugin/retrofit_plugin.dart';

abstract class NetWorkConfigBuild {
  static void init() {
    NetWorkConfigManager.addDefault(NetWorkConfig(
      baseUrl: "http://api.wpbom.com/".// Public header
      headers: {
        "Authorization": "Bearer",
      },
      intercept: _Intercept(),
    ));

    NetWorkConfigManager.addConfig(
        "second",
        NetWorkConfig(
          baseUrl: "https://www.apiopen.top/", headers: {}, intercept: _Intercept(), )); }}///Filter, mainly used for log printing
class _Intercept extends NetworkIntercept {
  @override
  onResponse(Request request, Response response) {
    if (response.success) {
      print("log: ${request.toString()} - ${response.toString()}");
    } else {
      ///System error
      print("log-error: ${request.toString()} - ${response.toString()}"); }}}Copy the code

Each domainKey corresponds to a separate netWorkConfig configuration, and each configuration generates a separate Retrofit request instance. In the preceding example, two network configurations are added. The corresponding domainkeys are default and second respectively.

Creating a network request

Inherits RequestBaseLoader with a generic return value type

import 'package:retrofit_plugin/retrofit_plugin.dart';

class PostRequest extends RequestBaseLoader<String> {
  // The domainKey is overwritten to match the network layer configuration. If the domainKey is not overwritten, the network configuration whose domainKey is default is read by default
  @override
  String get domainKey => "second";

  @override
  Method get method => Method.Post;

  @override
  String get path => "likePoetry";

  @override
  Map<String.dynamic> get data => {
        "name": "Li bai"};@override
  Map<String.String> get header => {};

  @override
  Future<String> execute() {
    return request().then((value) {
      returnvalue.toString(); }); }}Copy the code

Using network request

_post() async {
    //PostRequest is a custom class (need to inherit RequestBaseLoader)
    PostRequest().execute().then((value) {
      setState(() {
        _data = value;
      });
    });
  }
Copy the code

Note: the android manifest file, application label need to add attributes: android: networkSecurityConfig = “@ XML/network_security_config”

Demo has been uploaded. For details, see github.com/liyufengrex… In the example.


Analysis of key code in plug-ins

The native layer does not convert data types and returns strings directly

mRetrofitBuilder.addConverterFactory(ScalarsConverterFactory.create());
mRetrofitBuilder.addConverterFactory(GsonConverterFactory.create());
Copy the code

Retrofit dynamically fills parameters

 @POST
    fun doPost(
        @Url url: String.@HeaderMap headers: Map<String.String>,
        @Body params: Map<String.@JvmSuppressWildcards Any>
    ): Observable<String>

    @GET
    fun doGet(
        @Url url: String.@HeaderMap headers: Map<String.String>,
        @QueryMap(encoded=false) params: Map<String.@JvmSuppressWildcards Any>
    ): Observable<String>

Copy the code

Use BasicMessageChannel for this fragmented and continuous interaction

class RetrofitPlugin : FlutterPlugin.BasicMessageChannel.MessageHandler<Any> {... Override fun onMessage(message: Any? , reply: BasicMessageChannel.Reply<Any> ) { message? .let { val mapParams = itas Map<String, Any>
            val method = mapParams["method"] as String  // separate post get
            val baseUrl = mapParams["baseUrl"] as String // Get the Retrofit instance from baseUrl
            val pathUrl = mapParams["pathUrl"] as String
            val headers = mapParams["headers"]
            val params = mapParams["params"]
            doRequest(
                method = method,
                baseUrl = baseUrl,
                pathUrl = pathUrl,
                headers = headers as Map<String.String>,
                params = params as Map<String, Any>,
                reply = reply
            )
        }
    }

    private fun doRequest(
        method: String,
        baseUrl: String,
        pathUrl: String,
        headers: Map<String.String>,
        params: Map<String, Any>,
        reply: BasicMessageChannel.Reply<Any>
    ) {
        mRequestLoader.doRequest(
            method,
            baseUrl,
            pathUrl,
            headers,
            params,
            object : IRequestResult {
                override fun onSuccess(data: String) {
                    reply.reply(createResult(true, data))
                }

                override fun onError(msg: String) {
                    reply.reply(createResult(false, msg))
                }
            })
    }
    // Return the same format
    private fun createResult(success: Boolean, data: String): HashMap<String, Any> {
        var result = HashMap<String, Any>()
        result["success"] = success
        result["data"] = data
        return result
    }
}
Copy the code

The dart layerchannelencapsulation

class NativeRequestTool {
  // The BasicMessageChannel name should correspond to the native end
  static const BasicMessageChannel _messageChannel =
      const BasicMessageChannel('retrofitRequest', StandardMessageCodec());

  static Future<Response> doRequest(Request param) async {
    // We normalize the parameter type to map
    return_messageChannel .send(param.toJson()) .then((value) => Response.fromNative(value)); }}// We specify the parameter transfer model paradigm
class Request {
  final String method;
  final String baseUrl;
  final String pathUrl;
  final Map<String.String> headers;
  final Map<String.dynamic> params;

  Request({
    this.method,
    this.baseUrl,
    this.pathUrl,
    this.headers,
    this.params,
  }) : assert( baseUrl ! =null, pathUrl ! =null,); HashMap<String.dynamic> toJson() {
    return new HashMap<String.dynamic> ().. ['method'] = method .. ['baseUrl'] = baseUrl .. ['pathUrl'] = pathUrl .. ['headers'] = headers ?? {}
      ..['params'] = params ?? {}; }}// We specify the callback model paradigm
class Response {
  final bool success;
  final String data; //data returns the result of the request

  Response({this.success, this.data});

  factory Response.fromNative(dynamic result) {
    return Response(
      success: result['success'],
      data: result['data']); }}Copy the code

Dart layer Network call layer encapsulation

import 'dart:collection';
import 'dart:convert' as JSON;
import 'channel/message_channel_tool.dart';
import 'retrofit_plugin.dart';

///Call the native network library
abstract class RequestBaseLoader<T> {
  ///Request the path
  String get path;

  /// Post 或者 Get
  Method get method;

  ///NetConfig key
  String get domainKey => null;

  ///Post the participation
  Map<String.dynamic> get data => HashMap();

  ///header
  Map<String.String> get header => HashMap();

  NetWorkConfig _getConfig() {
    return NetWorkConfigManager.getConfig(domainKey: domainKey);
  }

  ///Obtaining domain name Address
  String _getBaseUrl() {
    return _getConfig().baseUrl;
  }

  ///The interceptor
  NetworkIntercept get intercept => _getConfig().intercept;

  ///Get the request header
  Map<String.String> _getHeaders() {
    Map<String.String> headers = _getConfig().headers;
    if (headers == null) {
      headers = Map(a); }if(header ! =null && header.length > 0) {
      header.forEach((key, value) {
        headers.putIfAbsent(key, () => value);
      });
    }
    return headers;
  }

  ///Gets the request pass parameter
  Map<String.dynamic> _getParams() {
    return data ?? {};
  }

  Request _createReq() {
    return Request(
      method: method == Method.Post ? "Post" : "Get",
      baseUrl: _getBaseUrl(),
      pathUrl: path,
      headers: _getHeaders(),
      params: _getParams(),
    );
  }

  ///The request result is MAP
  Future<Map<String.dynamic>> request() async {
    Request _request = _createReq();
    Response response = await NativeRequestTool.doRequest(_request);
    // Add response interceptor
    if(intercept ! =null) {
      intercept.onResponse(_request, response);
    }
    if (response.success) {
      Map<String.dynamic> result;
      try {
        result = JSON.jsonDecode(response.data);
      } catch (_) {
        result = {"response": response.data};
      }
      return result;
    } else {
      throw Exception(response.data);
    }
  }

  Future<T> execute();
}

enum Method {
  Post,
  Get,
}
Copy the code
abstract class NetWorkConfigManager {
  static HashMap<String, NetWorkConfig> _configs = HashMap();

  static void addDefault(NetWorkConfig config) {
    addConfig("default", config);
  }

  static void addConfig(String key, NetWorkConfig config) {
    _configs.putIfAbsent(key, () => config);
  }

  static NetWorkConfig getConfig({String domainKey}) {
    var config = _configs[domainKey];
    return config == null ? _configs["default"] : config; }}class NetWorkConfig {
  final String baseUrl;
  final Map<String.String> headers;
  final NetworkIntercept intercept;

  NetWorkConfig({
    this.baseUrl,
    this.headers,
    this.intercept,
  });
}
Copy the code
///Network request interceptor
abstract class NetworkIntercept {
  onResponse(Request request, Response response);
}
Copy the code

The plugin code has been uploaded: retrofit_plugin