【跨平臺開發(fā)Flutter】Flutter里的MVVM

目錄
一、最原始的MVVM
二彭则、使用Listener的MVVM
三鳍刷、使用Provider的MVVM
四、使用GetX的MVVM

需求很簡單:

  • 搜索框輸入“張”時就請求前綴為“張”的用戶數(shù)據(jù)俯抖、同時也請求后綴為“張”的用戶數(shù)據(jù)输瓜,搜索框輸入“李”時就請求前綴為“李”的用戶數(shù)據(jù)、同時也請求后綴為“李”的用戶數(shù)據(jù)
  • 請求完成后芬萍,前綴數(shù)據(jù)交給上面的ListView顯示尤揣,后綴數(shù)據(jù)交給下面的ListView顯示


一、最原始的MVVM


  • Model層
-----------person_model.dart-----------

/*
 Model的職責:Model只負責封裝數(shù)據(jù)柬祠,不做任何其它操作北戏。
 */

/// Person模型
class PersonModel {
  /// 普通構(gòu)造方法
  PersonModel({
    this.name,
    this.sex,
    this.age,
  });

  /// 姓名
  String? name;

  /// 性別
  ///
  /// 0-未知,1-男漫蛔,2-女
  int? sex;

  /// 年齡
  int? age;

  /// 工廠構(gòu)造方法
  factory PersonModel.fromJson(Map<String, dynamic> json) => PersonModel(
        name: json["name"] == null ? null : json["name"],
        sex: json["sex"] == null ? null : json["sex"],
        age: json["age"] == null ? null : json["age"],
      );

  /// 模型轉(zhuǎn)字典
  Map<String, dynamic> toJson() => {
        "name": name == null ? null : name,
        "sex": sex == null ? null : sex,
        "age": age == null ? null : age,
      };
}
  • View層
-----------search_bar_widget.dart-----------

/*
 View的職責:View負責響應(yīng)與業(yè)務(wù)有關(guān)的事件并交給Controller去處理嗜愈,怎么交給Controller呢?通過閉包莽龟、通知等芝硬。
 */

import 'package:flutter/material.dart';

/// 搜索框Widget
class SearchBarWidget extends StatelessWidget {
  SearchBarWidget({
    this.searchTextDidChangeCallback,
  });

  /// 搜索內(nèi)容改變的回調(diào)
  final void Function(String text)? searchTextDidChangeCallback;

  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.red,
      child: TextField(
        textInputAction: TextInputAction.done,
        onSubmitted: (text) {
          if (searchTextDidChangeCallback != null) {
            searchTextDidChangeCallback!(text);
          }
        },
      ),
    );
  }
}
-----------prefix_list_view_widget.dart-----------

/*
 View的職責:View負責顯示數(shù)據(jù),那怎么顯示數(shù)據(jù)呢轧房?View可以持有ViewModel。
 */

import 'package:flutter/material.dart';

import 'package:flutter_mvvm/classes/view_model/person_view_model.dart';

/// 前綴列表Widget
class PrefixListViewWidget extends StatelessWidget {
  PrefixListViewWidget({
    required this.personViewModel,
  });

  final PersonViewModel personViewModel;

  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.green,
      child: ListView.builder(
        itemCount: personViewModel.prefixPersonViewModelList.length,
        itemBuilder: (context, index) {
          PersonViewModel personVM =
              personViewModel.prefixPersonViewModelList[index];

          return Row(
            mainAxisAlignment: MainAxisAlignment.spaceAround,
            children: [
              Text(personVM.name),
              Text(personVM.sex),
              Text("${personVM.age}"),
            ],
          );
        },
        itemExtent: 100,
      ),
    );
  }
}
-----------suffix_list_view_widget.dart-----------

/*
 View的職責:View負責顯示數(shù)據(jù)绍绘,那怎么顯示數(shù)據(jù)呢奶镶?View可以持有ViewModel。
 */

import 'package:flutter/material.dart';

import 'package:flutter_mvvm/classes/view_model/person_view_model.dart';

/// 后綴列表Widget
class SuffixListViewWidget extends StatelessWidget {
  SuffixListViewWidget({
    required this.personViewModel,
  });

  final PersonViewModel personViewModel;

  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.blue,
      child: ListView.builder(
        itemCount: personViewModel.suffixPersonViewModelList.length,
        itemBuilder: (context, index) {
          PersonViewModel personVM =
              personViewModel.suffixPersonViewModelList[index];

          return Row(
            mainAxisAlignment: MainAxisAlignment.spaceAround,
            children: [
              Text(personVM.name),
              Text(personVM.sex),
              Text("${personVM.age}"),
            ],
          );
        },
        itemExtent: 100,
      ),
    );
  }
}
  • ViewModel層
-----------base_view_model.dart-----------

import 'package:flutter/cupertino.dart';

enum ViewState {
  loading, // 加載中
  success, // 加載成功
  empty, // 加載成功陪拘,但數(shù)據(jù)為空
  failure, // 加載失敗
  noNetwork, // 沒網(wǎng)
}

class BaseViewModel extends ChangeNotifier {
  ViewState _state = ViewState.success;

  ViewState get state => _state;

  set state(ViewState value) {
    _state = value;
    notifyListeners();
  }
}
-----------person_view_model.dart-----------

/*
 ViewModel的職責:
  1厂镇、ViewModel負責獲取數(shù)據(jù);
  2左刽、ViewModel負責處理數(shù)據(jù)捺信;
  3、ViewModel負責存儲數(shù)據(jù)。
 */

import 'dart:convert';

import 'package:flutter/services.dart';

import 'package:flutter_mvvm/classes/model/person_model.dart';
import 'package:flutter_mvvm/classes/view_model/base_view_model.dart';

/// Person視圖模型
class PersonViewModel extends BaseViewModel {
  // 命名構(gòu)造方法迄靠,專門用來初始化_personModel
  PersonViewModel._from(PersonModel? personModel) {
    _personModel = personModel;
  }

  // 持有一個_personModel秒咨,以便處理數(shù)據(jù):ViewModel一對一Model地添加屬性并處理,搞成getter方法即可
  PersonModel? _personModel;

  /// 普通構(gòu)造方法
  PersonViewModel();

  /// 存儲數(shù)據(jù):vm數(shù)組
  ///
  /// 真正暴露給外面使用的是vm數(shù)組掌挚,里面的數(shù)據(jù)已經(jīng)處理好了雨席,直接拿著顯示就行了
  List<PersonViewModel> prefixPersonViewModelList = [];
  List<PersonViewModel> suffixPersonViewModelList = [];

  /// 存儲數(shù)據(jù):錯誤消息
  String errorMsg = "";

  /// 處理數(shù)據(jù):姓名
  String get name {
    return _personModel?.name ?? "";
  }

  /// 處理數(shù)據(jù):性別
  ///
  /// 0-未知,1-男吠式,2-女
  String get sex {
    if (_personModel?.sex == 1) {
      return "男";
    } else if (_personModel?.sex == 2) {
      return "女";
    } else {
      return "未知";
    }
  }

  /// 處理數(shù)據(jù):年齡
  int get age {
    return _personModel?.age ?? 0;
  }

  /// 請求前綴數(shù)據(jù)
  Future<void> loadPrefixData(
      String params, void Function(bool isSuccess) completionHandler) async {
    await Future.delayed(Duration(seconds: 1));

    try {
      String path = "lib/assets/json/${params}_prefix.json";

      String jsonString = await rootBundle.loadString(path);
      List list = jsonDecode(jsonString);

      prefixPersonViewModelList.clear();
      for (Map<String, dynamic> map in list) {
        PersonModel personModel = PersonModel.fromJson(map);
        PersonViewModel personViewModel = PersonViewModel._from(personModel);
        prefixPersonViewModelList.add(personViewModel);
      }

      state = ViewState.success;
      completionHandler(true);
    } catch (error) {
      errorMsg = error.toString();

      state = ViewState.failure;
      completionHandler(false);
    }
  }

  /// 請求后綴數(shù)據(jù)
  Future<void> loadSuffixData(
      String params, void Function(bool isSuccess) completionHandler) async {
    await Future.delayed(Duration(seconds: 2));

    try {
      String path = "lib/assets/json/${params}_suffix.json";

      String jsonString = await rootBundle.loadString(path);
      List list = jsonDecode(jsonString);

      suffixPersonViewModelList.clear();
      for (Map<String, dynamic> map in list) {
        PersonModel personModel = PersonModel.fromJson(map);
        PersonViewModel personViewModel = PersonViewModel._from(personModel);
        suffixPersonViewModelList.add(personViewModel);
      }

      state = ViewState.success;
      completionHandler(true);
    } catch (error) {
      errorMsg = error.toString();

      state = ViewState.failure;
      completionHandler(false);
    }
  }
}
  • Controller層
-----------list_page.dart-----------

/*
 Controller的職責:
  1陡厘、Controller負責持有View,創(chuàng)建View特占,并把View添加到窗口上顯示糙置;
  2、Controller負責持有ViewModel是目,調(diào)用ViewModel的方法去請求數(shù)據(jù)谤饭;
  3、vm --> view:Controller調(diào)用vm的方法請求數(shù)據(jù)胖笛,請求完成后vm是通過回調(diào)的方式告訴Controller的:
    請求成功后Controller需要調(diào)用一下setState來刷一下UI网持,這樣view就會去拿vm里最新存儲的數(shù)據(jù)來展示了
    請求失敗后Controller可以toast一下錯誤信息給用戶看,或者調(diào)用一下setState來刷一下UI长踊,刷成暫無數(shù)據(jù)那種view
  4功舀、view --> vm:view產(chǎn)生的變化是通過回調(diào)告訴Controller的,Controller可以調(diào)用vm的方法把view發(fā)生的變化告訴它
 */

import 'package:flutter/material.dart';

import 'package:flutter_mvvm/classes/widget/search_bar_widget.dart';
import 'package:flutter_mvvm/classes/widget/prefix_list_view_widget.dart';
import 'package:flutter_mvvm/classes/widget/suffix_list_view_widget.dart';
import 'package:flutter_mvvm/classes/view_model/person_view_model.dart';

/// 列表界面
class ListPage extends StatefulWidget {
  const ListPage({Key? key}) : super(key: key);

  @override
  State<ListPage> createState() => _ListPageState();
}

class _ListPageState extends State<ListPage> {
  PersonViewModel _personViewModel = PersonViewModel();

  @override
  Widget build(BuildContext context) {
    print("build");

    return Scaffold(
      appBar: AppBar(
        title: Text("Flutter MVVM"),
      ),
      body: SafeArea(
        child: Column(
          children: [
            _buildSearchBarWidget(),
            Expanded(
              child: _buildPrefixListViewWidget(),
            ),
            Expanded(
              child: _buildSuffixListViewWidget(),
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildSearchBarWidget() {
    print("_buildSearchBarWidget");

    return Container(
      color: Colors.red,
      child: SearchBarWidget(
        searchTextDidChangeCallback: (text) {
          _personViewModel.loadPrefixData(text, (isSuccess) {
            if (mounted) {
              // 注意要判斷一下當前界面還在不在視圖樹中身弊,因為請求都是異步的辟汰,很有可能請求還沒完成我們就退出界面了,退出界面時我們什么都不做即可
              if (isSuccess) {
                setState(() {});
              } else {
                print(_personViewModel.errorMsg);
              }
            }
          });

          _personViewModel.loadSuffixData(text, (isSuccess) {
            if (mounted) {
              // 注意要判斷一下當前界面還在不在視圖樹中阱佛,因為請求都是異步的帖汞,很有可能請求還沒完成我們就退出界面了,退出界面時我們什么都不做即可
              if (isSuccess) {
                setState(() {});
              } else {
                print(_personViewModel.errorMsg);
              }
            }
          });
        },
      ),
    );
  }

  Widget _buildPrefixListViewWidget() {
    print("_buildPrefixListViewWidget");

    return PrefixListViewWidget(
      personViewModel: _personViewModel,
    );
  }

  Widget _buildSuffixListViewWidget() {
    print("_buildPrefixListViewWidget");

    return SuffixListViewWidget(
      personViewModel: _personViewModel,
    );
  }
}


二凑术、使用Listener的MVVM


相對于最原始的MVVM來說翩蘸,它的變化其實就是【Controller調(diào)用vm的方法請求數(shù)據(jù),請求完成后vm通過什么方式告訴Controller淮逊,之前是通過回調(diào)的方式催首,現(xiàn)在是通過Listener的方式】。

它的優(yōu)勢就是【界面退出時假如我們的請求還沒走完泄鹏,會自動移除監(jiān)聽】郎任,也就是說就算網(wǎng)絡(luò)請求完了也不會再觸發(fā)Controller里的監(jiān)聽了,我們壓根兒不需要關(guān)心這個界面在不在視圖樹里备籽,只需要做業(yè)務(wù)即可舶治。【它的劣勢就是因為所有的業(yè)務(wù)都會觸發(fā)同一個監(jiān)聽而使得代碼判斷起來復雜了,之前起碼是一個業(yè)務(wù)一個回調(diào)、各管各的】霉猛,它給人感覺上好像是在數(shù)據(jù)驅(qū)動UI尺锚,但其實并不是,因為這里并不存在數(shù)據(jù)和UI的綁定操作韩脏,本質(zhì)上還是數(shù)據(jù)變化后缩麸、我們手動刷新UI來展示最新的數(shù)據(jù)。

因此相對于最原始的MVVM來說赡矢,更推薦最原始的MVVM杭朱。

  • Model層和View層都不需要改動
  • ViewModel層
-----------base_view_model.dart-----------

import 'package:flutter/cupertino.dart';

enum ViewState {
  loading, // 加載中
  success, // 加載成功
  empty, // 加載成功,但數(shù)據(jù)為空
  failure, // 加載失敗
  noNetwork, // 沒網(wǎng)
}

class BaseViewModel extends ChangeNotifier {
  ViewState _state = ViewState.success;

  ViewState get state => _state;

  set state(ViewState value) {
    _state = value;
    notifyListeners();
  }
}
-----------person_view_model.dart-----------

/*
 ViewModel的職責:
  1吹散、ViewModel負責獲取數(shù)據(jù)弧械;
  2、ViewModel負責處理數(shù)據(jù)空民;
  3刃唐、ViewModel負責存儲數(shù)據(jù)。
 */

import 'dart:convert';

import 'package:flutter/services.dart';

import 'package:flutter_mvvm/classes/model/person_model.dart';
import 'package:flutter_mvvm/classes/view_model/base_view_model.dart';

/// PersonViewModel里所做的業(yè)務(wù)
class PersonViewModelService {
  static const String loadPrefixData = "loadPrefixData";
  static const String loadSuffixData = "loadSuffixData";
}

/// PersonViewModel里所做業(yè)務(wù)的結(jié)果
class PersonViewModelServiceResult {
  PersonViewModelServiceResult({
    required this.service,
    required this.isSuccess,
  });

  /// 具體是哪個業(yè)務(wù)
  String service;

  /// 結(jié)果
  bool isSuccess;
}

/// Person視圖模型
///
/// 要想使用Listener界轩,PersonViewModel得繼承自或者混入ChangeNotifier
class PersonViewModel extends BaseViewModel {
  late PersonViewModelServiceResult serviceResult;

  // 命名構(gòu)造方法画饥,專門用來初始化_personModel
  PersonViewModel._from(PersonModel? personModel) {
    _personModel = personModel;
  }

  // 持有一個_personModel,以便處理數(shù)據(jù):ViewModel一對一Model地添加屬性并處理浊猾,搞成getter方法即可
  PersonModel? _personModel;

  /// 普通構(gòu)造方法
  PersonViewModel();

  /// 存儲數(shù)據(jù):vm數(shù)組
  ///
  /// 真正暴露給外面使用的是vm數(shù)組抖甘,里面的數(shù)據(jù)已經(jīng)處理好了,直接拿著顯示就行了
  List<PersonViewModel> prefixPersonViewModelList = [];
  List<PersonViewModel> suffixPersonViewModelList = [];

  /// 存儲數(shù)據(jù):錯誤消息
  String errorMsg = "";

  /// 處理數(shù)據(jù):姓名
  String get name {
    return _personModel?.name ?? "";
  }

  /// 處理數(shù)據(jù):性別
  ///
  /// 0-未知葫慎,1-男衔彻,2-女
  String get sex {
    if (_personModel?.sex == 1) {
      return "男";
    } else if (_personModel?.sex == 2) {
      return "女";
    } else {
      return "未知";
    }
  }

  /// 處理數(shù)據(jù):年齡
  int get age {
    return _personModel?.age ?? 0;
  }

  /// 請求前綴數(shù)據(jù)
  ///
  /// 請求完成后,本來是通過回調(diào)告訴Controller的偷办,現(xiàn)在不要回調(diào)了艰额,通過Listener告訴Controller
  Future<void> loadPrefixData(String params) async {
    await Future.delayed(Duration(seconds: 1));

    try {
      String path = "lib/assets/json/${params}_prefix.json";

      String jsonString = await rootBundle.loadString(path);
      List list = jsonDecode(jsonString);

      prefixPersonViewModelList.clear();
      for (Map<String, dynamic> map in list) {
        PersonModel personModel = PersonModel.fromJson(map);
        PersonViewModel personViewModel = PersonViewModel._from(personModel);
        prefixPersonViewModelList.add(personViewModel);
      }

      state = ViewState.success;
      serviceResult = PersonViewModelServiceResult(
          service: PersonViewModelService.loadPrefixData, isSuccess: true);
      notifyListeners();
    } catch (error) {
      errorMsg = error.toString();

      state = ViewState.failure;
      serviceResult = PersonViewModelServiceResult(
          service: PersonViewModelService.loadPrefixData, isSuccess: false);
      notifyListeners();
    }
  }

  /// 請求后綴數(shù)據(jù)
  ///
  /// 請求完成后,本來是通過回調(diào)告訴Controller的椒涯,現(xiàn)在不要回調(diào)了柄沮,通過Listener告訴Controller
  Future<void> loadSuffixData(String params) async {
    await Future.delayed(Duration(seconds: 2));

    try {
      String path = "lib/assets/json/${params}_suffix.json";

      String jsonString = await rootBundle.loadString(path);
      List list = jsonDecode(jsonString);

      suffixPersonViewModelList.clear();
      for (Map<String, dynamic> map in list) {
        PersonModel personModel = PersonModel.fromJson(map);
        PersonViewModel personViewModel = PersonViewModel._from(personModel);
        suffixPersonViewModelList.add(personViewModel);
      }

      state = ViewState.success;
      serviceResult = PersonViewModelServiceResult(
          service: PersonViewModelService.loadSuffixData, isSuccess: true);
      notifyListeners();
    } catch (error) {
      errorMsg = error.toString();

      state = ViewState.failure;
      serviceResult = PersonViewModelServiceResult(
          service: PersonViewModelService.loadSuffixData, isSuccess: false);
      notifyListeners();
    }
  }
}
  • Controller層
-----------list_page.dart-----------

/*
 Controller的職責:
  1、Controller負責持有View废岂,創(chuàng)建View铡溪,并把View添加到窗口上顯示;
  2泪喊、Controller負責持有ViewModel,調(diào)用ViewModel的方法去請求數(shù)據(jù)髓涯;
  3袒啼、vm --> view:Controller調(diào)用vm的方法請求數(shù)據(jù),請求完成后vm是通過Listener而非回調(diào)的方式告訴Controller的:
    請求成功后Controller需要調(diào)用一下setState來刷一下UI,這樣view就會去拿vm里最新存儲的數(shù)據(jù)來展示了
    請求失敗后Controller可以toast一下錯誤信息給用戶看蚓再,或者調(diào)用一下setState來刷一下UI滑肉,刷成暫無數(shù)據(jù)那種view
  4、view --> vm:view產(chǎn)生的變化是通過回調(diào)告訴Controller的摘仅,Controller可以調(diào)用vm的方法把view發(fā)生的變化告訴它
 */

import 'package:flutter/material.dart';

import 'package:flutter_mvvm/classes/widget/search_bar_widget.dart';
import 'package:flutter_mvvm/classes/widget/prefix_list_view_widget.dart';
import 'package:flutter_mvvm/classes/widget/suffix_list_view_widget.dart';
import 'package:flutter_mvvm/classes/view_model/person_view_model.dart';

/// 列表界面
class ListPage extends StatefulWidget {
  const ListPage({Key? key}) : super(key: key);

  @override
  State<ListPage> createState() => _ListPageState();
}

class _ListPageState extends State<ListPage> {
  PersonViewModel _personViewModel = PersonViewModel();

  @override
  void initState() {
    // 第一步:_personViewModel添加監(jiān)聽
    //
    // 它里面任何時候靶庙、任何地方發(fā)出notifyListeners,都會觸發(fā)這里添加好的監(jiān)聽
    // 那它里面什么時候娃属、什么地方發(fā)出notifyListeners呢六荒?當然就是請求完成的時候,在請求完成的回調(diào)里發(fā)出
    // 當然因為它內(nèi)部可能會做多個業(yè)務(wù)矾端,如果多個業(yè)務(wù)都發(fā)出了notifyListeners掏击,則都會觸發(fā)這里的同一個回調(diào),因此我們會在它里面添加一個類來區(qū)分到底是哪個業(yè)務(wù)完成了秩铆,以便在監(jiān)聽里處理不同的業(yè)務(wù)
    _personViewModel.addListener(_personViewModelListener);

    super.initState();
  }

  // 第二步:_personViewModel處理監(jiān)聽
  void _personViewModelListener() {
    if (_personViewModel.serviceResult.service ==
        PersonViewModelService.loadPrefixData) {
      if (_personViewModel.serviceResult.isSuccess) {
        setState(() {});
      } else {
        print(_personViewModel.errorMsg);
      }
    } else if (_personViewModel.serviceResult.service ==
        PersonViewModelService.loadSuffixData) {
      if (_personViewModel.serviceResult.isSuccess) {
        setState(() {});
      } else {
        print(_personViewModel.errorMsg);
      }
    }
  }

  @override
  void dispose() {
    // 第三步:_personViewModel移除監(jiān)聽
    _personViewModel.removeListener(_personViewModelListener);

    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    print("build");

    return Scaffold(
      appBar: AppBar(
        title: Text("Flutter MVVM"),
      ),
      body: SafeArea(
        child: Column(
          children: [
            _buildSearchBarWidget(),
            Expanded(
              child: _buildPrefixListViewWidget(),
            ),
            Expanded(
              child: _buildSuffixListViewWidget(),
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildSearchBarWidget() {
    print("_buildSearchBarWidget");

    return SearchBarWidget(
      searchTextDidChangeCallback: (text) {
        _personViewModel.loadPrefixData(text);
        _personViewModel.loadSuffixData(text);
      },
    );
  }

  Widget _buildPrefixListViewWidget() {
    print("_buildPrefixListViewWidget");

    return PrefixListViewWidget(
      personViewModel: _personViewModel,
    );
  }

  Widget _buildSuffixListViewWidget() {
    print("_buildSuffixListViewWidget");

    return SuffixListViewWidget(
      personViewModel: _personViewModel,
    );
  }
}


三砚亭、使用Provider的MVVM


首先我們要明白Provider這個框架的首要功能是數(shù)據(jù)共享,所以我們這里使用Provider的首要目的就是用它來共享_personViewModel這個數(shù)據(jù)殴玛,因為我們的ListPage捅膘、PrefixListViewWidget和SuffixListViewWidget都使用到了同一個_personViewModel,當然我們的例子可能比較簡單滚粟,【實際開發(fā)中你的一個界面里可能會由很多很多個Widget組成寻仗,而它們可能都需要使用同一個viewModel,甚至多個界面之間也需要使用同一個viewModel坦刀,那最原始的寫法就是像最原始的MVVM里那樣通過指針傳遞viewModel來共享愧沟,而Provider則提供了另外一種共享數(shù)據(jù)的方式——只要一堆Widget擁有同一個Provider作為父視圖,那么這些Widget就都可以共享這個Provider綁定的viewModel數(shù)據(jù)】鲤遥,這就是它相對于最原始MVVM的第一個變化沐寺,這個變化主要解決了viewModel傳來傳去的問題。(如果你只想一個界面內(nèi)的多個Widget共享數(shù)據(jù)盖奈,那么這多個Widget的父視圖就必須得是這個Provider混坞,所以你可以把Provider包在這個界面上即可;如果你想多個界面之間共享數(shù)據(jù)钢坦,那么這多個界面的父視圖就必須得是這個Provider究孕,因此這個時候我們會把Provider包在App的最底層)

實現(xiàn)了上面的內(nèi)容之后其實已經(jīng)完成了Provider數(shù)據(jù)共享的功能,【但是我們會發(fā)現(xiàn)使用Provider時爹凹,它要求我們的ViewModel必須繼承自或混入ChangeNotifier厨诸,這是因為Provider的第二個功能就是局部刷新,也就是說我們只需要在ViewModel里合適的時機禾酱、合適的地方發(fā)出一個notifyListeners微酬,Provider就會自動觸發(fā)它Consumer或Selector的回調(diào)來只刷新局部UI來展示最新的數(shù)據(jù)绘趋、當然我們也可以在些回調(diào)里Toast錯誤消息等。同時我們也只需要在ViewModel里發(fā)notifyListeners就行了,也不用像使用Listener那樣考慮到底是什么業(yè)務(wù)完成了而做一堆判斷,Controller里也沒有什么監(jiān)聽蛾茉,因為各個監(jiān)聽已經(jīng)分散到了各個Consumer或Selector回調(diào)那里】,這就是它相對于最原始MVVM的第二個變化帽馋,這個變化主要解決了ViewModel怎么把變化告訴Controller的問題。

【它的優(yōu)勢就是更加簡單的數(shù)據(jù)共享方式 + 響應(yīng)式編程數(shù)據(jù)驅(qū)動UI比吭≌雷澹】【它的劣勢就是Provider框架的侵入性太強了,而且代碼編寫起來有點費勁梗逮∠畋】

因此相對于最原始的MVVM來說,你有余力的話可以學學Provider并應(yīng)用在你的MVVM中慷彤。

  • App最底層
-----------main.dart-----------

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

import 'package:flutter_mvvm/classes/page/list_page.dart';
import 'package:flutter_mvvm/classes/view_model/person_view_model.dart';

void main() {
  // 第一步:創(chuàng)建數(shù)據(jù)存放地娄蔼,也就是我們想共享的view_model———即person_view_model.dart
  // 第二步:在App的最底層外再包一層ChangeNotifierProvider,并把我們需要共享的view_model傳給ChangeNotifierProvider的create屬性
  runApp(ChangeNotifierProvider(
    create: (ctx) => PersonViewModel(),
    child: MyApp(),
  ));
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: ListPage(),
    );
  }
}
  • Model層不需要改動
  • ViewModel層
-----------base_view_model.dart-----------

import 'package:flutter/cupertino.dart';

enum ViewState {
  loading, // 加載中
  success, // 加載成功
  empty, // 加載成功底哗,但數(shù)據(jù)為空
  failure, // 加載失敗
  noNetwork, // 沒網(wǎng)
}

class BaseViewModel extends ChangeNotifier {
  ViewState _state = ViewState.success;

  ViewState get state => _state;

  set state(ViewState value) {
    _state = value;
    notifyListeners();
  }
}
-----------person_view_model.dart-----------

/*
 ViewModel的職責:
  1岁诉、ViewModel負責獲取數(shù)據(jù);
  2跋选、ViewModel負責處理數(shù)據(jù)涕癣;
  3、ViewModel負責存儲數(shù)據(jù)前标。
 */

import 'dart:convert';

import 'package:flutter/services.dart';

import 'package:flutter_mvvm/classes/model/person_model.dart';
import 'package:flutter_mvvm/classes/view_model/base_view_model.dart';

/// Person視圖模型
///
/// 【變化一】:要想使用Provider坠韩,PersonViewModel得繼承自或者混入ChangeNotifier
class PersonViewModel extends BaseViewModel {
  // 命名構(gòu)造方法,專門用來初始化_personModel
  PersonViewModel._from(PersonModel? personModel) {
    _personModel = personModel;
  }

  // 持有一個_personModel炼列,以便處理數(shù)據(jù):ViewModel一對一Model地添加屬性并處理只搁,搞成getter方法即可
  PersonModel? _personModel;

  /// 普通構(gòu)造方法
  PersonViewModel();

  /// 存儲數(shù)據(jù):vm數(shù)組
  ///
  /// 真正暴露給外面使用的是vm數(shù)組,里面的數(shù)據(jù)已經(jīng)處理好了俭尖,直接拿著顯示就行了
  List<PersonViewModel> prefixPersonViewModelList = [];
  List<PersonViewModel> suffixPersonViewModelList = [];

  /// 存儲數(shù)據(jù):錯誤消息
  String errorMsg = "";

  /// 處理數(shù)據(jù):姓名
  String get name {
    return _personModel?.name ?? "";
  }

  /// 處理數(shù)據(jù):性別
  ///
  /// 0-未知氢惋,1-男,2-女
  String get sex {
    if (_personModel?.sex == 1) {
      return "男";
    } else if (_personModel?.sex == 2) {
      return "女";
    } else {
      return "未知";
    }
  }

  /// 處理數(shù)據(jù):年齡
  int get age {
    return _personModel?.age ?? 0;
  }

  /// 請求前綴數(shù)據(jù)
  Future<void> loadPrefixData(String params) async {
    await Future.delayed(Duration(seconds: 1));

    try {
      String path = "lib/assets/json/${params}_prefix.json";

      String jsonString = await rootBundle.loadString(path);
      List list = jsonDecode(jsonString);

      prefixPersonViewModelList.clear();
      for (Map<String, dynamic> map in list) {
        PersonModel personModel = PersonModel.fromJson(map);
        PersonViewModel personViewModel = PersonViewModel._from(personModel);
        prefixPersonViewModelList.add(personViewModel);
      }

      //【變化二】
      state = ViewState.success;
    } catch (error) {
      errorMsg = error.toString();

      //【變化二】
      state = ViewState.failure;
    }
  }

  /// 請求后綴數(shù)據(jù)
  Future<void> loadSuffixData(String params) async {
    await Future.delayed(Duration(seconds: 2));

    try {
      String path = "lib/assets/json/${params}_suffix.json";

      String jsonString = await rootBundle.loadString(path);
      List list = jsonDecode(jsonString);

      suffixPersonViewModelList.clear();
      for (Map<String, dynamic> map in list) {
        PersonModel personModel = PersonModel.fromJson(map);
        PersonViewModel personViewModel = PersonViewModel._from(personModel);
        suffixPersonViewModelList.add(personViewModel);
      }

      //【變化二】
      state = ViewState.success;
    } catch (error) {
      errorMsg = error.toString();

      //【變化二】
      state = ViewState.failure;
    }
  }
}
  • View層
-----------prefix_list_view_widget.dart-----------

/*
 View的職責:View負責顯示數(shù)據(jù)稽犁,那怎么顯示數(shù)據(jù)呢焰望?View可以持有ViewModel。
 */

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

import 'package:flutter_mvvm/classes/view_model/person_view_model.dart';

/// 前綴列表Widget
class PrefixListViewWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    print("【PrefixListViewWidget】---build");

    return Container(
      color: Colors.green,
      // 第三步(get的情況):在需要使用共享數(shù)據(jù)的Widget外包一層Consumer或者Selector來使用共享數(shù)據(jù)已亥,需要get共享數(shù)據(jù)的地方使用Consumer
      // Consumer是個泛型熊赖,泛的型就是問要使用哪個viewModel里的數(shù)據(jù),填進去即可
      child: Consumer<PersonViewModel>(
        // Consumer有一個必添屬性builder虑椎,接收一個函數(shù)作為屬性值震鹉,該函數(shù)的第二個參數(shù)就是共享數(shù)據(jù)存放地
        // 當數(shù)據(jù)發(fā)生變化時的妖,就會觸發(fā)這個builder函數(shù)來刷新Widget,所以我們在這個函數(shù)里返回原始的Widget足陨,從而達到Widget包裹Consumer的目的
        builder: (context, personViewModel, child) {
          print("【PrefixListViewWidget】------Consumer");

          return ListView.builder(
            itemCount: personViewModel.prefixPersonViewModelList.length,
            itemBuilder: (context, index) {
              PersonViewModel personVM =
                  personViewModel.prefixPersonViewModelList[index];

              return Row(
                mainAxisAlignment: MainAxisAlignment.spaceAround,
                children: [
                  Text(personVM.name),
                  Text(personVM.sex),
                  Text("${personVM.age}"),
                ],
              );
            },
            itemExtent: 100,
          );
        },
      ),
    );
  }
}
-----------suffix_list_view_widget.dart-----------

/*
 View的職責:View負責顯示數(shù)據(jù),那怎么顯示數(shù)據(jù)呢娇未?View可以持有ViewModel墨缘。
 */

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

import 'package:flutter_mvvm/classes/view_model/person_view_model.dart';

/// 后綴列表Widget
class SuffixListViewWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    print("【PrefixListViewWidget】---build");

    return Container(
      color: Colors.blue,
      // 第三步(get的情況):在需要使用共享數(shù)據(jù)的Widget外包一層Consumer或者Selector來使用共享數(shù)據(jù),需要get共享數(shù)據(jù)的地方使用Consumer
      // Consumer是個泛型零抬,泛的型就是問要使用哪個viewModel里的數(shù)據(jù)镊讼,填進去即可
      child: Consumer<PersonViewModel>(
        // Consumer有一個必添屬性builder,接收一個函數(shù)作為屬性值平夜,該函數(shù)的第二個參數(shù)就是共享數(shù)據(jù)存放地
        // 當數(shù)據(jù)發(fā)生變化時蝶棋,就會觸發(fā)這個builder函數(shù)來刷新Widget,所以我們在這個函數(shù)里返回原始的Widget忽妒,從而達到Widget包裹Consumer的目的
        builder: (context, personViewModel, child) {
          print("【SuffixListViewWidget】------Consumer");

          return ListView.builder(
            itemCount: personViewModel.suffixPersonViewModelList.length,
            itemBuilder: (context, index) {
              PersonViewModel personVM =
                  personViewModel.suffixPersonViewModelList[index];

              return Row(
                mainAxisAlignment: MainAxisAlignment.spaceAround,
                children: [
                  Text(personVM.name),
                  Text(personVM.sex),
                  Text("${personVM.age}"),
                ],
              );
            },
            itemExtent: 100,
          );
        },
      ),
    );
  }
}
  • Controller層
-----------list_page.dart-----------

/*
 Controller的職責:
  1玩裙、Controller負責持有View,創(chuàng)建View段直,并把View添加到窗口上顯示吃溅;
  2、Controller負責持有ViewModel鸯檬,調(diào)用ViewModel的方法去請求數(shù)據(jù)决侈;
  3、vm --> view:Controller調(diào)用vm的方法請求數(shù)據(jù)喧务,請求完成后vm是通過回調(diào)的方式告訴Controller的:
    請求成功后Controller需要調(diào)用一下setState來刷一下UI赖歌,這樣view就會去拿vm里最新存儲的數(shù)據(jù)來展示了
    請求失敗后Controller可以toast一下錯誤信息給用戶看,或者調(diào)用一下setState來刷一下UI功茴,刷成暫無數(shù)據(jù)那種view
  4庐冯、view --> vm:view產(chǎn)生的變化是通過回調(diào)告訴Controller的,Controller可以調(diào)用vm的方法把view發(fā)生的變化告訴它
 */

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:fluttertoast/fluttertoast.dart';

import 'package:flutter_mvvm/classes/widget/search_bar_widget.dart';
import 'package:flutter_mvvm/classes/widget/prefix_list_view_widget.dart';
import 'package:flutter_mvvm/classes/widget/suffix_list_view_widget.dart';
import 'package:flutter_mvvm/classes/view_model/base_view_model.dart';
import 'package:flutter_mvvm/classes/view_model/person_view_model.dart';

/// 列表界面
class ListPage extends StatefulWidget {
  const ListPage({Key? key}) : super(key: key);

  @override
  State<ListPage> createState() => _ListPageState();
}

class _ListPageState extends State<ListPage> {
  @override
  Widget build(BuildContext context) {
    print("build");

    return Scaffold(
      appBar: AppBar(
        title: Text("Flutter MVVM"),
      ),
      body: SafeArea(
        child: Column(
          children: [
            _buildSearchBarWidget(),
            Expanded(
              child: _buildTwoListViewWidget(),
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildSearchBarWidget() {
    print("_buildSearchBarWidget");

    // 第三步(set的情況):在需要使用共享數(shù)據(jù)的Widget外包一層Consumer或者Selector來使用共享數(shù)據(jù)痊土,需要set共享數(shù)據(jù)的地方使用Selector
    // Selector是個泛型肄扎,而且還泛了兩個型,第一個泛的型是問要使用哪個viewModel里的數(shù)據(jù)赁酝,填進去即可犯祠,第二個泛的型是轉(zhuǎn)換之后的數(shù)據(jù)類型,比如我這里轉(zhuǎn)換之后依然是使用PersonViewModel酌呆,那么我們填一樣的類型即可(大多數(shù)情況都不需要轉(zhuǎn)吧)
    return Selector<PersonViewModel, PersonViewModel>(
      // Selector有一個必添屬性selector衡载,接收一個函數(shù)作為屬性值,該函數(shù)的第二個參數(shù)就是共享數(shù)據(jù)存放地
      // 該函數(shù)用來指定Selector的兩個泛型之間如何進行數(shù)據(jù)轉(zhuǎn)換隙袁,這里我們不轉(zhuǎn)換痰娱,所以返回原先的personViewModel
      // 如果轉(zhuǎn)換后弃榨,則下面builder函數(shù)真正渲染W(wǎng)idget時的第二個參數(shù)就是轉(zhuǎn)換后的ViewModel了,不轉(zhuǎn)換的話builder函數(shù)的第二個參數(shù)就是原始的ViewModel
      selector: (ctx, personViewModel) {
        return personViewModel;
      },
      // Selector有一個必添屬性builder梨睁,接收一個函數(shù)作為屬性值鲸睛,該函數(shù)的第二個參數(shù)就是共享數(shù)據(jù)存放地
      // 當數(shù)據(jù)發(fā)生變化時,就會觸發(fā)這個builder函數(shù)來刷新Widget坡贺,所以我們在這個函數(shù)里返回原始的Widget官辈,從而達到Widget包裹Selector的目的
      builder: (context, personViewModel, child) {
        print("_buildSearchBarWidget---Selector");

        return Container(
          color: Colors.red,
          child: SearchBarWidget(
            searchTextDidChangeCallback: (text) {
              personViewModel.loadPrefixData(text);
              personViewModel.loadSuffixData(text);
            },
          ),
        );
      },
      // 當共享數(shù)據(jù)發(fā)生變化時,是否執(zhí)行builder方法重新構(gòu)建Widget遍坟,我們可以設(shè)定為 return pre = next拳亿,不一樣時才刷新,一樣就不刷新
      // 但是因為本次案例里是僅僅set共享數(shù)據(jù)愿伴,不需要get變化后的共享數(shù)據(jù)來刷新Widget的情況肺魁,所以這種情況我們總是返回false就ok
      shouldRebuild: (prev, next) {
        return false;
      },
    );
  }

  Widget _buildTwoListViewWidget() {
    return Consumer<PersonViewModel>(
        builder: (context, personViewModel, child) {
      switch (personViewModel.state) {
        case ViewState.success:
          return Column(
            children: [
              Expanded(
                child: _buildPrefixListViewWidget(),
              ),
              Expanded(
                child: _buildSuffixListViewWidget(),
              ),
            ],
          );
        default:
          Fluttertoast.showToast(
            msg: personViewModel.errorMsg,
            gravity: ToastGravity.CENTER,
          );

          return Center(
            child: Text("${personViewModel.errorMsg}"),
          );
      }
    });
  }

  Widget _buildPrefixListViewWidget() {
    print("_buildPrefixListViewWidget");

    return PrefixListViewWidget();
  }

  Widget _buildSuffixListViewWidget() {
    print("_buildPrefixListViewWidget");

    return SuffixListViewWidget();
  }
}


四、使用GetX的MVVM


GetX支持響應(yīng)式編程隔节,使用它可以非常簡單地實現(xiàn)數(shù)據(jù)驅(qū)動UI的效果鹅经,只需要兩步:一在ViewModel里把外界想監(jiān)聽的數(shù)據(jù)通過.obs搞成Observable,二在外界想使用數(shù)據(jù)的地方通過Obx(() => Widget)包裹真正的Widget搞成Observer官帘,這樣就可以數(shù)據(jù)驅(qū)動UI了瞬雹,這就是它相對于最原始MVVM的第一個變化,這個變化主要解決了ViewModel怎么把變化告訴Controller的問題刽虹。

GetX也支持數(shù)據(jù)共享酗捌,也只需要兩步:一在Controller里本來創(chuàng)建_viewModel的地方Get.put一下,二完事就可以在任何想使用_viewModel的地方Get.find到它來使用了涌哲,這就是它相對于最原始MVVM的第二個變化胖缤,這個變化主要解決了viewModel傳來傳去的問題。

【它的優(yōu)勢就是非常簡單地響應(yīng)式編程數(shù)據(jù)驅(qū)動UI + 非常簡單得數(shù)據(jù)共享方式阀圾,比RxDart簡單地多哪廓。】【它的劣勢就是GetX框架的侵入性太強了初烘∥姓妫】

因此相對于最原始的MVVM來說,你有余力的話可以學學GetX并應(yīng)用在你的MVVM中肾筐;相對于使用Provider的MVVM來說哆料,則強烈推薦使用GetX,它的使用簡直太簡單了吗铐。

當然GetX還提供了很多其它的功能东亦,如相對于系統(tǒng)自帶的Navigator更加簡單地路由管理App國際化等唬渗,可以根據(jù)自己的情況選擇使用典阵。

  • Model層不需要改動
  • ViewModel層
-----------base_view_model.dart-----------

import 'package:get/get.dart';

enum ViewState {
  loading, // 加載中
  success, // 加載成功
  empty, // 加載成功奋渔,但數(shù)據(jù)為空
  failure, // 加載失敗
  noNetwork, // 沒網(wǎng)
}

class BaseViewModel extends GetxController {
  Rx<ViewState> state = ViewState.success.obs;
}
-----------person_view_model.dart-----------

/*
 ViewModel的職責:
  1、ViewModel負責獲取數(shù)據(jù)壮啊;
  2嫉鲸、ViewModel負責處理數(shù)據(jù);
  3歹啼、ViewModel負責存儲數(shù)據(jù)充坑。
 */

import 'dart:convert';

import 'package:flutter/services.dart';

import 'package:flutter_mvvm/classes/model/person_model.dart';
import 'package:flutter_mvvm/classes/view_model/base_view_model.dart';

/// Person視圖模型
class PersonViewModel extends BaseViewModel {
  // 命名構(gòu)造方法,專門用來初始化_personModel
  PersonViewModel._from(PersonModel? personModel) {
    _personModel = personModel;
  }

  // 持有一個_personModel染突,以便處理數(shù)據(jù):ViewModel一對一Model地添加屬性并處理,搞成getter方法即可
  PersonModel? _personModel;

  /// 普通構(gòu)造方法
  PersonViewModel();

  /// 存儲數(shù)據(jù):vm數(shù)組
  ///
  /// 真正暴露給外面使用的是vm數(shù)組辈灼,里面的數(shù)據(jù)已經(jīng)處理好了份企,直接拿著顯示就行了
  List<PersonViewModel> prefixPersonViewModelList = [];
  List<PersonViewModel> suffixPersonViewModelList = [];

  /// 存儲數(shù)據(jù):錯誤消息
  String errorMsg = "";

  /// 處理數(shù)據(jù):姓名
  String get name {
    return _personModel?.name ?? "";
  }

  /// 處理數(shù)據(jù):性別
  ///
  /// 0-未知,1-男巡莹,2-女
  String get sex {
    if (_personModel?.sex == 1) {
      return "男";
    } else if (_personModel?.sex == 2) {
      return "女";
    } else {
      return "未知";
    }
  }

  /// 處理數(shù)據(jù):年齡
  int get age {
    return _personModel?.age ?? 0;
  }

  /// 請求前綴數(shù)據(jù)
  Future<void> loadPrefixData(String params) async {
    await Future.delayed(Duration(seconds: 1));

    try {
      String path = "lib/assets/json/${params}_prefix.json";

      String jsonString = await rootBundle.loadString(path);
      List list = jsonDecode(jsonString);

      prefixPersonViewModelList.clear();
      for (Map<String, dynamic> map in list) {
        PersonModel personModel = PersonModel.fromJson(map);
        PersonViewModel personViewModel = PersonViewModel._from(personModel);
        prefixPersonViewModelList.add(personViewModel);
      }

      loadSuffixData(params);
    } catch (error) {
      errorMsg = error.toString();

      state.value = ViewState.failure;
    }
  }

  /// 請求后綴數(shù)據(jù)
  Future<void> loadSuffixData(String params) async {
    await Future.delayed(Duration(seconds: 2));

    try {
      String path = "lib/assets/json/${params}_suffix.json";

      String jsonString = await rootBundle.loadString(path);
      List list = jsonDecode(jsonString);

      suffixPersonViewModelList.clear();
      for (Map<String, dynamic> map in list) {
        PersonModel personModel = PersonModel.fromJson(map);
        PersonViewModel personViewModel = PersonViewModel._from(personModel);
        suffixPersonViewModelList.add(personViewModel);
      }

      state.value = ViewState.success;
    } catch (error) {
      errorMsg = error.toString();

      state.value = ViewState.failure;
    }
  }
}
  • View層
-----------prefix_list_view_widget.dart-----------

/*
 View的職責:View負責顯示數(shù)據(jù)司志,那怎么顯示數(shù)據(jù)呢?View可以持有ViewModel降宅。
 */

import 'package:flutter/material.dart';
import 'package:get/get.dart';

import 'package:flutter_mvvm/classes/view_model/base_view_model.dart';
import 'package:flutter_mvvm/classes/view_model/person_view_model.dart';

/// 前綴列表Widget
class PrefixListViewWidget extends StatelessWidget {
  final _personViewModel = Get.find<PersonViewModel>();

  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.green,
      child: Obx(
        () {
          print("---PrefixListViewWidget---");

          if (_personViewModel.state.value == ViewState.failure) {
            print(_personViewModel.errorMsg);

            return Center(
              child: Text("${_personViewModel.errorMsg})"),
            );
          } else {
            return ListView.builder(
              itemCount: _personViewModel.prefixPersonViewModelList.length,
              itemBuilder: (context, index) {
                PersonViewModel personVM =
                _personViewModel.prefixPersonViewModelList[index];

                return Row(
                  mainAxisAlignment: MainAxisAlignment.spaceAround,
                  children: [
                    Text(personVM.name),
                    Text(personVM.sex),
                    Text("${personVM.age}"),
                  ],
                );
              },
              itemExtent: 100,
            );
          }
        },
      ),
    );
  }
}
-----------suffix_list_view_widget.dart-----------

/*
 View的職責:View負責顯示數(shù)據(jù)骂远,那怎么顯示數(shù)據(jù)呢?View可以持有ViewModel腰根。
 */

import 'package:flutter/material.dart';
import 'package:get/get.dart';

import 'package:flutter_mvvm/classes/view_model/base_view_model.dart';
import 'package:flutter_mvvm/classes/view_model/person_view_model.dart';

/// 后綴列表Widget
class SuffixListViewWidget extends StatelessWidget {
  final _personViewModel = Get.find<PersonViewModel>();

  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.blue,
      child: Obx(() {
        print("---SuffixListViewWidget---");

        if (_personViewModel.state.value == ViewState.failure) {
          print(_personViewModel.errorMsg);

          return Center(
            child: Text("${_personViewModel.errorMsg})"),
          );
        } else {
          return ListView.builder(
            itemCount: _personViewModel.suffixPersonViewModelList.length,
            itemBuilder: (context, index) {
              PersonViewModel personVM =
                  _personViewModel.suffixPersonViewModelList[index];

              return Row(
                mainAxisAlignment: MainAxisAlignment.spaceAround,
                children: [
                  Text(personVM.name),
                  Text(personVM.sex),
                  Text("${personVM.age}"),
                ],
              );
            },
            itemExtent: 100,
          );
        }
      }),
    );
  }
}
  • Controller層
-----------list_page.dart-----------

/*
 Controller的職責:
  1激才、Controller負責持有View,創(chuàng)建View额嘿,并把View添加到窗口上顯示瘸恼;
  2、Controller負責持有ViewModel册养,調(diào)用ViewModel的方法去請求數(shù)據(jù)东帅;
  3、vm --> view:Controller調(diào)用vm的方法請求數(shù)據(jù)球拦,請求完成后vm是通過回調(diào)的方式告訴Controller的:
    請求成功后Controller需要調(diào)用一下setState來刷一下UI靠闭,這樣view就會去拿vm里最新存儲的數(shù)據(jù)來展示了
    請求失敗后Controller可以toast一下錯誤信息給用戶看,或者調(diào)用一下setState來刷一下UI坎炼,刷成暫無數(shù)據(jù)那種view
  4愧膀、view --> vm:view產(chǎn)生的變化是通過回調(diào)告訴Controller的,Controller可以調(diào)用vm的方法把view發(fā)生的變化告訴它
 */

import 'package:flutter/material.dart';
import 'package:get/get.dart';

import 'package:flutter_mvvm/classes/widget/search_bar_widget.dart';
import 'package:flutter_mvvm/classes/widget/prefix_list_view_widget.dart';
import 'package:flutter_mvvm/classes/widget/suffix_list_view_widget.dart';
import 'package:flutter_mvvm/classes/view_model/person_view_model.dart';

/// 列表界面
class ListPage extends StatelessWidget {
  final _personViewModel = Get.put(PersonViewModel());

  @override
  Widget build(BuildContext context) {
    print("build");

    return Scaffold(
      appBar: AppBar(
        title: Text("Flutter MVVM"),
      ),
      body: SafeArea(
        child: Column(
          children: [
            _buildSearchBarWidget(),
            Expanded(
              child: _buildPrefixListViewWidget(),
            ),
            Expanded(
              child: _buildSuffixListViewWidget(),
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildSearchBarWidget() {
    print("_buildSearchBarWidget");

    return Container(
      color: Colors.red,
      child: SearchBarWidget(
        searchTextDidChangeCallback: (text) {
          _personViewModel.loadPrefixData(text);
        },
      ),
    );
  }

  Widget _buildPrefixListViewWidget() {
    print("_buildPrefixListViewWidget");

    return PrefixListViewWidget();
  }

  Widget _buildSuffixListViewWidget() {
    print("_buildPrefixListViewWidget");

    return SuffixListViewWidget();
  }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末点弯,一起剝皮案震驚了整個濱河市扇调,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌抢肛,老刑警劉巖狼钮,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件碳柱,死亡現(xiàn)場離奇詭異,居然都是意外死亡熬芜,警方通過查閱死者的電腦和手機莲镣,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來涎拉,“玉大人瑞侮,你說我怎么就攤上這事」呐。” “怎么了半火?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長季俩。 經(jīng)常有香客問我钮糖,道長,這世上最難降的妖魔是什么酌住? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任店归,我火速辦了婚禮,結(jié)果婚禮上酪我,老公的妹妹穿的比我還像新娘消痛。我一直安慰自己,他們只是感情好都哭,可當我...
    茶點故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布秩伞。 她就那樣靜靜地躺著,像睡著了一般欺矫。 火紅的嫁衣襯著肌膚如雪稠歉。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天汇陆,我揣著相機與錄音怒炸,去河邊找鬼。 笑死毡代,一個胖子當著我的面吹牛阅羹,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播教寂,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼捏鱼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了酪耕?” 一聲冷哼從身側(cè)響起导梆,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后看尼,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體递鹉,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年藏斩,在試婚紗的時候發(fā)現(xiàn)自己被綠了躏结。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡狰域,死狀恐怖媳拴,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情兆览,我是刑警寧澤屈溉,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站抬探,受9級特大地震影響语婴,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜驶睦,卻給世界環(huán)境...
    茶點故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望匿醒。 院中可真熱鬧场航,春花似錦、人聲如沸廉羔。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽憋他。三九已至孩饼,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間竹挡,已是汗流浹背镀娶。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留揪罕,地道東北人梯码。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像好啰,于是被迫代替她去往敵國和親轩娶。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,713評論 2 354

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