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);
  }),
);
複製代碼

若有錯誤或更好的方案,歡迎拍磚

相關文章
相關標籤/搜索