Flutter 中由 BuildContext 引發的血案

蕭文翰發表於2020-06-13

今天和各位分享一個博主在實際開發中遇到的問題,以及解決方法。廢話不多說,我們先來看需求:
我們要做一個iOS風格的底部選單彈出元件,具體涉及showCupertinoModalPopup()方法,該方法被執行後,會出現如下圖類似所示的選單彈出檢視:

在這裡插入圖片描述
相信這個彈出選單檢視都有見過吧?下面重點來了:在本次的專案需求中,該檢視的選項文字是由Server端返回的。也就是說,這些選項的內容和個數都不固定,因此不能將其在程式碼中寫固定值。
為了簡化程式碼以突出重點,下面放上我在一開始的實現方案:

  openActionSheet() {
    List<Widget> menuWidgets = new List();
    menuItems.forEach((element) {
      menuWidgets.add(CupertinoActionSheetAction(
        child: Text(element),
        onPressed: () {
          Navigator.pop(context);
          debugPrint("操作$element被執行“);
        },
        isDefaultAction: true,
      ));
    });

    showCupertinoModalPopup(
        context: context,
        builder: (buildContext) {
          return CupertinoActionSheet(
              title: Text('測試選單'),
              message: Text('點選選單項試試吧!'),
              actions: menuWidgets);
        });
  }
複製程式碼

如上述程式碼所示,openActionSheet()是顯示該元件的方法。其中,showCupertinoModalPopup()為Flutter SDK內建方法,其作用即顯示這個元件;再其上面的迴圈以及List宣告、賦值等操作實際上就是在動態新增選單項。menuItems型別是List<String>。
通過對程式碼的解釋,相信大家能夠一目瞭然地看出,當某個選單項被點選時,整個選單元件消失,並列印Debug Log(對應為真實專案要執行的操作)。
大家覺得上述程式碼有問題嗎?如果有問題,問題在哪兒呢?
現在公佈答案:這段程式碼有問題!
上述程式碼執行時,當使用者點選選單項後,其執行結果並非如我們預想的那樣:選單組消失並輸出Log,而變成了:整個頁面被Pop,選單組保留,並輸出Log!
這是什麼原因呢?
實際上,罪魁禍首就在我們迴圈遍歷賦值操作時的這條語句:

Navigator.pop(context);
複製程式碼

這裡的context是整個頁面的BuildContext,而非選單組的。這裡我們要明確一個概念——我們想Pop誰,一定要用誰的BuildContext物件。
在這裡,正確的BuildContext物件是誰呢?它在這裡:

showCupertinoModalPopup(
    context: context,
    builder: (buildContext) {
      return CupertinoActionSheet(
          title: Text('測試選單'),
          message: Text('點選選單項試試吧!'),
          actions: menuWidgets);
    }
);
複製程式碼

注意到了嗎?上面第三行括號裡的buildContext才是我們真正要用的物件。因此,正確的做法是什麼呢?

  openActionSheet() {
    BuildContext tempContext;
    List<Widget> menuWidgets = new List();
    menuItems.forEach((element) {
      menuWidgets.add(CupertinoActionSheetAction(
        child: Text(element),
        onPressed: () {
          Navigator.pop(tempContext);
          debugPrint("操作$element被執行");
        },
        isDefaultAction: true,
      ));
    });

    showCupertinoModalPopup(
        context: context,
        builder: (buildContext) {
          tempContext = buildContext;
          return CupertinoActionSheet(
              title: Text('測試選單'),
              message: Text('點選選單項試試吧!'),
              actions: menuWidgets);
        });
  }
複製程式碼

如上所示,我們只需將正確的物件“帶”到其作用域外面就可以了。
好了,這就是本篇文章的全部內容,希望能夠對你有所幫助!

相關文章