Provider principle introduction and actual combat (MVVM) (below)

This is the 19th day of my participation in the Gengwen Challenge. For details of the event, please check:Update Challenge

What is MVVM and its advantages

MVVM is shorthand for Model-View-ViewModel. It is essentially an improved version of MVC. MVVM abstracts the state and behavior of the View, allowing us to separate the view UI and business logic. Of course, the ViewModel has already done these things for us. It can take out the data of the Model and help deal with the business logic involved in the View due to the need to display the content.

advantage

The MVVM pattern is the same as the MVC pattern, the main purpose is to separateview(View) and Model (Model),

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

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

3. Independent development . Developers can focus on the development of business logic and data (ViewModel), designers can focus on page design, using Expression Blend can easily design the interface and generate xaml code.

4. Testable . The interface has always been difficult to test, and the test 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 UI for the application.

img

Several ways of MVVM pattern in Flutter

When not using any third-party packages, the official also provides a good choice, that is StatefulWidget, when we need to change the state to refresh the UI, we only need to call the setState()method.

This method is simple and direct, and can also be understood as an MVVM pattern, but View and Model are still coupled together, and ViewModel does not play its due role. As our projects get bigger, the code setState()will become more and more confusing, and sometimes forget to call setState(), causing a lot of wasted time to locate the problem.

A state management mode also provided by the official early is called BLOC. This method relies on third-party packages rxDartand solves the problem well in the way of Stream setState(). However, this kind of learning is difficult and not friendly to newcomers to Flutter. Later, a third-party library appeared Provider, which is an advanced tool for state management and dependency injection, and is easy to learn and understand, so it is currently recommended as the first choice Provider.

State management with ViewModel

Since we develop APP in MVVM mode, ViewModel is essential. That is, when the state property changes, we need the UI (that is, the View layer) to make corresponding changes.

There is a Provider that ChangeNotifierProvidercan help us monitor whether the state has changed, and its child parameter is a change that Consumercan help us consume the state. In layman's terms, the build method of the Widget is called here to refresh the UI.

So where to trigger the notification of state change? The answer is to use ChangeNotifier, when the notifyListeners()method is called, you can notify the listener ChangeNotifierProviderto refresh it.

@override
Widget build(BuildContext context) {
  return ChangeNotifierProvider<T>(
    child: Consumer<T>(
      //Widget的builder方法与child
      builder: widget.builder,
      child: widget.child,
    ),
    create: (BuildContext context) {
      //这里是我们的ViewModel,一个ChangeNotifier
      return model;
    },
  );
}
复制代码

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

Basic page logic analysis

Generally, a loading page will be displayed when the page is loaded, and then the data will be displayed successfully after loading, and the failed page will be displayed if it fails.

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

So enumerate a page state:

enum ViewState { Idle, Busy,Error }
复制代码

ViewModel will update ui after page state property changes, usually call notifyListeners, move this step to BaseModel:

class BaseModel extends ChangeNotifier {
  ViewState _state = ViewState.Loading;
 
  ViewState get state => _state;
 
  void setState(ViewState viewState) {
    _state = viewState;
    notifyListeners();
  }
}
复制代码

ChangeNotifierProvider needs to provide Model in ui, and update ui with Consumer. So we also built it into BaseView:


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>();
}
 
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,
      ),
    );
  }
}
复制代码

We complete the login page with the encapsulated 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("登录")),
          ],
        ),
      ),
    );
  }
}
 
/// viewModel
class LoginViewModel extends BaseModel {
  LoginServive _loginServive;
  String info = '请登录';
 
  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), () => "登录成功");
  }
}
复制代码

refer to

examplecode.cn/2020/05/09/…

Guess you like

Origin juejin.im/post/6975904656245391373