This is the 19th day of my participation in the Genwen Challenge

What is MVVM and its advantages

MVVM is short for model-view-viewModel. It’s essentially an improved version of MVC. MVVM abstracts the state and behavior of the View in it, and lets separate the View UI from the business logic. Of course, the ViewModel already does this for us, fetching the data from the Model and helping with the business logic involved in displaying content in the View

advantages

The MVVM pattern, like the MVC pattern, mainly aims to separate views from models.

1. Low coupling. Views can be changed and modified independently of Model. A ViewModel can be bound to different “views”. The Model can remain unchanged when the View changes, and the View can remain unchanged when the Model changes.

2. Reusability. You can put some view logic in a ViewModel and have many views reuse that view logic.

3. Develop independently. Developers can focus on business logic and data development (ViewModel), designers can focus on page design, and Expression Blend makes it easy to design interfaces and generate XAML code.

4. Testable. Interfaces are harder to test, and tests can be written against the ViewModel.

The View binds to the ViewModel and then executes some commands to request an action from it. In turn, the ViewModel communicates with the Model, telling it to update in response to the UI. This makes it very easy to build the UI for your application.

Several modes of THE MVVM mode in Flutter

The official alternative to using any third-party packages is the StatefulWidget, which simply calls the setState() method when we need to change state to refresh the UI.

This approach is straightforward and can be understood as an MVVM pattern, except that the View and Model are still coupled and the ViewModel does not play its role. As our project got bigger, the setState() in our code became more and more confusing, and sometimes we forgot to call setState(), wasting a lot of time trying to locate the problem.

An early state management model that officials also offered was called BLOC. This approach relies on the third-party package rxDart, which nicely solves the setState() problem as a Stream. But this is difficult to learn and not friendly to new Flutter users. Later, there emerged a third-party library Provider, which is an advanced tool for state management and dependency injection and easy to learn and understand. Therefore, it is also officially recommended as the preferred Provider.

Use the ViewModel for state management

Since we are developing our APP in MVVM mode, the ViewModel is essential. When the state properties change, we need the UI (the View layer) to change accordingly.

Providers have a ChangeNotifierProvider to help us listen for state changes, and its child is a Consumer to help us consume state changes. This is where the Widget’s build method is called to refresh the UI.

So where do I trigger notifications of state changes? The answer is to use the ChangeNotifier to notify the ChangeNotifierProvider listening on it to refresh when the notifyListeners() method is called.

@override
Widget build(BuildContext context) {
  return ChangeNotifierProvider<T>(
    child: Consumer<T> (//Widget builder method with child
      builder: widget.builder,
      child: widget.child,
    ),
    create: (BuildContext context) {
      // Here is our ViewModel, a ChangeNotifier
      returnmodel; }); }Copy the code

Now that we have producers and consumers, we can refine our MVVM pattern.

Basic page logic analysis

In general, loading a page will display a loading state. After loading data successfully, the page fails to display

! [Screenshot 2021-06-20 PM 11.47.16](/Users/ Liaoyp /Desktop/ Screenshot 2021-06-20 PM 11.47.16.png)

So enumerate a page state:

enum ViewState { Idle, Busy,Error }
Copy the code

The ViewModel updates the UI whenever the page state property changes, often calling notifyListeners to move this step into BaseModel:

class BaseModel extends ChangeNotifier {
  ViewState _state = ViewState.Loading;
 
  ViewState get state = > _state;
 
  void setState(ViewState viewState) {
    _state =viewState; notifyListeners(); }}Copy the code

The ChangeNotifierProvider needs to provide the Model in the UI and update the UI with Consumer. So we built it into BaseView as well:


enum ViewState { Loading.Success.Failure.None }
 
class BaseModel extends ChangeNotifier {
  ViewState _state = ViewState.None;
 
  ViewState get state = > _state;
 
  void setState(ViewState viewState) {
    _state =viewState; notifyListeners(); }}class BaseWidget<T extends ChangeNotifier> extends StatefulWidget {
  final Widget Function(BuildContext context, T model, Widget child) builder;
  final T model;
  final Widget child;
  final Function(T) onModelReady;
 
  BaseWidget({
    Key key,
    this.builder,
    this.model,
    this.child,
    this.onModelReady,
  }) : super(key: key);
 
  _BaseWidgetState<T> createState() = > _BaseWidgetState<T>(a); }class _BaseWidgetState<T extends ChangeNotifier> extends State<BaseWidget<T>> {
  T model;
 
  @override
  void initState() {
    model = widget.model;
 
    if (widget.onModelReady ! = null) {
      widget.onModelReady(model);
    }
 
    super.initState();
  }
 
  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider<T>(
      create: (BuildContext context) = > model,
      child: Consumer<T>( builder: widget.builder, child: widget.child, ), ); }}Copy the code

We complete the login page with the wrapped base class:


class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return BaseWidget<LoginViewModel>(
      model: LoginViewModel(loginServive: LoginServive()),
      builder: (context, model, child) = > Scaffold(
        appBar: AppBar(
          title: Text('provider'),
        ),
        body: Column(
          children: <Widget>[
            model.state = = ViewState.Loading
                ? Center(
                    child: CircularProgressIndicator(),) :Text(model.info),
            FlatButton(
                color: Colors.tealAccent,
                onPressed: () = > model.login("pwd"),
                child: Text("Login"() [() [() [() [() }}/// viewModel
class LoginViewModel extends BaseModel {
  LoginServive _loginServive;
  String info ='Please log in ';LoginViewModel({@required LoginServive loginServive})
      : _loginServive = loginServive;
 
  Future<String> login(String pwd) async {
    setState(ViewState.Loading);
    info = await _loginServive.login(pwd);
    setState(ViewState.Success); }}/// api
class LoginServive {
  static const String Login_path = 'xxxxxx';
 
  Future<String> login(String pwd) async {
    return new Future.delayed(const Duration(seconds: 1), () = > "Login successful"); }}Copy the code

reference

Examplecode. Cn / 2020/05/09 /…