- Magic in Obx
- In the GetX GetBuilder
In GetX(1) we used the official demo, and the source code we posted was simply changed.
The main change is to change CountModel from a GetxController subclass to a normal object.
Let’s look at GetxController source code.
1. Implementation of GetxController
Among other things, let’s take a look at GetxController’s inheritance structure, as shown below:
As you can see from the above figure, GetxController provides the lifecycle and batch update capability by inheriting DisposableInterface and Mixin ListNotifier.
-
DisposableInterface
DisposableInterface provides the following lifecycle methods by inheriting GetLifeCycle:
- OnStart is a Function type, but exists as a final variable to prevent subclass overrides. This method is called by the framework when the widget is loaded into memory. The default implementation of this method checks to see if it has been initialized, and if not, calls onInit(), so calling onStart multiple times will only execute onInit() once.
- OnInit () this method is called after the widget is loaded into memory. Generally used to initialize some objects that need to be used later.
- OnReady () this method calls a frame later than onInit() and is typically used to do things like snackbar, new routes, etc., that need to be done after the page init is complete.
- OnDelete, like onStart, is a Function for the final variable type of the framework call. OnClose () is called associatively by default and is executed multiple times, and only once.
- OnClose () This method is used to dispose resources, similar to the Dispose method in widgets.
The **onReady() method will be called in the next frame of onInit()**.
@override @mustCallSuper void onInit() { super.onInit(); SchedulerBinding.instance? .addPostFrameCallback((_) => onReady()); }Copy the code
We see to achieve this effect, used here is SchedulerBinding instance? AddPostFrameCallback way, similarly addPersistentFrameCallback method, children’s shoes are interested can know about the Flutter in the loading process.
-
ListNotifier since is together to see the source, that first pickled cabbage:
class ListNotifier implements Listenable { // Ignore some unimportant code List<GetStateUpdate> _updaters = <GetStateUpdate>[]; HashMap<Object.List<GetStateUpdate>> _updatersGroupIds = HashMap<Object.List<GetStateUpdate>>(); @protected void refresh() { if (_microtask == _version) { _microtask++; // Update asynchronouslyscheduleMicrotask(() { _version++; _microtask = _version; _notifyUpdate(); }); }}// Simple update logic void _notifyUpdate() { for (var element in_updaters) { element(); }}// Update by group ID void _notifyIdUpdate(Object id) { if (_updatersGroupIds.containsKey(id)) { final listGroup = _updatersGroupIds[id]; for (var item inlistGroup) { item(); }}}@protected void refreshGroup(Object id) { // Update asynchronously if(_microtask == _version) { _microtask++; scheduleMicrotask(() { _version++; _microtask = _version; _notifyIdUpdate(id); }); }}// Add the setState method to the update queue _updaters @protected void notifyChildrens() { TaskManager.instance.notify(_updaters); } // Delete some methods to add and delete listeners } Copy the code
As you can see from the source code above, ListNotifier provides two ways of registering updates: registering updates individually and registering updates as a group.
We know that a Flutter runs on a single thread and two queues are maintained during Flutter startup: EventQueue and MicroTaskQueue.
Each time a Ticker triggers, two queues are executed, with MicroTaskQueue having priority over EventQueue. The contents of the EventQueue are executed only after the task completes execution.
The scheduleMicrotask() method is used to perform the specific update operation, which can be deferred to the next Ticker execution and executed before the next drawing without blocking the caller.
2. Automatic update of GetX
GetX is described in the official demo as follows:
// controller
final count1 = 0.obs;
final count2 = 0.obs;
int get sum => count1.value + count2.value;
/ / view
GetX<Controller>(
builder: (controller) {
print("count 1 rebuild");
return Text('${controller.count1.value}');
},
),
GetX<Controller>(
builder: (controller) {
print("count 2 rebuild");
return Text('${controller.count2.value}');
},
),
GetX<Controller>(
builder: (controller) {
print("count 3 rebuild");
return Text('${controller.sum}'); },),Copy the code
If we increment **count1.value++**, it will print:
- count 1 rebuild
- count 3 rebuild
If we change count2.value++, it will print.
- count 2 rebuild
- count 3 rebuild
Since count2.value has changed, sum is now 2.
From the Demo, there are three possible problems:
- How does GetX automatically update?
- How do Controller and GetX interact?
- Sum did not use Rx, but when count1 and count2 were updated, he also triggered the update. How did he do that?
In the process of looking at the source code, with these three questions, we step by step to find the answer.
2.1. Automatic update of GetX
In our previous article, we looked at how Obx updates automatically. We saw how Obx uses global proxy substitution and how it uses the GET method of Rx objects.
We hypothesize that GetX is also a UI redraw based on the capabilities of StatefulWidget and uses the same pattern for event flow listening.
GetX = GetX
class GetX<T extends DisposableInterface> extends StatefulWidget {
final GetXControllerBuilder<T> builder;
final bool global;
final bool autoRemove;
final bool assignId;
final void Function(State state) initState, dispose, didChangeDependencies;
final void Function(GetX oldWidget, State state) didUpdateWidget;
final T init;
final String tag;
const GetX({
this.tag,
this.builder,
this.global = true.this.autoRemove = true.this.initState,
this.assignId = false.this.dispose,
this.didChangeDependencies,
this.didUpdateWidget,
this.init,
});
@override
GetXState<T> createState() => GetXState<T>();
}
Copy the code
See from the above source code:
- GetX is indeed a UI update using the StatefulWidget
- The input parameter generic T of GetX is a subclass of DisposableInterface, which we’ve already analyzed, and which DisposableInterface provides the function of declaring cycles. So type T has a declaration period, which could be type GetxController.
- GetX provides callback methods similar to those found in State to give State callback timing to the consumer.
- GetX also provides input parameters such as global and Tag. Let’s look at the specific effects of State analysis.
GetX is a StatefulWidget that corresponds to the GetXState class.
class GetXState<T extends DisposableInterface> extends State<GetX<T>> {
GetXState() {
_observer = RxNotifier();
}
RxInterface _observer;
// Omit some code
Widget get notifyChildren {
final observer = RxInterface.proxy;
RxInterface.proxy = _observer;
final result = widget.builder(controller);
if(! _observer.canUpdate) {throw "Error message";
}
RxInterface.proxy = observer;
return result;
}
@override
Widget build(BuildContext context) => notifyChildren;
}
Copy the code
As you can see from the above source code, GetX also uses proxy exchange techniques to set up observers remotely, as we might expect.
2.2. Interaction between GetX and GetxController
In addition to the above source code, State has a part of the source code for interacting with the Controller, as follows:
/ / GetXState source code
T controller;
bool isCreator = false;
StreamSubscription subs;
@override
void initState() {
/ / 1
var isRegistered = GetInstance().isRegistered<T>(tag: widget.tag);
/ / 2
if (widget.global) {
if (isRegistered) {
if (GetInstance().isPrepared<T>(tag: widget.tag)) {
isCreator = true;
} else {
isCreator = false;
}
controller = GetInstance().find<T>(tag: widget.tag);
} else {
controller = widget.init;
isCreator = true; GetInstance().put<T>(controller, tag: widget.tag); }}else {
/ / 3
controller = widget.init;
isCreator = true; controller? .onStart(); }/ / 4widget.initState? .call(this);
if(widget.global && Get.smartManagement == SmartManagement.onlyBuilder) { controller? .onStart(); }/ / 5
subs = _observer.listen((data) => setState(() {}));
super.initState();
}
Copy the code
The above code does the following:
- The GetInstance class provides instance sharing and management across pages. Let’s put that aside for the moment. The isRegistered() method is used to determine whether the specified tag object of the specified type has been registered in the framework.
- If the Controller object is a global object (global=true), then check whether the object is ready. In GetInstance, isPrepared() returns false if an object is already init or not registered, so isRegistered() is used to make sure the object isRegistered, and isPrepared() is used to determine if the object is loaded.
- If loaded, mark the creator of the Controller as the current state, that is, isCreator=true.
- Initialize the object with widget.init, register it in Get, and set isCreator=true
- If the Controller object is local (global=false) and sets widget.init directly to Controller and calls its onStart() method, turn on the life cycle.
- As we know from analyzing GetxController, onStart() calls onInit(), so controller.oninit () corresponds to state.oninit (). The onReady() method is called the next frame, after onInit().
- Get. SmartManagement is a management policy for shared objects in GetX, which we won’t discuss for now.
- Set setState to be called when the data flow in the Observer changes. Similar to Obx, but Obx determines the current State and feels more secure.
Please dispose of it in the GetX control in accordance with isCreator. Please dispose of it in the GetX control in accordance with isCreator.
After looking at the GetState source code, let’s go back and take a closer look at the GetX definition:
class GetX<T extends DisposableInterface> extends StatefulWidget {
final GetXControllerBuilder<T> builder;
final bool global;
final bool autoRemove;
final bool assignId;
final void Function(State state) initState, dispose, didChangeDependencies;
final void Function(GetX oldWidget, State state) didUpdateWidget;
final T init;
final String tag;
const GetX({
this.tag,
this.builder,
this.global = true.this.autoRemove = true.this.initState,
this.assignId = false.this.dispose,
this.didChangeDependencies,
this.didUpdateWidget,
this.init,
});
}
Copy the code
- Tag We know from the State source code that this tag defines the key value when retrieving a shared variable from Get. So if two pages share the same instance, they must be consistent when the tag is customized.
- Builder builds real display widgets.
- Global defines whether the controller variable is global.
- If it is not global, init is used as the Controller and is recycled following the lifetime of the current Widget.
- If it is global, it looks for shared instances in Get.
- If not, use the init parameter as the default controller and add it to Get for management. The autoRemove parameter is required to determine whether to reclaim.
- If so, reuse the shared instance.
- AutoRemove If it determines that the current instance is created by the current widget, it judges this variable and disclaims the shared instance at state.dispose ().
- The initState, Dispose, didChangeDependencies, didUpdateWidget methods call back when the corresponding State method is called.
- AssignId If this variable is true, then instances of controller in Get, whether or not they are currently created, will attempt to reclaim according to autoRemove.
- Init default Controller object.
- If global=false, use this entry as controller directly. And when dispose, delete according to autoRemove
- If global=true, an attempt is made to load a shared instance of the specified tag and type from Get. If not, init is used as the default argument. Similarly, dispose is disposed according to whether it is the current create or assignId and whether it is autoRemove
GetX is a responsive automatic update that can set controller shared objects and support automatic collection of Controllers. But because the Controller object here is not really GetxController, updates within the Controller are not supported.
GetX and Obx both update automatically in response, but the biggest difference is that GetX supports many State lifecycle callbacks, as well as instance sharing and lifecycle triggering for Controllers.
Think of Obx as a lightweight GetX implementation.
2.3. How is SUM updated
Sum does not use Rx, but when count1 and count2 are updated, he also triggers an update.
As we know from analyzing GetX’s code, GetX uses the same proxy swap technique as Obx to provide responsive updates.
When sum is used to build the third GetX, it will use count1 and count2 at the same time. When the get method is called, the proxy is the observer of the current GetX. So both count1 and count2 set the observer in GetX to the stream listener. Count1 is now listened on by GetX1 and GetX3, and count2 is listened on by GetX2 and GetX3, so any change to either of them triggers an update to GetX3.
3. Summary
GetxController provides the lifecycle and batch update capability by inheriting DisposableInterface and Mixin ListNotifier.
While GetX requires the input parameter to DisposableInterface, which implements the maintenance of the life cycle of the shared instance. Same as Obx, proxy exchange is used to achieve responsive update.
The update capability we have here in GetxController is not used in GetX (the entry parameter is of type DisposableInterface), but will be used later in GetBuilder.
Source code GetX(1) : start with the official demo