Flutter 从0搭建一个完整的项目(一、网络请求)

一、核心理念

化繁为简,最终使用时,简单易用,扩展性强即可。

二、网络请求,基于Dio的封装

dio 官网地址

先看最终使用事例(1.定义返回的实体类 2.发起网络请求)

// 1. 返回实体类 UserBean 的定义(根据Json 用开发工具生成即可)
class UserBean implements BaseHttpBean {
  UserBean({
    this.token,
    this.userId,
  });
  
  String? token;
  String? userId;
  
  //  解析数据
  UserBean.fromJson(dynamic json) {
    token = json['token'];
    user_id = json['userId']?.toString();
  }

  // 这个方法是 实现父类BaseHttpBean 的方法,下面会详解
  @override
  UserBean fromJson(dynamic json) {
    return UserBean.fromJson(json);
  }
}
// 2. 登录网络请求--事例
void login(){
    AppRequest.login("account","password", cancelToken).then((UserBean bean) {
      // 成功处理 ...
    }).catchError((e) {
      // 异常处理 ...
    });
}

三、网络封装具体过程

1.接口定义
/// 网络请求 最常用的定义 实际项目自己添加或者修改
class BaseHttp {
  /// BaseUrl
  String baseUrl() {
    return "";
  }

  /// 固定的请求头(所有接口只走一次)
  Map<String, dynamic>? baseHeaders() {
    return null;
  }

  /// 特有的请求头(每个接口都会走)
  Map<String, dynamic>? headers() {
    return null;
  }

  /// 超时时间
  Duration timeout() {
    return const Duration(seconds: 45);
  }

  /// 连接超时时间
  Duration connectTimeout() {
    return const Duration(seconds: 3);
  }

  /// 数据转换 - 扩展用(比如数据解密)
  dynamic convert(dynamic json) {
    return json;
  }
}
2. DIO 实现基本的网络请求
/// 根据Dio的基本封装
class BaseDio extends BaseHttp {
  late Dio _dio;
  // 配置信息
  late BaseOptions baseOptions;
  // 是否初始化
  bool _isInit = false;

  /// 初始化构造
  BaseDio.init() {
    if (_isInit) {
      return;
    }
    baseOptions = BaseOptions(
      baseUrl: baseUrl(),
      headers: baseHeaders(),
      connectTimeout: connectTimeout(),
    );
    _dio = Dio(baseOptions);
    _isInit = true;
  }

  /// 设置BaseURL 根据实际情况修改 如果固定 就复写 baseUrl()
  void setBaseUrl(String url) {
    if (_isInit) {
      baseOptions.baseUrl = url;
    }
  }

  /// 配置信息,子类可以根据实际覆写
  Options options() {
    final options = Options(
      headers: headers(),
    );
    return options;
  }

  /// 核心方法:网络请求统一实现
  Future<T> request<T>(
    String path, {
    String method = "GET",
    Map<String, dynamic>? params,
    CancelToken? cancelToken,
    ProgressCallback? onSendProgress,
    ProgressCallback? onReceiveProgress,
  }) async {
    // 参数配置
    var option = options();
    if (method == "FORM") {
      option.method = "POST";
    } else {
      option.method = method;
    }
    // 请求参数
    Map<String, dynamic>? queryParameters;
    // 表单请求
    Object? data;
    if (method == "GET") {
      queryParameters = params;
    } else {
      // POST FORM
      if (params != null) {
        if (method == "POST") {
          data = params;
        } else {
          data = FormData.fromMap(params);
        }
      }
    }
    // dio 发起网络请求
    var response = await _dio.request(
      path,
      data: data,
      queryParameters: queryParameters,
      cancelToken: cancelToken,
      options: option,
      onSendProgress: onSendProgress,
      onReceiveProgress: onReceiveProgress,
    );
    if (response.statusCode == 200) {
      // 返回请求成功的结果
      return Future.value(response.data);
    }
    throw Exception("网络请求出错:${response.statusCode}");
  }
}
3.基本数据类的定义
/// 1.网络请求 数据解析定义
abstract class BaseHttpBean {
  dynamic fromJson(dynamic json);
}

/// 2. 网络请求处理 根据实际项目 自己统一封装的类
class HttpBean<T extends BaseHttpBean, K> {
  HttpBean({
    this.code,
    this.msg,
    this.data,
  });

  int? code; // 响应码
  String? msg; // 响应的信息
  K? data; // 响应的数据 可能是:{} 或者 []

  /// 是否成功
  bool success() {
    return code == 0 || code == 1;
  }

  HttpBean.fromJson(dynamic json, T type) {
    code = json['code'] as int?;
    msg = json['msg'];
    // 解析data
    var result = json['data'];
    // List 处理
    if (result is List) {
      this.data = List<T>.from(result.map((item) => type.fromJson(item)).toList()) as K?;
    } else {
      this.data = type.fromJson(result);
    }
  }

  Map<String, dynamic> toJson() {
    final map = <String, dynamic>{};
    map['code'] = code;
    map['msg'] = msg;
    map['data'] = data;
    return map;
  }
}
4.网络请求,实际项目客户端请求封装(Get Post Form),上传下载这些可以自己扩展
/// 网络请求,实际项目客户端请求封装(Get Post Form)
class BaseClient extends BaseDio {
  BaseClient.init() : super.init();

  /// 核心方法 -- 数据统一处理
  Future<K> _requestData<T extends BaseHttpBean, K>(
    String path, // 路径
    T type, // 实际解析类
    {
    String method = "GET", // 请求方式
    Map<String, dynamic>? params, // 请求参数
    CancelToken? cancelToken, // 取消的Token
    ProgressCallback? onSendProgress, // 扩展操作用
    ProgressCallback? onReceiveProgress, // 扩展操作用
  }) async {
    // 1. 请求数据
    var data = await request(
      path,
      method: method,
      params: params,
      cancelToken: cancelToken,
      onSendProgress: onSendProgress,
      onReceiveProgress: onReceiveProgress,
    );
    // 2. 根据实际 转换数据,默认就是返回 data原有数据
    var json = convert(data);
    // 测试模式 打印LOG
    if (kDebugMode) {
      print("url==>${baseOptions.baseUrl}$path");
      print("header==>${jsonEncode(params)}");
      print("result==>${jsonEncode(json)}");
    }
    // 3. 解析数据
    var bean = HttpBean.fromJson(json, type);
    if (bean.success()) {
      // 4. 返回最终结果
      return Future.value(bean.data as K);
    }
    throw Exception("数据解析出错 请检查实体Bean");
  }

  /// Get 请求
  Future<K> get<T extends BaseHttpBean, K>(
    String path,
    T type, {
    Map<String, dynamic>? params,
    CancelToken? cancelToken,
    ProgressCallback? onSendProgress,
    ProgressCallback? onReceiveProgress,
  }) async {
    return _requestData(
      path,
      type,
      method: "GET",
      params: params,
      cancelToken: cancelToken,
      onSendProgress: onSendProgress,
      onReceiveProgress: onReceiveProgress,
    );
  }

  /// Post请求
  Future<K> post<T extends BaseHttpBean, K>(
    String path,
    T type, {
    Map<String, dynamic>? params,
    CancelToken? cancelToken,
    ProgressCallback? onSendProgress,
    ProgressCallback? onReceiveProgress,
  }) async {
    return _requestData(
      path,
      type,
      method: "POST",
      params: params,
      cancelToken: cancelToken,
      onSendProgress: onSendProgress,
      onReceiveProgress: onReceiveProgress,
    );
  }

  /// 表单请求
  Future<K> form<T extends BaseHttpBean, K>(
    String path,
    T type, {
    Map<String, dynamic>? params,
    CancelToken? cancelToken,
    ProgressCallback? onSendProgress,
    ProgressCallback? onReceiveProgress,
  }) async {
    return _requestData(
      path,
      type,
      method: "FORM",
      params: params,
      cancelToken: cancelToken,
      onSendProgress: onSendProgress,
      onReceiveProgress: onReceiveProgress,
    );
  }
}
5.项目最终使用
5.1 项目网络的最终封装
/// 客户端网络请求
class AppClient extends BaseClient {
  AppClient.init() : super.init();

  /// 请求头
  @override
  Map<String, dynamic>? headers() {
    Map<String, dynamic> header = {};
    header["deviceid"] = "AAABBBCCC123456XXX";
    // ...
    return header;
  }

  /// 根据实际情况处理数据,比如解密操作
  @override
  dynamic convert(json) {
    return json;
  }
}
5.2 接口统一管理类(项目接口统一定义的地方)
/// 1. 接口统一管理类
class AppRequest {
  static final _client = AppClient.init();

  /// 设置BaseUrl
  static void setBaseUrl(String url) {
    _client.setBaseUrl(url);
  }

  /// 登录接口 (UserBean在下面最终使用里会看到)
  static Future<UserBean> login(var account,var password, {CancelToken? cancelToken}) {
    var path = "/login";
    Map<String, dynamic> params = {};
    params["account"] = account;
    params["password"] = password;
    return _client.post(path, UserBean(),
        cancelToken: cancelToken, params: params);
  }

  /// 系统配置接口
  static Future<SystemBean> systemConfig({CancelToken? cancelToken}) {
    var path = "/system/config";
    return _client.post(path, SystemBean(), cancelToken: cancelToken);
  }
}
5.3 最终使用事例(1.定义返回的实体类 2.发起网络请求)
// 1. 返回实体类 UserBean 的定义(根据Json 用开发工具生成即可)
class UserBean implements BaseHttpBean {
  UserBean({
    this.token,
    this.userId,
  });
  
  String? token;
  String? userId;
  
  //  解析数据
  UserBean.fromJson(dynamic json) {
    token = json['token'];
    user_id = json['userId']?.toString();
  }

  // 这个方法是 实现父类BaseHttpBean 的方法,下面会详解
  @override
  UserBean fromJson(dynamic json) {
    return UserBean.fromJson(json);
  }
}
// 2. 登录网络请求--事例
void login(){
    AppRequest.login("account","password", cancelToken).then((UserBean bean) {
      // 成功处理 ...
    }).catchError((e) {
      // 异常处理 ...
    });
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,372评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,368评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,415评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,157评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,171评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,125评论 1 297
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,028评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,887评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,310评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,533评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,690评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,411评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,004评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,659评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,812评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,693评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,577评论 2 353

推荐阅读更多精彩内容