Flutter 入門與實戰(四十八):使用MultiProvider實現多狀態同時管理

島上碼農發表於2021-08-11

這是我參與8月更文挑戰的第11天,活動詳情檢視:8月更文挑戰

前言

上一篇我們利用 Provider 對動態列表進行了改造,改造後的程式碼相比之前簡潔了很多,程式碼邏輯也更清晰,具體情況可以檢視上一篇:Flutter 入門與實戰(四十七):使用 Provider 改造?一樣的程式碼,程式碼量降低了2/3!。本篇繼續,我們來看如何完成刪除功能和詳情頁。

刪除動態

刪除這個操作比較簡單,我們只需要實現之前預留的刪除動態方法 removeWithId 即可。這個方法首先向後端請求刪除介面,刪除成功後將元素從列表資料_dynamics移除即可。

void removeWithId(String id) async {
  var response = await DynamicService.delete(id);
  if (response?.statusCode == 200) {
    _dynamics.removeWhere((element) => element.id == id);
    notifyListeners();
  } else {
    EasyLoading.showError(response?.statusMessage ?? '刪除失敗');
  }
}
複製程式碼

這裡我們用到了Dart 陣列的 removeWhere 方法來刪除 id 匹配的元素。捎帶講一下 Dart 的陣列操作,Dart提供了豐富的陣列操作方法,方法定義在List<E>類中,常見的有:

  • generate:使用指定的長度產生固定的陣列,非常適用於 Mock 資料。
  • forEach:按元素展開,分佈處理每一個元素。
  • map:將元素展開轉換為另一個可迭代物件。
  • addinsertremoveremoveXX:資料的新增,插入、刪除指定元素和按XX條件刪除元素。
  • indexWhere:按回撥函式的條件超找滿足條件的元素的位置。
  • fisrtWhere:找到符合條件的第一個元素,如果找不到會返回符合第二個條件的首個元素,第二個引數如果沒設定也沒找到會拋異常。
  • retainWhere:只保留符合條件的元素。
  • sort:按給定的回撥比較函式排序。
  • 其他請參考 List<E>類中陣列操作方法的定義。

動態詳情

動態詳情的改造有點麻煩,這其中有兩個原因:

  • 動態詳情並不是列表頁面的子元件,也就沒法直接和列表共享狀態管理,意味著狀態管理需要從更高層級定義。
  • 動態詳情需要請求網路資料後才能夠顯示,而且還需要更新閱讀數。這就意味著動態詳情存在著時序操作,這種操作 StatelessWidget 滿足不了,因此需要使用 StatefulWidget,在對應的State類的生命週期完成相應操作。

這個情況有點類似我們之前的購物車應用(參考:Flutter 入門與實戰(四十):以購物車為例初探狀態管理),商品列表頁和購物車頁共享了部分資料,但是並不存在上下級關係。之前購物車的例子我們是將狀態定義在了 main.dartrunApp 方法中,使得購物車的狀態處於了最頂級元件中。目前看來我們的動態狀態管理也需要定義在 main.dartrunApp 方法裡。可是已經有了一個狀態管理元件了,這個時候怎麼辦?

這個時候 ProviderMultiProvider 就派上用場了!MultiProvider 專門為了解決多狀態共存的情況而設計,用法如下:

MultiProvider({
  Key? key,
  providers: List<SingleChildWidget>, 
  Widget? child, 
  TransitionBuilder? builder
})
複製程式碼
  • providers:一組 Provider 物件,用於下級元件依賴多個狀態的情況。
  • child:依賴狀態管理的下級子元件。
  • builder:用於直接獲取狀態資料的語法糖。

我們改造一下 runApp 方法的呼叫:

runApp(MultiProvider(
  providers: [
    ChangeNotifierProvider(create: (context) => CartModel()),
    ChangeNotifierProvider(create: (context) => DynamicModel()),
  ],
  child: MyApp(),
));
複製程式碼

這樣在頂級元件 MyApp 上就有了兩個狀態了,這些狀態可以給所有下級的元件使用。然後來改造動態詳情程式碼 DynamicDetailPage 類。上面講過,這個類還需要保留為 StatefulWidget,只是我們不再使用 setState 方法更新介面了。 ​

initState 的方法中我們需要請求詳情資料,從而保證只請求一次資料。請求成功後狀態管理會自動重新整理介面。同時請求成功後我們還需要更新瀏覽數量。這個我們使用了Futurethen 方法,在獲取動態成功後(方法返回 true)才更新瀏覽數量。請求詳情資料和更新瀏覽數都屬於業務程式碼,我們放入到 DynamicModel 裡,同時我們在 DynamicModel 增加了一個_currentDynamic 用於詳情頁或後續的編輯頁面。

Future<bool> getDynamic(String id) async {
  EasyLoading.showInfo('載入中...', maskType: EasyLoadingMaskType.black);
	if (_currentDynamic?.id != id) {
    _currentDynamic = null;
  }
  var response = await DynamicService.get(id);
  if (response != null && response.statusCode == 200) {
    _currentDynamic = DynamicEntity.fromJson(response.data);
    notifyListeners();

    EasyLoading.dismiss();
    return true;
  } else {
    EasyLoading.showInfo(response.statusMessage);
    return false;
  }
}

void updateViewCount(String id) async {
  var response = await DynamicService.updateViewCount(id);
  if (response != null && response.statusCode == 200) {
    _currentDynamic.viewCount = response.data['viewCount'];
    // 如果元素在列表中,則更新
    int currentIndex =
        dynamics.indexWhere((element) => element.id == _currentDynamic.id);
    if (currentIndex != -1) {
      _dynamics[currentIndex] = _currentDynamic;
    }
    notifyListeners();
  }
}
複製程式碼

詳情頁面就比較簡單了,我們在initState 呼叫 DynamicModelgetDynamic方法獲取詳情,如果成功更新介面,並且更新瀏覽數,這裡我們只貼出部分程式碼:

class DynamicDetailPage extends StatefulWidget {
  final String id;
  DynamicDetailPage(this.id, {Key key}) : super(key: key);

  _DynamicDetailState createState() => _DynamicDetailState();
}

class _DynamicDetailState extends State<DynamicDetailPage> {
  @override
  void initState() {
    super.initState();
    context.read<DynamicModel>().getDynamic(widget.id).then((success) {
      if (success) {
        context.read<DynamicModel>().updateViewCount(widget.id);
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    DynamicEntity currentDynamic = context.watch<DynamicModel>().currentDynamic;
    return Scaffold(
      appBar: AppBar(
        title: Text('動態詳情'),
        brightness: Brightness.dark,
      ),
      body: currentDynamic == null
          ? Center(
              child: Text('請稍候...'),
            )
          : _getDetailWidget(currentDynamic),
    );
  }
  
  //..
}
複製程式碼

改造完,整個程式碼也縮減了幾十行,業務上也更加清晰了。

執行結果

執行結果如下圖,可以看到動態模組和購物車模組都能正常執行,說明 MultiProvider 提供的多狀態可以並行使用。

螢幕錄製2021-08-10 下午10.28.08.gif

總結

本篇介紹了動態模組的刪除和詳情的優化改造,通過使用 MultiProvider,我們能夠實現多狀態共同管理,為 App 的子元件提供多個狀態,從而避免狀態管理類的程式碼揉和不同類業務,導致業務程式碼過於臃腫。當然,這裡也有一個缺陷,那就是如果我們的 App 比較龐大,不可能都將狀態管理提升到main 方法的 runApp 來,這樣會導致整個 App 掛載的狀態過多。對於這種方式又該如何處理,我們接下來會在後續的動態模組程式碼改造中解決這個問題。


我是島上碼農,微信公眾號同名,這是Flutter 入門與實戰的專欄文章。

??:覺得有收穫請點個贊鼓勵一下!

?:收藏文章,方便回看哦!

?:評論交流,互相進步!

相關文章