一步一步搭建Flutter开发架子-网络请求,异步UI更新封装

1,481 阅读3分钟

接上文,这篇文章主要介绍Flutter网络请求, 页面不同状态展示的封装

网络请求

主要使用的是dio库进行网络请求,引入的第三个库。对于Flutter的自带的HttpClient也可以。用习惯了dio还是放不下。或者有兴趣的可以自己对于HttpClient封装一下

引入dio

在pubspec.yaml文件中加入

dio: ^3.0.10

引入头文件方式

import 'package:dio/dio.dart';

简单封装使用dio

先分析一下使用场景,比较常见的场景是用户登录之后获取到token之后,设置在请求头中进行请求获取数据。接下来沿着这个思路设计一下。 新建一个base.dart文件

import 'package:dio/dio.dart';

Dio initDio() {
  BaseOptions _baseOptions = BaseOptions(
      baseUrl: '设置服务器地址', connectTimeout: 15000);
  Dio dio = Dio(_baseOptions);
  dio.interceptors
      .add(InterceptorsWrapper(onRequest: (RequestOptions options) async {
    print('走网络请求了设置头部了');
    options.queryParameters['Authorization'] =
        SpUtil.getString('token', defValue: '');
    return options;
  }));
  return dio;
}
class Api {
  static Dio dio = initDio();

  static Future<Map> requestApi(method, path, data) async {
    Response result;
    try {
      if (method == 'get') {
        result = await dio.get(path, queryParameters: data);
      } else if (method == 'post') {
        result = await dio.post(path, data: data);
      }
      print(result);
      return result.data;
    } on DioError catch (e) {
      print(e);
      if (e == null) {
        return Future.error(Response(data: -1));
      } else if (e.response != null) {
        if (e.response.statusCode >= 300 && e.response.statusCode < 400) {
          return Future.error(Response(data: -1));
        } else {
          result = e.response;
          return Future.value(result.data);
        }
      } else {
        return Future.error(Response(data: -1));
      }
    } finally {
      print('网络请求结束');
    }
  }
}

以上初始化了dio。具体的请求我们可以再创建文件。放在一起比较乱。比如首页的请求放在一起,我的页面的请求放在一起。应该能理解哈

class ReqHome {
  Future<Map> loginInApi(username, password) {
    return Api.requestApi('post', '/login',
        {"username": username, "password": password});
  }
  static Future<Map> getSwiperApi() {
    return Api.requestApi('get', '地址', {});
  }
}

下面就是调用了,已登录为例

 Map result = await ReqHome().loginInApi('用户名', '密码');
 // 这样在获取到token之后在存入到本地
  SpUtil.putString('token', '获取的token');

获取到token之后需要把token设置在header中

 dio.interceptors
      .add(InterceptorsWrapper(onRequest: (RequestOptions options) async {
      
    // 在这添加!!!!!!
    options.queryParameters['Authorization'] =
        SpUtil.getString('token', defValue: '');
        
    return options;
  }));

页面不同状态展示封装

我是用FutureBuilder来控制页面显示不同状态。首先创建个类common_widget文件, 线分析一波,页面状态分成加载中, 加载完成空数据, 加载完成展示UI,加载失败。 演示一下加载成功的图视: 首先定义三个属性

// final Future networkApi; // 传进来的网络请求
// final Function onSuccess; // 加载成功之后把数据传递出去
// final Widget successWidget; // 加载成功的视图

下面是主要代码:

@override
  Widget build(BuildContext context) {
    return FutureBuilder<Map>(
        future:
             widget.networkApi,
        builder: (BuildContext context, AsyncSnapshot snapshot) {
          // 请求已结束
          if (snapshot.connectionState == ConnectionState.done) {
            print(snapshot);
            if (snapshot.hasError) {
              // 请求失败,显示错误
              return _errorView;
            } else {
              // 请求成功,显示数据
              if (snapshot.data == null || snapshot.data.length == 0) {
                return _emptyView;
              }
              widget.onSuccess();
              return widget.successWidget;
            }
          } else {
            // 请求未结束,显示loading
            return Center(
              child: CircularProgressIndicator(),
            );
          }
        });
  }

调用代码:

 @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text('首页'),
        ),
        body: CommonWidget(
          networkApi: ReqHome.getSwiperApi(),
          successWidget: successWidget(),
          onSuccess: () {},
        ));
  }

展示错误UI

在common_widget文件中加入代码:

///错误视图
  Widget get _errorView {
    return Container(
      width: double.infinity,
      height: double.infinity,
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          // Image.asset(
          //   'assets/images/load_error_view.png',
          //   height: 80,
          //   width: 100,
          // ),
          Text("加载失败,请重试"),
          RaisedButton(
            color: Colors.red,
            onPressed: () {
              setState(() {});
            },
            child: Text(
              '重新加载',
              style: TextStyle(color: Colors.white),
            ),
          )
        ],
      ),
    );
  }

效果如下:

展示空数据UI

///数据为空的视图
  Widget get _emptyView {
    return Container(
      width: double.infinity,
      height: double.infinity,
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.center,
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          Padding(
            padding: EdgeInsets.only(top: 10),
            child: Text('暂无数据'),
          )
        ],
      ),
    );
  }

效果图:

视图可以根据UI设计去修改,答题结构就是这样子的。。

one more things。。。。。。

  • 列表数据刷新加载封装
  • 一些工具类封装