1. 程式人生 > 實用技巧 >Flutter&Dart Callback轉同步

Flutter&Dart Callback轉同步

前言

怎麼將一個Callback回撥轉化成Future同步方法(Callback to Future),可以配套async / await去使用呢?

個人覺得,這是一個很常見的現象,不知道為啥,很多人在說明Future用法的時候,都沒提到這個場景,奇怪+懵逼,只能自己去苟解決方案了。

歷程

  • 為什麼我要將Callback轉成Future方法?
    • 大家知道,Flutter在載入頁面的時候,有個渲染的過程,在沒渲染完成的時候,你去顯示一些View的操作,會報錯的,例如:載入loading彈窗
    • 解決方法可能大家都知道,Lifecycle.initState / iniState 生命週期裡面做個延時操作或者使用WidgetsBinding
//延時操作
await Future.delayed(Duration(milliseconds: 200));
//下面可以載入彈窗

//使用WidgetsBinding
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
    //此處可以載入彈窗
});
  • 當然,使用WidgetsBinding是更靠譜和準確的,但是這個Callback就讓我很方了,而且,這名字太長,也不太記的住,這就需要將它封裝了
  • 封裝WidgetsBinding
    • 蛋筒了,這玩意怎麼封裝呢
class ViewUtil {
  ///介面初始化完成
  static Future<Void> initFinish() async {
    WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
      
    });
  }
}
  • 首先我想到了:Future.delayed()
    • 進去看下他的原始碼
    • 有戲,可以看到,這裡面明顯包含了一個Timer中的Callback回撥,但最後轉換成了,Future方法
factory Future.delayed(Duration duration, [FutureOr<T> computation()?]) {
    if (computation == null && !typeAcceptsNull<T>()) {
        throw ArgumentError.value(
            null, "computation", "The type parameter is not nullable");
    }
    _Future<T> result = new _Future<T>();
    new Timer(duration, () {
        if (computation == null) {
            result._complete(null as T);
        } else {
            try {
                result._complete(computation());
            } catch (e, s) {
                _completeWithErrorCallback(result, e, s);
            }
        }
    });
    return result;
}
  • 分析下

    • 首先是例項化一個_Future()物件,然後返回了這個_Future()物件
    • 可以看到方法的最下方是直接返回這個物件,可想而知,這地方,肯定一直處於一個阻塞狀態,在等待一個條件結束這個阻塞狀態
    • 然後在Timer的延遲時間到了後,其回撥中使用了_complete()這個方法,這個方法應該是結束了_Future()物件的阻塞狀態,然後再返回_Future()物件,同時這個方法也結束了
  • 這不就簡單了,我把這個抄出來不就歐了

  • 這個_Future類是個私有方法,在future_impl.dart檔案,把這個檔案拷出來,放在我們工具類檔案同一個包下,

    • 然後。。。

  • 這一堆報錯,玩毛線啊,肯定是我開啟的方式不對

    • 難道要一個一個去解決這些報錯?要是這麼麻煩,還搞毛線!

  • 是不是我搜索的姿勢不對,再來搜搜看
    • 我去,還自動給我提示:dart callback to future,這麼神奇的嗎?試試看

解決方案

  • 記錄下Callback to Future的寫法,很簡單,使用Completer類即可
class ViewUtil {
  ///介面初始化完成
  static Future<Void> initFinish() async {
    Completer<Void> completer = Completer();

    WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
      completer.complete();
    });

    return completer.future;
  }
}
  • 使用
    • 使用起來,瞬間簡單很多
void _init() async {
    await ViewUtil.initFinish();
    ///下面可以使用載入彈窗
}

說明

  • Future和Completer的泛型最好保持一致
    • 例如都是String的話,complete()方法裡面就可以加上相應的內容,然後await接受這個方法時候,就能拿到complete()方法裡面輸入的值了
class ViewUtil {
    ///介面初始化完成
    static Future<String> initFinish() async {
        Completer<String> completer = Completer();

        WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
            completer.complete("測試一下...");
        });

        return completer.future;
    }
}


void _init() async {
    var s = await ViewUtil.initFinish();
    print(s);
}