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