Flutter☞网络封装

第一章 学习指南

1、AS 快捷键

  快捷创建Widget: 在dart文件中输入stf或stl出现提示后按回车即可
  快捷修复: option + 回车
  自动生成构造函数: 选中 final 参数, 快捷键: option + 回车
  添加父组件、变为子组件、删除子组件: option + 回车
  万能的搜索: 双击shift
  查看最近打开的文件: command + E
  重命名: fn + shift + f6
  查看当前类结构: command + fn + f12
  查看源码: 将光标放到要查看源码的类名或方法名上, 长按command然后的点击
  查看类的子类: 选中要查看的类, 然后: command + B 或 option + command + B
  将代码更新到模拟器上: 选中模拟器然后 command + R
  导入类的快捷键: 将光标放在要导入类的上面, 然后按 option + enter
  前进后退: 当跟踪代码的时候, 经常跳转到其他类, 后退快捷键: option + command+ 方向左键, 前进快捷键: option + command+ 方向右键
  全局搜索: command + shift + F
  全局替换: command + shift + R
  查找引用: option + shift + F7
Cmd + Option + L : 代码格式化。
Cmd + “-” 或者 “+” :收起和展开代码。
Cmd + Shift + “-” 或者 “+” : 收起和展开全部代码。
Cmd + Shift + Enter :代码快速补全。
Cmd + F12 :快速查看当前文件所有方法。
Cmd + Shift + F :全局搜索。
Cmd + Shift + R :全局替换。
Cmd + F :当前文件搜索。
Cmd + R :当前文件替换。
Cmd + Option + M :方法抽离或重构。
Cmd + Option + V : 抽离局部变量。
Cmd + Option + F :抽离成员变量。
Cmd + O :全局搜索类/文件/关键字/(包括系统类,自定义类)。
Cmd + Option + W :抽取代码为单独的方法,或者抽取成get方法。
Cmd + Option + E :创建文件,抽取代码为单独的方法,或者抽取成get方法。
Cmd + Option + B :查看抽象类的实现。
Cmd + D :复制单行。
Cmd+ Delete :删除行。
Cmd + \ :热重载(hot reload) 。
Cmd + Option + \ :热重启(hot restart)。
Cmd+ Shift + Enter :if后面自动加(){ }。
Cmd+ J :快速生成模版代码块。
Ctl+ R :运行项目。
Option + Enter :在widget包裹一个新的widget。
Option + Enter :自动修正错误(但是要把光标移动到错误上面)。
Option + Enter :将StatelessWidget转 StatefulWidget。
Option + Up :选择这个widget。
Option + Ctl + O :清除无效包引用。
Option + Ctl + I :自动缩进对齐/代码对齐。
Option + Shift + Up/Down :上下移动代码。
Option + 双击 Up :选择区域。
选中代码 + tab :选中代码缩进。
选中代码 + shift + tab :选中代码缩进。

2、自动补全

可以在Android Studio的Plugin中搜素 Flutter Snippets这个插件然后进行安装

3、代码提取

选中代码-->右键选择"Refactor"-->Extract Method...(提取成方法)

4、代码自动格式化

虽然我们可以通过快捷键option(alt)+command(ctrl)+L来在提交代码时格式化, 但是这种手动方式显然还不够效率. 下面我们来借助AS的保存时代码自动格式化功能来释放双手: 在Settings-->Language&Frameworks-->Flutter中选上"Format Code on Save"这个选项. (保存代码时自动格式化)

第二章 Flutter网络和数据存储框架搭建

1、 Flutter网络架构架构设计

支持网络库插拔设计, 且不干扰业务层
简洁易用, 支持配置来进行请求
Adapter设计, 扩展性强
统一异常和返回处理
  
https://github.com/szy0syz/flutter_bilibili
HitNet
Dao层 首页、详情、点赞、收藏、登录、注册、通知、个人中心、排行...
REST ful支持 GET、POST、DELETE
统一逻辑处理 Request Processing、Response Processing、Error Processing
适配层 Dio Adapter、Http Adapter、Mock Adapter
三方库 dio、http、Mock

2、基于配置的请求封装与hinet架构搭建

在lib目录下, 新建http目录; 在http目录下分别创建core和request目录; 在request目录下新建"base_request.dart"文件, 在base_request.dart中:

enum HttpMethod { GET, POST, DELETE }

///抽象类封装--基础请求
abstract class BaseRequest {
  // curl -X GET "http://api.devio.org/uapi/test/test?requestPrams=11" -H "accept: */*"
  // curl -X GET "https://api.devio.org/uapi/test/test/1
  var pathParams;
  var useHttps = true; // 用于切换http和https

  String authority() {//设置域名
    return "api.devio.org";
  }

  HttpMethod httpMethod();

  String path();

  String url() {
    Uri uri;
    var pathStr = path();
    //拼接path参数
    if (pathParams != null) {
      if (path().endsWith("/")) {
        pathStr = "${path()}$pathParams";
      } else {
        pathStr = "${path()}/$pathParams";
      }
    }
    //http和https切换
    if (useHttps) {
      uri = Uri.https(authority(), pathStr, params);
    } else {
      uri = Uri.http(authority(), pathStr, params);
    }
    if (needLogin()) {
      //给需要登录的接口携带登录令牌
      addHeader(LoginDao.BOARDING_PASS, LoginDao.getBoardingPass());
    }
    print('url:${uri.toString()}');
    return uri.toString();
  }

  bool needLogin();// 用于判断是否需要登录

  Map<String, String> params = Map();

  ///添加参数
  BaseRequest add(String k, Object v) {
    params[k] = v.toString();
    return this;
  }

  Map<String, dynamic> header = {
    HiConstants.authTokenK: HiConstants.authTokenV,
      HiConstants.courseFlagK: HiConstants.courseFlagV,
    // 'course-flag': 'fa',
    // "auth-token": "ZmEtMjAyMS0wNC0xMiAyMToyMjoyMC1mYQ==fa",
  };

  ///添加header
  BaseRequest addHeader(String k, Object v) {
    header[k] = v.toString();
    return this;
  }
}

3、基于配置的请求封装与hinet架构搭建-2

在http目录-->request目录--> 新建 test_request.dart 文件, 用于测试接口:

import 'package:flutter_bilibili/http/request/base_request.dart';

//测试排程类
class TestRequest extends BaseRequest {
  @override
  HttpMethod httpMethod() {
    return HttpMethod.GET;
  }

  @override
  bool needLogin() {
    return false;
  }

  @override
  String path() {
    return 'uapi/test/test';
  }
}

在http目录-->core目录-->新建 hi_net.dart 文件:

import 'package:flutter_bilibili/http/core/dio_adapter.dart';

import '../request/base_request.dart';
import 'hi_error.dart';
import 'hi_net_adapter.dart';

///1.支持网络库插拔设计,且不干扰业务层
///2.基于配置请求请求,简洁易用
///3.Adapter设计,扩展性强
///4.统一异常和返回处理
class HiNet {
  HiNet._();
  static HiNet? _instance;//创建单例

  static HiNet getInstance() {//静态方法获取单例
    if (_instance == null) {
      _instance = HiNet._();
    }
    return _instance!;
  }

  // 获取请求结果
  Future fire(BaseRequest request) async {
    HiNetResponse? response;
    var error;

    try {
      response = await send(request);
    } on HiNetError catch (e) {
      error = e;
      response = e.data;
      printLog(e.message);
    } catch (e) {
      //其它异常
      error = e;
      printLog(e);
    }

    if (response == null) {
      printLog(error);
    }

    var result = response?.data;
    printLog(result);

    var status = response?.statusCode;
    switch (status) {
      case 200:
        return result;
      case 401:
        throw NeedLogin();
      case 403:
        throw NeedAuth(result.toString(), data: result);
      default:
        throw HiNetError(status ?? -1, result.toString(), data: result);
    }
  }

  //发送请求
  Future<HiNetResponse<T>> send<T>(BaseRequest request) async {
    ///使用Dio发送请求
    HiNetAdapter adapter = DioAdapter();
    return adapter.send(request);
  }

  void printLog(log) {//用于测试打印
    print('hi_net:' + log.toString());
  }
}

在随便一个文件内, 构建测试请求:

void _incrementCounter() async {
  TestRequest request = TestRequest();
  request.add("aa", "ddd").add("bb", "333");
  var result = await HiNet.getInstance().fire(request);
  print(result);
}

4、 hinet统一异常和返回处理与Adapter模式设计

在http-->core-->新建 hi_error.dart, 用来统一处理异常:

///需要登录的异常
class NeedLogin extends HiNetError {
  NeedLogin({int code: 401, String message: '请先登录'}) : super(code, message);
}

///需要授权的异常
class NeedAuth extends HiNetError {
  NeedAuth(String message, {int code: 403, dynamic data})
      : super(code, message, data: data);
}

///网络异常统一格式类
class HiNetError implements Exception {
  final int code;
  final String message;
  final dynamic data;

  HiNetError(this.code, this.message, {this.data});
}

在http-->core-->新建 hi_net_adapter.dart, 用来统一处理网络层返回格式:

import 'dart:convert';

import 'package:flutter_bilibili/http/request/base_request.dart';

///网络请求抽象类
abstract class HiNetAdapter {
  Future<HiNetResponse<T>> send<T>(BaseRequest request);
}

/// 统一网络层返回格式
class HiNetResponse<T> {
  // 构造方法: 非必填
  HiNetResponse({
    this.data,
    required this.request,
    this.statusCode,
    this.statusMessage,
    this.extra,
  });

  /// Response body. may have been transformed, please refer to [ResponseType].
  T? data;

  /// The corresponding request info.
  BaseRequest request;

  /// Http status code.
  int? statusCode;

  /// Returns the reason phrase associated with the status code.
  /// The reason phrase must be set before the body is written
  /// to. Setting the reason phrase after writing to the body.
  String? statusMessage;

  /// Custom field that you can retrieve it later in `then`.
  late dynamic extra;

  /// We are more concerned about `data` field.
  @override
  String toString() {
    if (data is Map) {
      return json.encode(data);
    }
    return data.toString();
  }
}

在http-->core-->新建 mock_adapter.dart, 用来新建测试适配器:

import 'package:flutter_bilibili/http/core/hi_net_adapter.dart';
import 'package:flutter_bilibili/http/request/base_request.dart';

///测试适配器,mock数据
class MockAdapter extends HiNetAdapter {
  @override
  Future<HiNetResponse<T>> send<T>(BaseRequest request) async {
    return Future.delayed(Duration(milliseconds: 1000), () {
      return HiNetResponse(
          request: request,
          data: {"code": 0, "message": 'success'} as T,
          statusCode: 403);
    });
  }
}

在hi_net.dart中使用:

    //发送请求
  Future<HiNetResponse<T>> send<T>(BaseRequest request) async {
    ///使用Dio发送请求
    HiNetAdapter adapter = MockAdapter();
    return adapter.send(request);
  }

5、扩展hinet添加对dio的支持

在pubspec.yaml中导入dio三方库:

dependencies:
  flutter:
    sdk: flutter

  ...
  cupertino_icons: ^1.0.2
  dio: ^4.0.0

在http-->core-->新建 dio_adapter.dart ,用于适配Dio:

import 'package:dio/dio.dart';
import 'package:flutter_bilibili/http/core/hi_error.dart';
import 'package:flutter_bilibili/http/core/hi_net_adapter.dart';
import 'package:flutter_bilibili/http/request/base_request.dart';

///Dio适配器
class DioAdapter extends HiNetAdapter {
  @override
  Future<HiNetResponse<T>> send<T>(BaseRequest request) async {
    // 搞两个变量,一个没初始化赋值,一个初始化赋值
    // options 是Dio的可选参数
    var response, options = Options(headers: request.header);
    var error;
    try { //发送网络请求
      if (request.httpMethod() == HttpMethod.GET) {
        response = await Dio().get(request.url(), options: options);
      } else if (request.httpMethod() == HttpMethod.POST) {
        response = await Dio()
            .post(request.url(), data: request.params, options: options);
      } else if (request.httpMethod() == HttpMethod.DELETE) {
        response = await Dio()
            .delete(request.url(), data: request.params, options: options);
      }
    } on DioError catch (e) {
      error = e;
      response = e.response;
    }
    if (error != null) {
      ///抛出HiNetError
      throw HiNetError(response?.statusCode ?? -1, error.toString(),
          data: await buildRes(response, request));
    }
    return buildRes(response, request);
  }

  ///构建HiNetResponse
  Future<HiNetResponse<T>> buildRes<T>(
      Response? response, BaseRequest request) {
    return Future.value(HiNetResponse(
        //?.防止response为空
        data: response?.data,
        request: request,
        statusCode: response?.statusCode,
        statusMessage: response?.statusMessage,
        extra: response));
  }
}

6、Dart JSON编码器和解码器剖析

void test(){
  const jsonString = "{\"name\":\"flutter\",}"
  //json 转map
  Map<String, dynamic> jsonMap = jsonDecode(jsonString);
  print('name:${jsonMap['name']}');
  
  //map 转JSON
  String json = jsonEncode(jsonMap);
  print('json:$json');
}

7、三种JSON解析技巧带你解放生产力

Step1: 安装插件

dependencies:
  ...
  dio: ^4.0.0
  json_annotation: ^4.0.1
  
dev_dependencies:
  ...
  json_serializable: ^4.1.3
  build_runner: ^2.0.4

Step2: 配置实体类

示例: 比如在result.dart中:
// result.g.dart 将在我们运行生成命令后自动生成
import 'package:json_annotation/json_annotation.dart';

part 'result.g.dart'; //运行命令后自动生成的文件名

@JsonSerializable()
class Result {
  //定义字段
  int code;
  String method;
  String requestPrams;

  //构造方法
  Result(this.code, this.method, this.requestPrams);

  //固定格式,不同的类使用不同的mixin即可
  factory Result.fromJson(Map<String, dynamic> json) => _$ResultFromJson(json);

  //固定格式,
  Map<String, dynamic> toJson() => _$ResultToJson(this);
}

因为实体类的生成代码还不存在, 所以上面代码会提示一些错误是正常现象

Step3 : 执行build生成实体类

$ flutter packages pub run build_runner build

11 统一缓存管理框架hicache设计

添加依赖插件: shared_preferences

shared_preferences: ^0.5.12+4

hi_cache封装

import 'package:shared_preferences/shared_preferences.dart';

///缓存管理类
class HiCache {
  SharedPreferences? prefs;

  HiCache._() {
    init();
  }

  static HiCache? _instance;

  HiCache._pre(SharedPreferences prefs) {
    this.prefs = prefs;
  }

  ///预初始化,防止在使用get时,prefs还未完成初始化
  static Future<HiCache> preInit() async {
    if (_instance == null) {
      var prefs = await SharedPreferences.getInstance();
      _instance = HiCache._pre(prefs);
    }
    return _instance!;
  }

  static HiCache getInstance() {
    if (_instance == null) {
      _instance = HiCache._();
    }
    return _instance!;
  }

  //初始化
  void init() async {
    if (prefs == null) {
      prefs = await SharedPreferences.getInstance();
    }
  }

  setString(String key, String value) {
    prefs?.setString(key, value);
  }

  setDouble(String key, double value) {
    prefs?.setDouble(key, value);
  }

  setInt(String key, int value) {
    prefs?.setInt(key, value);
  }

  setBool(String key, bool value) {
    prefs?.setBool(key, value);
  }

  setStringList(String key, List<String> value) {
    prefs?.setStringList(key, value);
  }

  remove(String key) {
    prefs?.remove(key);
  }

  // 通过泛型的方式来获取数据, 提高复用性
  T? get<T>(String key) {
    var result = prefs?.get(key);
    if (result != null) {
      return result as T;
    }
    return null;
  }
}

测试使用封装的hi_cache

在main.dart中:
class _MyHomePageState extends State<MyHomePage>{
  @voerride
  void initState() {
    super.initState();
    
    HiCache.preInit(); //初始化
  }
}

/// 保存与读取本地信息
void test(){
  HiCache.getInstance().setString("aa","1234");
  var value = HiCache.getInstance().get("aa");
  print('value:$value')
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,219评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,363评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,933评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,020评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,400评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,640评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,896评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,597评论 0 199
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,327评论 1 244
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,581评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,072评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,399评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,054评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,083评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,849评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,672评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,585评论 2 270

推荐阅读更多精彩内容