Flutter MVVM 實(shí)用框架

基于Provider實(shí)現(xiàn)MVVM框架僵朗,常用的方式是 ViewModel 繼承 ChangeNotifier 看彼,再通過 ChangeNotifierProvider 提供給子Widget,ViewModel數(shù)據(jù)刷新通過調(diào)用 notifyListeners() 來通知Widget進(jìn)行刷新,Widget 通過 Provider.of 、Consumer、Selector 來監(jiān)聽數(shù)據(jù)變化重新 build 更新UI韩肝。這種方式存在的問題有:

  • ViewModel數(shù)據(jù)刷新需要每次調(diào)用 notifyListeners()容易被遺漏
  • notifyListeners()作用在整個(gè)ViewModel,不方便進(jìn)行局部UI刷新控制
  • Selector 雖然可以控制局部刷新九榔,但需要需要自定義 shouldRebuild 要去了解Provider原理
  • 缺少 ViewModel 和 Widget 生命周期的管理

ViewModelProvider 在兼容現(xiàn)有功能基礎(chǔ)刷哀峻,實(shí)現(xiàn)最小改動、不需要每次調(diào)用notifyListeners()哲泊、支持局部刷新UI和生命周期管理的框架

先給出源碼后續(xù)有空再詳細(xì)介紹 view_model_provider

局部刷新控制

1. 通過ValueNotifier創(chuàng)建可觀察對象

class ViewModel extends ChangeNotifier {
  final value1 = ValueNotifier(0);
  final value2 = ValueNotifier(0);
}

2. 通過 ValueListenableBuilder 監(jiān)聽數(shù)據(jù)變化刷新

ValueListenableBuilder(
  valueListenable: viewModel.value1,
  builder: (context, value, child) {
    debugPrint("ValueListenableBuilder $value");
    return Text("ValueListenableBuilder $value");
    },
)

列表刷新控制

1. 通過 ListNotifier 創(chuàng)建可觀察對象

class ViewModel extends ChangeNotifier {
  final list = ListNotifier<String>([]);
}

2. 通過 ListListenableBuilder 監(jiān)聽數(shù)據(jù)變化刷新

ListListenableBuilder(
  valueListenable: viewModel.list,
  builder: (context, value, child) {
    debugPrint("ValueListenableBuilder $value");
    return Text("ValueListenableBuilder $value");
    },
)

實(shí)現(xiàn)生命周期管理

LifecycleWidget剩蟀,提供Widget生命周期監(jiān)聽,開放了以下回調(diào)接口可進(jìn)行初始化和解綁操作

  • create切威,可以監(jiān)聽一個(gè)數(shù)據(jù)變化
  • initState育特,Widget initState 回調(diào)
  • initFrame,Widget 第一幀繪制完成調(diào)用
  • deactivate,Widget deactivate 回調(diào)
  • dispose缰冤,Widget dispose 回調(diào)
  • didUpdateWidget犬缨,Widget didUpdateWidget 回調(diào)
  • didChangeDependencies,Widget didChangeDependencies 回調(diào)

ViewModelProvider

創(chuàng)建ViewModel 提供給子Widget使用棉浸,開放了以下回調(diào)接口可進(jìn)行初始化和解綁操作

  • initViewModel怀薛,ViewModel首次初始化 Widget initState 期間執(zhí)行
  • bindViewModel,ViewModel 首次綁定 Widget 迷郑,方法在Widget build 期間執(zhí)行
  • disposeViewModel枝恋,ViewModel 銷毀,Widget dispose 時(shí)執(zhí)行
/// [ViewModelProvider] 創(chuàng)建ViewModel
class ProviderExample extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ViewModelProvider<ViewModel>(
      create: (_) => ViewModel(),
      initViewModel: (context, viewModel) {
        debugPrint("ProviderBuilderExample initViewModel $viewModel");
      },
      bindViewModel: (context, viewModel) {
        debugPrint("ProviderBuilderExample bindViewModel $viewModel");
      },
      disposeViewModel: (context, viewModel) {
        debugPrint("ProviderBuilderExample disposeViewModel $viewModel");
      },
      builder: (context, viewModel, child) {
        debugPrint("ProviderBuilderExample builder $viewModel");
        return ViewModelWidget(viewModel);
      },
    );
  }
}

另外還可以通過繼承ViewModelProviderWidget來創(chuàng)建ViewModel

/// 繼承 [ViewModelProviderWidget] 創(chuàng)建ViewModel
class ProviderWidgetExample extends ViewModelProviderWidget<ViewModel> {
  ProviderWidgetExample() : super();

  @override
  ViewModel create(BuildContext context) => ViewModel();

  @override
  void initViewModel(BuildContext context, ViewModel viewModel) {
    debugPrint("ProviderWidgetExample initViewModel $viewModel");
  }

  @override
  void bindViewModel(BuildContext context, ViewModel viewModel) {
    debugPrint("ProviderWidgetExample bindViewModel $viewModel");
  }

  @override
  Widget buildChild(BuildContext context, ViewModel viewModel, Widget child) {
    debugPrint("ProviderWidgetExample build $viewModel");
    return ViewModelWidget(viewModel);
  }
}

ViewModel嵌套處理

ViewModel 嵌套 ViewModel 管理子 ViewModel 嗡害,提供了兩種方式焚碌,一種需要手動調(diào)用刷新,另一種通過ValueNotifier包裝替換ViewModel不需要手動刷新霸妹,同ViewModelProvider一樣也有相關(guān)的抽象類提供繼承支持十电。

class ParentViewModel extends ChangeNotifier {
  final valueViewModel = ValueNotifier(ChildViewModel());
  var childViewModel = ChildViewModel();

  void valueNotifier() {
    valueViewModel.value = ChildViewModel();
  }

  void notifyListenerChild() {
    childViewModel = ChildViewModel();
    notifyListeners();
  }
}

class ChildViewModel extends ChangeNotifier {
  final value = ValueNotifier(0);

  addValue() {
    value.value++;
  }
}

1 通過 ViewModelProvider 創(chuàng)建父ViewModel

class ChildProviderExapmle extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ViewModelProvider<ParentViewModel>(
      create: (context) => ParentViewModel(),
      builder: (context, viewModel, child) {
        return Scaffold(
          body: Container(
            child: Column(
              children: [
                ValueViewModelProviderExample(),
                ChildViewModelProviderExample(),
                ElevatedButton(
                  onPressed: () => viewModel.valueNotifier(),
                  child: Text("valueNotifier"),
                ),
                ElevatedButton(
                  onPressed: () => viewModel.notifyListenerChild(),
                  child: Text("notifyListenerChild"),
                ),
              ],
            ),
          ),
        );
      },
    );
  }
}

2 創(chuàng)建子ViewModelProvider

2-1 ChildViewModelProvider

需要手動刷新通常用于列表刷新Item區(qū)域,在ViewModelProvider已有回調(diào)基礎(chǔ)上添加了

  • changeViewModel 叹螟,在子 ViewModel 被替換后可重新執(zhí)行綁定流程
/// [ChildViewModelProvider] 獲取子 ViewModel 例子
class ChildViewModelProviderExample extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ChildViewModelProvider<ParentViewModel, ChildViewModel>(
      create: (_, parent) => parent.childViewModel,
      changeViewModel: (context, parent, viewModel, oldViewModel) {
        debugPrint(
            "ChildViewModelProvider changeViewModel $viewModel, $oldViewModel");
      },
      builder: (context, parent, viewModel, child) {
        debugPrint("ChildViewModelProvider builder $viewModel");
        return Row(
          children: [
            ValueListenableBuilder(
              valueListenable: viewModel.value,
              builder: (context, value, child) => Text("${viewModel.value}"),
            ),
            ElevatedButton(
              onPressed: () => viewModel.addValue(),
              child: Text("addValue"),
            )
          ],
        );
      },
    );
  }
}

2-2 ValueViewModelProvider

作用和回調(diào)與 ChildViewModelProvider一樣摆出,接收數(shù)據(jù)類型為 ValueListenable<ChangeNotifier>

/// [ValueViewModelProvider] 獲取子 ViewModel 例子
class ValueViewModelProviderExample extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ValueViewModelProvider<ParentViewModel, ChildViewModel>(
      create: (_, parent) => parent.valueViewModel,
      changeViewModel: (context, parent, viewModel, oldViewModel) {
        debugPrint(
            "ValueViewModelProvider changeViewModel $viewModel, $oldViewModel");
      },
      builder: (context, parent, viewModel, child) {
        debugPrint("ValueViewModelProvider builder $viewModel");
        return Row(
          children: [
            ValueListenableBuilder(
              valueListenable: viewModel.value,
              builder: (context, value, child) => Text("${viewModel.value}"),
            ),
            ElevatedButton(
              onPressed: () => viewModel.addValue(),
              child: Text("addValue"),
            )
          ],
        );
      },
    );
  }

獲取ViewModel

1 擴(kuò)展函數(shù)

通過context.viewModel<ViewModel>() 可以快速取出ViewModelProvider ChildViewModelProviderValueViewModelProvider的ViewModel,這個(gè)方法在Widget build期間使用首妖,如果要在initStatus期間使用可以直接使用Provider提供的擴(kuò)展context.read<ViewModel>()

2 ViewModelBuilder

用于取出ViewModelProvider提供的ViewModel

ValueListenableBuilder

ValueListenableBuilder 只能監(jiān)聽當(dāng)額數(shù)據(jù)刷新爷恳,同時(shí)監(jiān)聽多個(gè)數(shù)據(jù)刷新可采用ValueTuple2WidgetBuilderValueListenableTuple7BuilderValueListenableListBuilder

  /// 外部傳入 ViewModel有缆,可采用[ValueListenableBuilder]系列監(jiān)聽數(shù)據(jù)變化
  Widget _buildValueListenable(ViewModel viewModel) {
    return Column(
      children: [
        /// 監(jiān)聽單個(gè)數(shù)據(jù)變化
        ValueListenableBuilder(
            valueListenable: viewModel.value1,
            builder: (context, value, child) {
              debugPrint("ValueListenableBuilder $value");
              return Text("ValueListenableBuilder $value");
            }),
        /// 監(jiān)聽多個(gè)數(shù)據(jù)變化,繼承自
        ValueListenableListBuilder(
          valueListenables: [
            viewModel.value1,
            viewModel.value2,
          ],
          builder: (context, value, child) {
            debugPrint(
                "ValueListenableListBuilder ${value.first}, ${value.last}");
            return Text(
                "ValueListenableListBuilder ${value.first}, ${value.last}");
          },
        ),
        /// 監(jiān)聽多個(gè)數(shù)據(jù)變化温亲,繼承自[ValueListenableListBuilder]可指定泛型
        ValueListenableTuple2Builder(
          valueListenables: Tuple2(viewModel.value1, viewModel.value2),
          builder: (context, value, child) {
            debugPrint(
                "ValueListenableTuple2Builder ${value.item1}, ${value.item2}");
            return Text(
                "ValueListenableTuple2Builder ${value.item1}, ${value.item2}");
          },
        ),
      ],
    );
  }

ViewModelValueBuilder

ViewModelBuilder 和 ValueListenableBuilder 組合棚壁,用于獲取 ViewModel 和管理Widget刷新區(qū)域。

提供過個(gè)實(shí)現(xiàn) ViewModelValueListBuilder栈虚,ViewModelValueTuple2BuilderViewModelValueTuple7WidgetBuilder可同時(shí)監(jiān)聽多個(gè)ViewMode參數(shù)變化來刷新Widget

  /// 不通過外部傳入 ViewModel袖外,可采用[ViewModelValueBuilder]系列獲取 ViewModel 并監(jiān)聽數(shù)據(jù)變化
  Widget _buildViewModelValue() {
    return Column(
      children: [
        ViewModelValueBuilder(
          valueListenable: (ViewModel viewModel) => viewModel.value1,
          builder: (context, viewModel, value, child) {
            debugPrint("ViewModelValueBuilder $value");
            return Text("ViewModelValueBuilder $value");
          },
        ),
        ViewModelValueListBuilder(
          valueListenables: (ViewModel viewModel) => [
            viewModel.value1,
            viewModel.value2,
          ],
          builder: (context, viewModel, value, child) {
            debugPrint(
                "ViewModelValueListBuilder ${value.first}, ${value.last}");
            return Text(
                "ViewModelValueListBuilder ${value.first}, ${value.last}");
          },
        ),
        ViewModelValueTuple2Builder(
          valueListenables: (ViewModel viewModel) =>
              Tuple2(viewModel.value1, viewModel.value2),
          builder: (context, viewModel, value, child) {
            debugPrint(
                "ViewModelValueTuple2Builder ${value.item1}, ${value.item2}");
            return Text(
                "ViewModelValueTuple2Builder ${value.item1}, ${value.item2}");
          },
        ),
      ],
    );
  }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市魂务,隨后出現(xiàn)的幾起案子曼验,更是在濱河造成了極大的恐慌,老刑警劉巖粘姜,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件鬓照,死亡現(xiàn)場離奇詭異,居然都是意外死亡孤紧,警方通過查閱死者的電腦和手機(jī)豺裆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來号显,“玉大人臭猜,你說我怎么就攤上這事躺酒。” “怎么了蔑歌?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵羹应,是天一觀的道長。 經(jīng)常有香客問我丐膝,道長量愧,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任帅矗,我火速辦了婚禮偎肃,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘浑此。我一直安慰自己累颂,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布凛俱。 她就那樣靜靜地躺著紊馏,像睡著了一般。 火紅的嫁衣襯著肌膚如雪蒲犬。 梳的紋絲不亂的頭發(fā)上朱监,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天,我揣著相機(jī)與錄音原叮,去河邊找鬼赫编。 笑死,一個(gè)胖子當(dāng)著我的面吹牛奋隶,可吹牛的內(nèi)容都是我干的擂送。 我是一名探鬼主播,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼唯欣,長吁一口氣:“原來是場噩夢啊……” “哼嘹吨!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起境氢,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤蟀拷,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后萍聊,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體匹厘,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年脐区,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了愈诚。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,161評論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖炕柔,靈堂內(nèi)的尸體忽然破棺而出酌泰,到底是詐尸還是另有隱情,我是刑警寧澤匕累,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布陵刹,位于F島的核電站,受9級特大地震影響欢嘿,放射性物質(zhì)發(fā)生泄漏衰琐。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一炼蹦、第九天 我趴在偏房一處隱蔽的房頂上張望羡宙。 院中可真熱鬧,春花似錦掐隐、人聲如沸狗热。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽匿刮。三九已至,卻和暖如春探颈,著一層夾襖步出監(jiān)牢的瞬間熟丸,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工伪节, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留光羞,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓架馋,卻偏偏與公主長得像,于是被迫代替她去往敵國和親全闷。 傳聞我的和親對象是個(gè)殘疾皇子叉寂,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內(nèi)容