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 实现网络请求 /
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 里面进行异步请求的初始化 .
以上是关于Flutter 项目实战(Dio+MVP+FutureBuilder )五的主要内容,如果未能解决你的问题,请参考以下文章