Understand the GetState

❓ 为什么做GetState

Flutter state management solutions flourish, from ScopeModel to Provide, MobX, BLoC, Redux, Provider. BLoC and Provider, in particular, have already had a large number of users, but I found the following problems when I actually used them:

  • Inconvenient to use, need to manually write a lot of boilerplate code, state need manual registration
  • The business logic is coupled to the UI presentation logic, or even directly to the UI.
  • In the face of large projects, it is difficult to draw a clear boundary for each level, and unit test code writing is tedious.

In the face of these problems, GetState came into being

  • Automatic registration status: Free hands and protect hair
  • Extreme speed: GetState provides O(1) time complexity access performance, beating out all O(N) state management solutions
  • Easy unit testing: Business logic is decoupled from UI code, mom doesn’t have to worry about my unit tests anymore, protect hair *2
  • State time machine: Use the Recorder to travel between the past and the present
  • Flexible usage: Supports both the mutable state used by Provider and the immutable state used by BLoC and Redux
  • Great compatibility: If you’re already using Provider, Redux, BLoC, etc., switch to GetState. You don’t need to remove the existing state management code, GetState can coexist with existing state management solutions.







GetState: An MVVM state management solution dedicated to the decoupling of Flutter application UI and business logic




Get into the business

🛸 put firstPubAs well asThe project address

Welcome to Star, PR, Issue 😘

The first three demos cover viewModels, views, and models, respectively. If you’re in a hurry, you can skip them, or read Demo3 along with Tutorial 3

Here is the Demo source for the tutorial

  • 🛴 Understand the principle of ViewModel: ViewModel debut.dart

  • 🚲 Wrap a View: Bring view.dart

  • 🛵 Custom Model: M, V, VM a neat. Dart

  • 🚗 Driver on the road: semi-automatic registration status and cross-page status modification. Dart


🛴 Learn about GetState – ViewModel (Demo0)

According to Flutter convention, the first Demo will of course be the classic CounterApp

👻 is not recommended in this example. Demo is only used to understand the GetState principle

0- Ensure that the yamL configuration is correct

dependencies:
  flutter:
    sdk: flutter
  Introduced into get_state # #
  get_state: < Fill in version number here >
Copy the code

1- Write the ViewModel class – Countervm

The ViewModel is responsible for the simple business logic and operational view

💡 Guess how complex business logic should be handled

The methods of manipulating Model here (such as incrementCounter) are equivalent to events in BLoC

The generics of the ViewModel are the types of the Model. We can use the int type directly, but we can also use the custom type. See “Recommended usage” below.

class CounterVm extends ViewModel<int> {
  // 1.1 In the construction of the ViewModel, provide default initial values
  CounterVm() : super(initModel: 0);

  // 1.2 Get the Model method, where Model is an attribute in the parent class whose type is specified by the class generics
  int counter()=> m;

  // select * from Model;
  // Call the vmUpdate(M M) method in the parent class to update the model value
  void incrementCounter() {
    vmUpdate(m + 1); }}Copy the code

2- Register ViewModel in main method (manual register)

😃 Since there is “manual registration” mode, then there must be automatic registration mode, see the following code

Use GetIt g = GetIt. Instance; Get GetIt instance.

In fact, using geti. instance or geti. I directly has the same effect, and both are singletons. It’s assigned to g here just for ease of use. Of course, the recommended name is _g

Add WidgetsFlutterBinding. The ensureInitialized (); In case the ViewModel registration fails

About WidgetsFlutterBinding. The ensureInitialized () function, here the instructions in the “post Flutter source code You only need to call this method if You need the binding to be initialized before calling [runApp].”

Use GetIt. I.r egisterSingleton < generic > (constructor); Register the ViewModel as a lazy singleton

There are more ways to register get_it, but only lazy singleton registrations are covered here for the time being

GetIt g = GetIt.instance;

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  // 4. Manually inject dependencies to ensure that the View can get the ViewModel
  g.registerSingleton<CounterVm>(CounterVm());
  runApp(MyApp());
}
Copy the code

3- Finally, the UI code calls the ViewMdoel method to manipulate and retrieve data

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) => MaterialApp(
        home: Scaffold(
          appBar: AppBar(
            title: Text('Demo :0. Minimalist use method'),
          ),
          body: Center(
            child: Text('test 0:${g<CounterVm>().counter()}'),
          ),
          floatingActionButton: FloatingActionButton(
            child: Icon(Icons.add),
            onPressed: () => g<CounterVm>().incrementCounter(),
          ),
        ),
      );
}
Copy the code

This is the end of Demo1. This example is just for understanding the GetState principle. In practice, it is not recommended to use this method. The standard version is Demo3 and then the Demo that wraps the View.








🚲 Wrap a View (Demo1)

Exposing the ViewModel and GetIt instances directly is not elegant; it is much easier to use as views

0- Make sure YAML is configured first

Yaml has the same content as Demo0

1- Write the ViewModel again

Here we use the ViewModel in Demo0 directly

2- Write the View class (MyCounterView)

View class is responsible for UI drawing, UI control logic should be as far as possible in the View, business logic can be placed in the ViewModel, a ViewModel often corresponds to multiple views, according to the Demeter principle, each View should handle UI drawing logic in its internal.

The View is the Widget that will eventually be displayed

class MyCounterView extends View<MyCounterViewModel> {
  @override
  Widget build(BuildContext c, MyCounterViewModel vm) => ListTile(
        title: Text('test 1:${vm.counter}'),
        trailing: RaisedButton(
          child: Icon(Icons.add),
          onPressed: () => vm.incrementCounter(),
        ),
      );
}
Copy the code

3- Place the View in the Widget tree

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) => MaterialApp(
        title: 'Demo :1. Primary Use method',
        home: Scaffold(
          appBar: AppBar(),
          body: Column(children: <Widget>[
            // Put the view where it is needed
            MyCounterView(),
          ]),
        ),
      );
}
Copy the code

3- Register dependencies in the main method

Here we continue to use the method in Demo0

That’s the end of the Demo wrapping the View. This works if the Model is very simple, but if the Model is very simple, there’s no point in using state management








🛵 Custom Model (Demo2)

In practice, the Model would certainly not be a basic type, or there would be no point in using state management

✨ It is recommended to follow the steps in this article if you are doing it yourself


0- Make sure YAML is configured first

dependencies:
  flutter:
    sdk: flutter
  ## 1. Introduce get_state
  get_state: ^ 3.3.0

  ## 2- You can eliminate manual overwriting == and hashCode by introducing equatable
  equatable: ^ 1.1.1
Copy the code


1 – writing Model (CounterModel)

So let’s set up a simple state with two variables inside and there are two ways to write number and STR Model, which is essentially the same, so let’s just write 1

/ / / write 1
class CounterModel {
  final int number;
  final String str;

  CounterModel(this.number, this.str);

  // todo note that == must be overridden with hashCode, otherwise it will not refresh properly
  @override
  bool operator= = (Object other) =>
      identical(this, other) ||
      other is CounterModel &&
          runtimeType == other.runtimeType &&
          number == other.number &&
          str == other.str;

  @override
  int get hashCode => number.hashCode ^ str.hashCode;
}
Copy the code

✨ here is the recommended writing method 2, using Equatable to implement the concept of “free hands, protect hair”.

Overwriting the == and hashCode methods does not generally take time, despite IDE support. However, if the Model has many fields, changing the == and hashCode methods while frequently changing the fields is too cumbersome.

Method 2: Use Equatable
class CounterModel2 extends Equatable {
  final int number;
  final String str;

  CounterModel2(this.number, this.str);

  // todo here we need to put all the property values in props
  @override
  List<Object> get props => [number, str];
  
  // ✨ tip, add the following line of code, do not even have to toString
  @override
  final stringify = true;
}
Copy the code


2 – write a ViewModel (CounterVm)

Here we use the code from Demo0


3 – write the View (MyCounterView)

Here we use the View from Demo1


4- Put the View into the Widget tree

I still use the code from Demo1


5- Finally, don’t forget to register dependencies (automatic registration is not a consideration)

Again, use the dependency registration method in Demo0


This concludes the GetState basics tutorial, isn’t it easy enough? 😎






🚗 Semi-automatic registration status and cross-page status modification (Demo3)

😀 EMMM, needless to say, there must be a method of automatic registration, but due to the limited space, automatic registration method please refer to here, there is no more detailed explanation (not recommended for beginners)


0- Make sure YAML is configured first

❗ YamL here is quite different from the previous one, pay attention to it

dependencies:
  flutter:
    sdk: flutter
  ## 1. Introduce get_state
  get_state: ^ 3.3.0

  ## 2- You can eliminate manual overwriting == and hashCode by introducing equatable
  equatable: ^ 1.2.0
  
  ## 3- Omit the manual registration step through injectable
  injectable: ^ 0.4.0 + 1

dev_dependencies:
  flutter_test:
    sdk: flutter
  ## 4-Injectable requires the following two additional dependencies
  build_runner: ^ 1.10.0
  ## 5- This is equally important
  injectable_generator: ^ 0.4.1
Copy the code


Page 1-1 A- Creating Model(CounterModel2)

This Demo will create two pages, looking at the first Page first. The Model content is basically the same as the CounterModel in the previous Demo

class CounterModel2 extends Equatable {
  final int number;
  final String str;

  CounterModel2(this.number, this.str);

  // 1. Here we need to put all the property values in props
  @override
  List<Object> get props => [number, str];
}
Copy the code


Page 1-2 A- Create ViewModel(MyCounterViewModel)

👻 be sure to add a “@lazysingleton” comment, this is part of the “semi-automatic”, don’t omit it

Not the end of the annotated light, “semi-automatic” and the other half of the operation 😜

@lazySingleton
class MyCounterViewModel extends ViewModel<CounterModel2> {
  MyCounterViewModel() : super(initModel: CounterModel2(3.'-'));

  int get counter => m.number;

  void incrementCounter() {
    vmUpdate(CounterModel2(m.number + 1.'New value')); }}Copy the code


Page 1-3 A- Create View(MyCounterView)

class MyCounterView extends View<MyCounterViewModel> {
  @override
  Widget build(BuildContext c, MyCounterViewModel vm) => ListTile(
        leading: Text('test 3:${vm.counter}'),
        title: Text('${vm.m.str}'),
        trailing: RaisedButton(
          child: Icon(Icons.add),
          onPressed: () => vm.incrementCounter(),
        ),
      );
}
Copy the code


1-4 Pages A- Place the View in the Page

This is a different MapApp, so don’t worry about the details, it’s fine

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) => Scaffold(
        appBar: AppBar(
          title: Text('Demo :3. Standard usage'),
        ),
        body: Column(children: <Widget>[
          // View 1
          MyCounterView(),
          RaisedButton(
            child: Text('Jump to new page'),
            onPressed: () => Navigator.of(context).push(MaterialPageRoute(
              builder: (c) => Page2(),
            )),
          ),
          RaisedButton(
            child: Text('Click to change the value of another page'),
            onPressed: () => g<Pg2Vm>().add,
          ),
        ]),
      );
}
Copy the code

See here, “Change status across pages” is as simple and crude as 😎

RaisedButton(
  child: Text('Click to change the value of another page'),
  onPressed: () => g<Pg2Vm>().add,
),
Copy the code


Page 2-1 B- Create Model

The MVVM family for page 1 has already been created, and page 2 is just a demonstration of cross-page state changes, so I’m just writing about it

// Page 2 does not define Model, so use int instead
Copy the code


Page B- Create ViewModel(Pg2Vm)

Again, don’t forget to add “@lazysingleton”

@lazySingleton
class Pg2Vm extends ViewModel<int> {
  Pg2Vm() : super(initModel: 3);

  String get strVal => "$m";

  get add => vmUpdate(m + 1);
}
Copy the code

2-3 page B- Create View(FooView)

Create a simple View that wraps the following ViewModel

class FooView extends View<Pg2Vm> {
  @override
  Widget build(BuildContext c, Pg2Vm vm) => RaisedButton(
        child: Text('${vm.strVal}'),
        onPressed: () => vm.add,
      );
}
Copy the code

2-4 pages B- Put the View into the Page

class Page2 extends StatelessWidget{
  @override
  Widget build(BuildContext context) => Scaffold(
        appBar: AppBar(),
        body: Center(
          child: FooView(),
        ),
      );
}
Copy the code


3-1 Initialize Injectable

Dart: Write the following functions directly in the main.dart file, or create a new dart file.

Functions must be outside the class, and those inside the class are called methods.

Also do not add the “@injecTableInit” annotation. It is recommended to copy the following code directly into your own project

When it’s done, the IDE will say “$initGetIt can’t be found”. Don’t worry, this function hasn’t been automatically generated yet

// Add annotations
@injectableInit
Future<void> configDi() async {
  $initGetIt(g);
}
Copy the code

❗ Note that the configDi method returns Future, but there is no await inside the function. This is because the currently generated dependency injection code is all synchronous, and if the @preresolve annotation is used, the generated $initGetIt() is an asynchronous method and must be await otherwise it will fail


3-2 Automatic generation of injection code

Open Terminal(or use CMD to enter the project’s lib sibling path) and type

flutter pub run build_runner build --delete-conflicting-outputs
Copy the code

Enter if you want Build_Runner to continuously generate code automatically in the background

flutter pub run build_runner watch --delete-conflicting-outputs
Copy the code

The “–delete-conflicting-outputs” parameter here means that you’re cleaning out the generated code, so don’t use this if you’ve generated code before and don’t want to restart the second generation

If the generation fails, look for the wrong code and “–delete-conflicting-outputs” will generally resolve the issue

After the code is generated, import the newly generated xxx.iconfig.dart file from the original error code.


4- Add dependency injection in main

GetIt g = GetIt.instance;

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  // 5. Add automatic dependency injection
  configDi();
  runApp(MaterialApp(home: MyApp()));
}
Copy the code








🎇🎇🎇 🎇🎇


The above Demo is the general use of get_state, but there are more tricks to get_state waiting for you to unlock 😀

The following demos rely on this file. Dart, which will not run directly through copy-and-paste, because you have not generated the dependency injection code for yourself

  • 🚙 page-level registration: the page is registered when you enter the page, and the page is destroyed when you exit. Dart

  • 🚐 ViewModel asynchronous initialization: I actually don’t find this useful. Dart

  • 🚒 State time machine jumps repeatedly between past and present. Dart





We hope you can give us a lot of praise and support, and welcome your comments and suggestions 😀

If there is time, I will make up the following tutorial 😜

subsequent

  • About the question left above

“💡 Guess how complex business logic should be handled “, see Introduction to GetArch


  • 🚙🚙 View-level registration: Register a View when you dispose it. Dart

  • 🚐 automatically generates Model classes with copyWith() : Modifying properties through copyWith() really doesn’t affect performance. Dart

  • 🚜 pass the View parameters to the ViewModel: dart

✨ ✨

Reprint without authorisation is prohibited