场景来源
这几天写 flutter 产品给了我一个新需求——在 app 打开时检查当前版本是否为最新版本,如果不是则弹窗提示更新。
初步尝试
一开始想,这需求简单啊,直接在 main.dart
的 _MyAppState
initState
中写下 showDialogUpdate()
就完事了。
但是被无情打脸,由于项目使用了 ScreenUtil.getInstance().setWidth(w)
(一个 flutter 的尺寸适配解决方案 API)几乎所有的 Widget
都会使用它,而 ScreenUtil.getInstance().setWidth(w)
使用的先决条件是用 ScreenUtil.instance = ScreenUtil(width: 375, height: 667)..init(context)
初始化过一次,否则会抛出异常。
showDialogUpdate()
会内构建 DialogUpadeWidget
,此时 ScreenUtil
还未被初始化,自然就来异常了。
应急解决
应急解决一下,写一个计时器就完了,等待 ScreenUtil
初始化再构建 DialogUpadeWidget
。
分析场景,优化解决方案
写一个计时器是不优雅且不可靠的。为了日后的维护与扩展,需要找到一种语义化、清晰、可靠的解决方案。
在这个场景下我们会发现 showDialogUpdate()
依赖于 ScreenUtil
的初始化(下文简称 ScreenUtilInit()
)。但是 showDialogUpdate()
与 ScreenUtilInit()
写在不同的文件中,我们无法便捷知道 ScreenUtilInit()
,而且代码执行顺序上 showDialogUpdate()
要优先于 ScreenUtilInit()
。
也就是说我们无法书写以下代码
/// main.dart
import './FirstPage.dart';
FirstPage.futureScreenUtilInit().then(() => showDialogUpdate());
/// FirstPage.dart
FirstPageState {
var futureScreenUtilInit;
Widget build() {
futureScreenUtilInit = new Future(() => ScreenUtilInit());
// ...
}
}
js解决方案
由于身边写 flutter 的大佬不多,这个问题简化描述后跟身边的 js
开发者讨论,在 js
中有 Promise
可以轻松解决这个问题。附上初版代码 JS粗制解决方案。
仔细观察可以发现核心思路是暴露出 Prmose
实例化提供的 resolve
方法给外部使用
function createResolve() {
let _resolve;
return {
promise: new Promise(resolve => {
_resolve = resolve; // 核心1
}),
resolve: _resolve // 核心2
};
}
回到 dart
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);
}),
);
如有错误或更好的方案,欢迎拍砖