flutter边缘实践——异步依赖(附JS粗制版解决方案)

场景来源

这几天写 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());
   // ...
  }
}
复制代码

js解决方案

因为身边写 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

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);
  }),
);
复制代码

若有错误或更好的方案,欢迎拍砖

相关文章
相关标签/搜索