/ 沒有感情萬千 、只有默默無聞 /
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);
});
}
}, one rror: (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 案例下載
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/400601.html
標籤:其他
