Flutter 项目实战(Dio+MVP+FutureBuilder )五

Posted xmiaoshen

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Flutter 项目实战(Dio+MVP+FutureBuilder )五相关的知识,希望对你有一定的参考价值。

       / 没有感情万千 、只有默默无闻 /

        

        2022年跨年了,又涨了一岁。随着时光的流逝,工作多年的我还是在坚持些代码。互联网都有所谓的大龄危机,我对此毫无畏惧。不要因为社会存在一些大龄危机的恐慌,产生了很多心理上的负担 ,我虽然不再年少轻狂,但激情依旧。

        你需要懂的法则就是 : 适者生存,优胜劣汰 。

        你朝思暮想的结果就是 : 冰冻三尺,非一日之寒 。

        你想太多的结果就是 : 还是在原点 , 人老了 , 留下的不是遗憾就是回忆 。

        ................

/  异步 UI 更新FutureBuilder  /

        当我们打开app在网络畅通的情况下从服务器获取数据 , 同时获取数据需要时间 ,这时我们需要一个可以体现数据请求过程的进度条 , 获取到数据后渲染页面 。

        FutureBuilder 是继承自 StatefulWidget  builder必须要有返回值 , 不能为空

 const FutureBuilder(
    Key? key,
    this.future,
    this.initialData,
    required this.builder,
  ) : assert(builder != null),
       super(key: key);

final Future<T>? future;

/// The asynchronous computation to which this builder is currently connected,
  /// possibly null.
  ///
  /// If no future has yet completed, including in the case where [future] is
  /// null, the data provided to the [builder] will be set to [initialData].

        builder 与异步请求建立的连接 (connected) 可能为空 (即future参数可能为空) 。如果异步请求没有完成 , 包括future参数为空 ,那么就提供默认数据(initialData) 给 builder使用 。

当future 不为空时

class _MyHomePageState extends State<MyHomePage> 
  var _futBuiHom;

  void _handleGetNetData() async 
    await _getNetData();
    setState(() );
  

  Future<String> _getNetData() async 
    return await Future.delayed(Duration(seconds: 2), () => "从互联网上获取的数据111111");
  

  @override
  void initState() 
    // TODO: implement initState
    super.initState();
    _futBuiHom = _getNetData();
  

  @override
  Widget build(BuildContext context) 
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: FutureBuilder<dynamic>(
          future: _futBuiHom,
          initialData: '初始化的数据',
          builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) 
            print('\\n获取到到数据: State$snapshot.connectionState \\n data$snapshot.data');
            switch (snapshot.connectionState) 
              case ConnectionState.none:
              case ConnectionState.waiting:
                return const Text('数据加载中.....'); //加载中
              default: //如果_calculation执行完毕
                if (snapshot.hasError) 
                  //若_calculation执行出现异常
                  return Text('Error: $snapshot.hasError');
                 else 
                  //若_calculation执行正常完成
                  return Text('Error: $snapshot.data');
                
            
          ,
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _handleGetNetData,
        tooltip: '模拟从网络获取数据',
        child: Icon(Icons.add),
      ),
    );
  

 当Future 为空时

final AsyncWidgetBuilder<T> builder;

  /// The build strategy currently used by this builder.
  ///
  /// The builder is provided with an [AsyncSnapshot] object whose
  /// [AsyncSnapshot.connectionState] property will be one of the following
  /// values:
  ///
  ///  * [ConnectionState.none]: [future] is null. The [AsyncSnapshot.data] will
  ///    be set to [initialData], unless a future has previously completed, in
  ///    which case the previous result persists.
  ///
  ///  * [ConnectionState.waiting]: [future] is not null, but has not yet
  ///    completed. The [AsyncSnapshot.data] will be set to [initialData],
  ///    unless a future has previously completed, in which case the previous
  ///    result persists.
  ///
  ///  * [ConnectionState.done]: [future] is not null, and has completed. If the
  ///    future completed successfully, the [AsyncSnapshot.data] will be set to
  ///    the value to which the future completed. If it completed with an error,
  ///    [AsyncSnapshot.hasError] will be true and [AsyncSnapshot.error] will be
  ///    set to the error object.
  ///
  /// This builder must only return a widget and should not have any side
  /// effects as it may be called multiple times.

builder提供了一个AsyncSnapshot 对象 , 该对象可以获取异步请求的状态 。

必须返回一个Widget , 因为builder 函数会被调用多次 。

awaiting状态下显示异步请求进度条 。

done 状态下 , 如果hasError 为false , 就重新渲染Widget 。

final T? initialData

/// The data that will be used to create the snapshots provided until a
  /// non-null [future] has completed.
  ///
  /// If the future completes with an error, the data in the [AsyncSnapshot]
  /// provided to the [builder] will become null, regardless of [initialData].
  /// (The error itself will be available in [AsyncSnapshot.error], and
  /// [AsyncSnapshot.hasError] will be true.)

FutureBuilder 初始化数据 . 当future参数不为空 (存在异步请求时) , 异步请求完成后 builder 函数将会被回调 .

/  FutureBuilder 源码讲解/

开始订阅 (异步获取数据)

  void _subscribe() 
    if (widget.future != null) 
      final Object callbackIdentity = Object();
      _activeCallbackIdentity = callbackIdentity;
      widget.future!.then<void>((T data) 
        if (_activeCallbackIdentity == callbackIdentity) 
          setState(() 
            _snapshot = AsyncSnapshot<T>.withData(ConnectionState.done, data);
          );
        
      , onError: (Object error, StackTrace stackTrace) 
        if (_activeCallbackIdentity == callbackIdentity) 
          setState(() 
            _snapshot = AsyncSnapshot<T>.withError(ConnectionState.done, error, stackTrace);
          );
        
      );
      _snapshot = _snapshot.inState(ConnectionState.waiting);
    
  

        当父视图调用 setState 函数时,子视图的 didUpdateWidget 函数被调用 .  如果 future 的值直接引用获取数据的异步函数, 那么builder 对应的函数要被调用多次 .

class _MyHomePageState extends State<MyHomePage> 
  var _futBuiHom;

  void _handleGetNetData() async 
    await _getNetData();
    setState(() );
  

  Future<String> _getNetData() async 
    return await Future.delayed(Duration(seconds: 2), () => "从互联网上获取的数据111111");
  

  @override
  void initState() 
    // TODO: implement initState
    super.initState();
    _futBuiHom = _getNetData();
  

  @override
  void didUpdateWidget(covariant MyHomePage oldWidget) 
    // TODO: implement didUpdateWidget
    super.didUpdateWidget(oldWidget);
    print('didUpdateWidget');
  

  @override
  Widget build(BuildContext context) 

    print('build(BuildContext context)');
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: FutureBuilder<dynamic>(
          future: _getNetData(),
          initialData: '初始化的数据',
          builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) 
            print('\\n获取到到数据: State$snapshot.connectionState \\n data$snapshot.data');
            switch (snapshot.connectionState) 
              case ConnectionState.none:
              case ConnectionState.waiting:
                return const Text('数据加载中.....'); //加载中
              default: //如果_calculation执行完毕
                if (snapshot.hasError) 
                  //若_calculation执行出现异常
                  return Text('Error: $snapshot.hasError');
                 else 
                  //若_calculation执行正常完成
                  return Text('Error: $snapshot.data');
                
            
          ,
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _handleGetNetData,
        tooltip: '模拟从网络获取数据',
        child: Icon(Icons.add),
      ),
    );
  

在FutureBuilder 状态管理类 _FutureBuilderState 的 didUpdateWidget 打印 当前future和上一次的future实例是否一样 .

结果显示不一样 

 所以为了让每次异步请求的实例是同一个 future ,需要在Widget 创建时在initState 函数里面初始化异步请求的future , 每次FutureBuilder 重新构建时都从原来的future异步请求中获取数据.

 var _futBuiHom;

  Future<String> _getNetData() async 
    return await Future.delayed(Duration(seconds: 2), () => "从互联网上获取的数据111111");
  

  @override
  void initState() 
    // TODO: implement initState
    super.initState();
    _futBuiHom = _getNetData();
  

每次调用setState刷新界面 

 void _handleGetNetData() async 
    await _getNetData();
    setState(() );
  

/ FutureBuilder + Dio +MVP 实现网络请求 / 

Flutter 项目实战 Dio网络请求 四

import 'dart:collection';
import 'dart:core';

import 'package:connectivity/connectivity.dart';
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';

import 'http_error.dart';

///http请求成功回调
typedef HttpSuccessCallback<T> = void Function(dynamic data);

///失败回调
typedef HttpFailureCallback = void Function(HttpError data);

///数据解析回调
typedef T JsonParse<T>(dynamic data);

class HttpManager 
  ///同一个CancelToken可以用于多个请求,当一个CancelToken取消时,
  ///所有使用该CancelToken的请求都会被取消,一个页面对应一个CancelToken。
  Map<String, CancelToken> _cancelTokens = Map<String, CancelToken>();

  ///超时时间
  static const int CONNECT_TIMEOUT = 30000;
  static const int RECEIVE_TIMEOUT = 30000;

  /// http request methods
  static const String GET = 'get';
  static const String POST = 'post';

  Dio? _client;

  static final HttpManager _instance = HttpManager._internal();

  factory HttpManager() => _instance;

  Dio get client => _client!;

  /// 创建 dio 实例对象
  HttpManager._internal() 
    if (_client == null) 
      /// 全局属性:请求前缀、连接超时时间、响应超时时间
      BaseOptions options = BaseOptions(
        connectTimeout: CONNECT_TIMEOUT,
        receiveTimeout: RECEIVE_TIMEOUT,
      );
      _client = Dio(options);
    
  

  ///初始化公共属性
  /// [baseUrl] 地址前缀
  /// [connectTimeout] 连接超时赶时间
  /// [receiveTimeout] 接收超时赶时间
  /// [interceptors] 基础拦截器
  void init(
      String? baseUrl,
      int? connectTimeout,
      int? receiveTimeout,
      List<Interceptor>? interceptors) 
    _client!.options = _client!.options.copyWith(
      baseUrl: baseUrl,
      connectTimeout: connectTimeout,
      receiveTimeout: receiveTimeout,
    );
    if (interceptors != null && interceptors.isNotEmpty) 
      _client!.interceptors..addAll(interceptors);
    
  

  ///Get网络请求
  ///[url] 网络请求地址不包含域名
  ///[params] url请求参数支持restful
  ///[options] 请求配置
  ///[successCallback] 请求成功回调
  ///[errorCallback] 请求失败回调
  ///[tag] 请求统一标识,用于取消网络请求
  get(
    String? url,
    Map<String, dynamic>? params,
    Options? options,
    HttpSuccessCallback? successCallback,
    HttpFailureCallback? errorCallback,
    String? tag,
  ) async 
    return _request(
      url: url,
      params: params,
      method: GET,
      options: options,
      successCallback: successCallback!,
      errorCallback: errorCallback!,
      tag: tag!,
    );
  

  ///post网络请求
  ///[url] 网络请求地址不包含域名
  ///[data] post 请求参数
  ///[params] url请求参数支持restful
  ///[options] 请求配置
  ///[successCallback] 请求成功回调
  ///[errorCallback] 请求失败回调
  ///[tag] 请求统一标识,用于取消网络请求
  post(
    String? url,
    data,
    Map<String, dynamic>? params,
    Options? options,
    HttpSuccessCallback? successCallback,
    HttpFailureCallback? errorCallback,
    @required String? tag,
  ) async 
    return _request(
      url: url!,
      data: data,
      method: POST,
      params: params!,
      options: options!,
      successCallback: successCallback!,
      errorCallback: errorCallback!,
      tag: tag!,
    );
  

  ///统一网络请求
  ///[url] 网络请求地址不包含域名
  ///[data] post 请求参数
  ///[params] url请求参数支持restful
  ///[options] 请求配置
  ///[successCallback] 请求成功回调
  ///[errorCallback] 请求失败回调
  ///[tag] 请求统一标识,用于取消网络请求
  _request(
    String? url,
    String? method,
    data,
    Map<String, dynamic>? params,
    Options? options,
    HttpSuccessCallback? successCallback,
    HttpFailureCallback? errorCallback,
    @required String? tag,
  ) async 
    //检查网络是否连接
    ConnectivityResult connectivityResult =
        await (Connectivity().checkConnectivity());
    if (connectivityResult == ConnectivityResult.none) 
      if (errorCallback != null) 
        errorCallback(HttpError(HttpError.NETWORK_ERROR, "网络异常,请稍后重试!"));
      
      return;
    
    //设置默认值
    params = params ?? ;
    method = method ?? 'GET';
    options?.method = method;
    options = options ??
        Options(
          method: method,
        );

    ///请求头
    options.headers = await _headers();
    try 
      CancelToken cancelToken;
      cancelToken =
          (_cancelTokens[tag] == null ? CancelToken() : _cancelTokens[tag])!;
      _cancelTokens[tag!] = cancelToken;
      Response response = await _client!.request(url!,
          data: data,
          queryParameters: params,
          options: options,
          cancelToken: cancelToken);
      var _responseData = response.data;
      print('相应数据:$_responseData');
      return _responseData;
      /*int statusCode = _responseData["code"];
      if (statusCode == 200) 
        //成功
        successCallback!(_responseData["data"]);
       else 
        //失败
        String message = _responseData["msg"].toString();
        errorCallback!(HttpError('$statusCode', message));
      */
     on DioError catch (e, s) 
      if (e.type != DioErrorType.cancel) 
        errorCallback!(HttpError.dioError(e));
      
     catch (e, s) 
      errorCallback!(HttpError(HttpError.UNKNOWN, "未知错误,请稍后重试!"));
    
  

  ///取消网络请求
  void cancel(String tag) 
    if (_cancelTokens.containsKey(tag)) 
      if (!_cancelTokens[tag]!.isCancelled) 
        _cancelTokens[tag]!.cancel();
      
      _cancelTokens.remove(tag);
    
  

  ///请求头
  Future<Map<String, String>> _headers() async 
    Map<String, String> _headers = new HashMap();
    String _token = '';
    _headers.addAll("token": _token);
    return _headers;
  

MVP 模式 - 模型层 - 基础类

创建IModel

import 'package:flutter_dio_mvp_futurebuilder/base/http/http_error.dart';

abstract class IModel 
  ///释放网络请求
  void dispose();


typedef SuccessCallback<T> = void Function(dynamic data);

typedef FailureCallback = void Function(HttpError error);

AbstractModel 

import 'IModel.dart';
abstract class AbstractModel implements IModel 
  String? _tag;

  String? get tag => _tag;

  AbstractModel() 
    _tag = '$DateTime.now().millisecondsSinceEpoch';
  


MVP模式 - Presenter基础类

IPresenter

import 'package:flutter_dio_mvp_futurebuilder/base/view/IView.dart';

abstract class IPresenter<V extends IView> 

  void attachView(V view);


  void detachView();

AbstractPresenter

import 'package:flutter_dio_mvp_futurebuilder/base/model/IModel.dart';
import 'package:flutter_dio_mvp_futurebuilder/base/view/IView.dart';

import 'IPresenter.dart';

abstract class AbstractPresenter<V extends IView , M extends IModel >
    implements IPresenter 
  M? _model;
  V? _view;

  @override
  void attachView(IView  view) 
    this._model = createModel() as M?;
    this._view = view as V?;
  

  @override
  void detachView() 
    if (_view != null) 
      _view = null;
    
    if (_model != null) 
      _model!.dispose();
      _model = null;
    
  

  V? get view 
    return _view;
  

//  V get view => _view;

  M? get model => _model;

  IModel  createModel();

 IView

class IView 
  ///开始加载
  void startLoading() 

  ///加载成功
  void showLoadSuccess() 

  ///加载失败
  void showLoadFailure(String code, String message) 


BaseView

import 'package:flutter/material.dart';
import 'package:flutter_dio_mvp_futurebuilder/base/presenter/IPresenter.dart';
import 'package:flutter_dio_mvp_futurebuilder/base/view/IView.dart';

abstract class BaseView extends StatefulWidget 
  BaseView(Key? key) : super(key: key);

  @override
  BaseViewState createState() => getState();

  ///子类实现
  BaseViewState getState();


abstract class BaseViewState<P extends IPresenter, V extends BaseView>
    extends State<V> with IView 
  P? presenter;

  @override
  void initState() 
    super.initState();

    presenter = createPresenter();
    if (presenter != null) 
      presenter?.attachView(this);
    
  

  P? createPresenter() 

  @override
  Widget build(BuildContext context) 
    return Scaffold(
      ///导航栏
      appBar: buildAppBar(),

      ///内容区域
      body: buildWidget(),

      ///内容区域背景颜色
      backgroundColor: buildBodyColor(),
    );
  

  buildWidget();

  buildAppBar() => null;

  Color buildBodyColor() 
    return Color(0xff00FFFFFF);
  

创建用于业务逻辑处理的callback

import 'package:flutter_dio_mvp_futurebuilder/base/model/IModel.dart';
import 'package:flutter_dio_mvp_futurebuilder/base/presenter/IPresenter.dart';
import 'package:flutter_dio_mvp_futurebuilder/base/view/IView.dart';

abstract class CDataModel extends IModel 
  ///
  loadData(Map<String, dynamic> p, SuccessCallback s, FailureCallback f);


abstract class CDataPresenter extends IPresenter 
  ///
  loadData(Map<String, dynamic> p);


abstract class CDataView extends IView 
  ///
  loadData(d);

模型层(Model)、Presenter、视图层(View) 的具体实现

模型层(Model) 实现

import 'package:flutter_dio_mvp_futurebuilder/base/http/http_manager.dart';
import 'package:flutter_dio_mvp_futurebuilder/base/model/AbstractModel.dart';
import 'package:flutter_dio_mvp_futurebuilder/busmer/mvp_callback.dart';

class MData extends AbstractModel implements CDataModel 
  @override
  void dispose() 
    // TODO: implement dispose
    HttpManager().cancel(tag!);
  

  @override
  loadData(Map<String, dynamic> p, s, f) async
    return HttpManager().get(
        ///网络请求地址
        url: '/f',
        tag: tag!,
        successCallback: (data) 
          s(data);
        ,
        errorCallback: (data) 
          f(data);
        ,
        params: p);
  

Presenter 实现 

import 'package:flutter_dio_mvp_futurebuilder/base/model/IModel.dart';
import 'package:flutter_dio_mvp_futurebuilder/base/presenter/AbstractPresenter.dart';
import 'package:flutter_dio_mvp_futurebuilder/busmer/mvp_callback.dart';
import 'package:flutter_dio_mvp_futurebuilder/model/m_data.dart';

class PData extends AbstractPresenter<CDataView, CDataModel>
    implements CDataPresenter 
  @override
  IModel createModel() 
    return MData();
  

  @override
  loadData(Map<String, dynamic> p) async
    // TODO: implement loadData
    view?.startLoading();
   return model?.loadData(p, (data) 
      view?.showLoadSuccess();
      view?.loadData(data);
      model?.dispose();
    , (error) 
      view?.showLoadFailure(error.code!, error.message!);
    );

  

视图层(View) 实现

class MyHomePage extends BaseView 
  @override
  BaseViewState<IPresenter<IView>, BaseView> getState() 
    // TODO: implement getState
    return _MyHomePageState();
  


class _MyHomePageState extends BaseViewState<PData, MyHomePage>
    implements CDataView 
  var _futBuiHom;

  void _handleGetNetData() async 
    await _getNetData();
    setState(() );
  

  _getNetData() async 
    return await presenter!
        .loadData('ie': 'utf-8', 'kw': '大佬', 'fr': 'search');
  

  @override
  void initState() 
    // TODO: implement initState
    super.initState();
    _futBuiHom = _getNetData();
  

  @override
  loadData(d) 

  @override
  PData? createPresenter() 
    // TODO: implement createPresenter
    return PData();
  

  @override
  Color buildBodyColor() 
    // TODO: implement buildBodyColor
    return Color(0xffFFFFFF);
  

  @override
  buildWidget() 
    // TODO: implement buildWidget
    print('build(BuildContext context)');

    return Column(
      children: [
        AppBar(
          title: Text('FutureBuilder MVP Dio'),
        ),
        OutlinedButton(
          onPressed: _handleGetNetData,
          child: Text('模拟从网络获取数据'),
        ),
        Expanded(
          flex: 1,
          child: Center(
            child: FutureBuilder<dynamic>(
              future: _futBuiHom,
              initialData: '初始化的数据',
              builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) 
                print(
                    '\\n获取到到数据: State$snapshot.connectionState \\n data$snapshot.data');
                switch (snapshot.connectionState) 
                  case ConnectionState.none:
                  case ConnectionState.waiting:
                    return const Text('数据加载中.....'); //加载中
                  default: //如果_calculation执行完毕
                    if (snapshot.hasError) 
                      //若_calculation执行出现异常
                      return Text('Error: $snapshot.hasError');
                     else 
                      //若_calculation执行正常完成
                      return Text('Error: $snapshot.data');
                    
                
              ,
            ),
          ),
        ),
      ],
    );
  

main() 函数初始化网络请求 (网络请求需要的域名baseUrl)

void main() async 
  WidgetsFlutterBinding.ensureInitialized();
  await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
  await SystemChrome.setEnabledSystemUIOverlays(SystemUiOverlay.values);

  ///网络请求管理初始化
  HttpManager().init(baseUrl: 'https://tieba.baidu.com');
  runApp(MyApp());

发起了网络请求

   

总结 

        FutureBuilder 用于显示异步请求的数据, 避免数据请求多次调用builder 对应的函数 , 我们需要在Widget 创建时 State 函数 initState 里面进行异步请求的初始化 .

 Future+Dio+MVP 案例下载 

以上是关于Flutter 项目实战(Dio+MVP+FutureBuilder )五的主要内容,如果未能解决你的问题,请参考以下文章

Flutter网络请求Dio库的使用及封装

包含在 Flutter 中使用 Dio 的 POST API 的项目列表的参数

基于dio库封装flutter项目的标准网络框架

flutter dio 网络请求问题

Flutter Dio源码分析--Dio介绍

mvp 在 flutter 中的应用