这几天写 flutter 产品给了我一个新需求——在 app 打开时检查当前版本是否为最新版本,若是不是则弹窗提示更新。promise
一开始想,这需求简单啊,直接在 main.dart
的 _MyAppState
initState
中写下 showDialogUpdate()
就完事了。bash
可是被无情打脸,因为项目使用了 ScreenUtil.getInstance().setWidth(w)
(一个 flutter 的尺寸适配解决方案 API)几乎全部的 Widget
都会使用它,而 ScreenUtil.getInstance().setWidth(w)
使用的先决条件是用 ScreenUtil.instance = ScreenUtil(width: 375, height: 667)..init(context)
初始化过一次,不然会抛出异常。app
showDialogUpdate()
会内构建 DialogUpadeWidget
,此时 ScreenUtil
还未被初始化,天然就来异常了。async
应急解决一下,写一个计时器就完了,等待 ScreenUtil
初始化再构建 DialogUpadeWidget
。函数
写一个计时器是不优雅且不可靠的。为了往后的维护与扩展,须要找到一种语义化、清晰、可靠的解决方案。优化
在这个场景下咱们会发现 showDialogUpdate()
依赖于 ScreenUtil
的初始化(下文简称 ScreenUtilInit()
)。可是 showDialogUpdate()
与 ScreenUtilInit()
写在不一样的文件中,咱们没法便捷知道 ScreenUtilInit()
,并且代码执行顺序上 showDialogUpdate()
要优先于 ScreenUtilInit()
。ui
也就是说咱们没法书写如下代码google
/// main.dart
import './FirstPage.dart';
FirstPage.futureScreenUtilInit().then(() => showDialogUpdate());
/// FirstPage.dart
FirstPageState {
var futureScreenUtilInit;
Widget build() {
futureScreenUtilInit = new Future(() => ScreenUtilInit());
// ...
}
}
复制代码
因为身边写 flutter 的大佬很少,这个问题简化描述后跟身边的 js
开发者讨论,在 js
中有 Promise
能够轻松解决这个问题。附上第一版代码 JS粗制解决方案。spa
仔细观察能够发现核心思路是暴露出 Prmose
实例化提供的 resolve
方法给外部使用code
function createResolve() {
let _resolve;
return {
promise: new Promise(resolve => {
_resolve = resolve; // 核心1
}),
resolve: _resolve // 核心2
};
}
复制代码
dart
中跟 js
promise
相似的为 Future
可是使用上稍有不一样, Future
经过返回值转变本身的状态为 success
or error
。没有能够暴露出去的 resolve
方法。
仔细查找发现 google 的开发者已经考虑到这一点了,不过不是 Future
而是隐藏的很深的 Completer
,能够暴露的方法是 completer.complete
。
已在业务上使用的代码以下
import 'dart:async';
/// 初始化函数收集者(随着不断开发,进行查漏补缺)
/// 对于部分早期调用的函数,为了确保其相关依赖者能够正常运行,请让依赖者等待它们。
/// 而被依赖者执行时请配合 delayRely.complete() 使用,详情参考 [DelayRely]
class InitCollector {
/// 初始化 Application.prefs 不然任何的 Application.prefs.get() 均返回 null
static DelayRely initSharedPreferences = new DelayRely();
/// 初始化 ScreenUtil ,全部的尺寸 $w() $h() 都依赖此函数
static DelayRely initScreenUtilInstance = new DelayRely();
/// 初始化 Application.currentRouteContext,全部的 $t 都依赖此值
static DelayRely initCurrentRouteContext = new DelayRely();
}
typedef Complete = void Function([FutureOr value]);
/// 延迟依赖
/// # Examples
/// ``` dart
/// var res = new DelayRely();
/// res.future.then((val) => print('then val $val'));
/// res.future.then((val) => print('then2 val $val'));
/// res.future.then((val) => print('then3 val $val'));
///
/// res.complete(
/// new Future.delayed(
/// new Duration(seconds: 3),
/// () {
/// print('3s callback');
/// return 996;
/// },
/// ),
/// );
/// ```
class DelayRely {
DelayRely() {
_completer = new Completer();
_complete = _completer.complete;
_future = _completer.future;
}
Completer _completer;
Complete _complete;
Future _future;
Future get future => _future;
// 完成 _future 这样 future.then 就能够开始进入任务队列了
void complete<T>(Future<T> future) {
if (_completer.isCompleted) return;
future.then((T val) => _complete(val));
}
}
复制代码
您能够在 dartpad 验证
/// main.dart
void showDialogUpdate({bool isNeedCheckLastReject = false}) async {
await Future.wait([
InitCollector.initSharedPreferences.future,
InitCollector.initScreenUtilInstance.future,
InitCollector.initCurrentRouteContext.future,
]);
// ...
}
/// FirstPage.dart
InitCollector.initScreenUtilInstance.complete(
Future.sync(() {
Application.mediaQuery = MediaQuery.of(context);
// 适配初始化
ScreenUtil.instance = ScreenUtil(width: 375, height: 667)..init(context);
}),
);
复制代码
若有错误或更好的方案,欢迎拍砖