- The GetxController and GetX controls in GetX
- In the GetX GetBuilder
Flutter is a responsive programming architecture. In the development of Flutter, it is often encountered how to update status easily and how to quickly transfer context shared data.
GetX provides this functionality.
GetX’s official demo shows how to update the UI quickly and efficiently:
class HomePage extends StatelessWidget {
final model = CountModel();
@override
Widget build(BuildContext context) {
return Scaffold(
// Use Obx(()=> update Text() whenever the count changes.
appBar: AppBar(title: Obx(() => Text("Clicks: ${model.count}"))), body: Center(), floatingActionButton: FloatingActionButton( child: Icon(Icons.add), onPressed: () => model.count++, )); }}class CountModel {
var count = 0.obs;
}
Copy the code
In the above code, some of the official code was changed to simplify the code and highlight the focus
As you can see from the above code, we use a StatelessWidget to do the UI update, which is pretty amazing.
This code differs from our usual dynamic UI updates in three ways: 0. Obs in CountModel, use of StatelessWidget, and Obx(() => Text(“Clicks: ${model.count}”).
Let’s take a look at the new obS and Obx implementations and try to figure out where the magic comes from.
0. Magic in OBS
Dart: Dart: 0. Obs: 0. Obs: 0
// rx_impl.dart
extension IntExtension on int {
/// Returns a `RxInt` with [this] `int` as initial value.
RxInt get obs => RxInt(this);
}
extension StringExtension on String {
/// Returns a `RxString` with [this] `String` as initial value.
RxString get obs => RxString(this);
}
extension DoubleExtension on double {
/// Returns a `RxDouble` with [this] `double` as initial value.
RxDouble get obs => RxDouble(this);
}
extension BoolExtension on bool {
/// Returns a `RxBool` with [this] `bool` as initial value.
RxBool get obs => RxBool(this);
}
extension RxT<T> on T {
/// Returns a `Rx` instace with [this] `T` as initial value.
Rx<T> get obs => Rx<T>(this);
}
Copy the code
GetX supports String, Int, Double, Bool, and T, so any type can use.obs.
Taking int as an example, take a closer look at the implementation.
We know that a class in Dart can integrate the functionality of another class through extends, Implement, and mixin. If inheritance alone is not enough to describe the three implementations. Extends is a traditional inheritance, implements is a traditional interface implementation, and mixin is a way of directly integrating the functionality of other classes in Dart.
When we call 0.obs, its return type is actually RxInt.
The inheritance relationship of RxInt class is shown as follows:
The other RxString, RxDouble, RxBool, and Rx classes are subclasses of _RxImpl. The indirect parent class of RxInt, for example, supports addition, subtraction, multiplication, and division. These capabilities come from the indirect parent class _BaseRxNum and itself.
So we can guess that _RxImpl is actually the root of magic support.
The _RxImpl class consists of two parts, RxObjectMixin, which comes in from mixin, and RxNotifier, which comes in from mixin.
-
RxNotifier RxObjectMixin RxObjectMixin RxNotifier
// Bind data T _value; // Refresh to the subject method void refresh() { subject.add(value); } // Set a null value void nil() { subject.add(_value = null); } // Provide direct parentheses for syntactic sugar calls T call([T v]) { if(v ! =null) { value = v; } return value; } / /... Omitted some hash == and other methods // Set value, do not update if it is not the first build and the value is the same. // Put the new value into subject set value(T val) { if(_value == val && ! firstRebuild)return; firstRebuild = false; _value = val; subject.add(_value); } // When the value is obtained, check whether the proxy is null. If the value is not null, use subject to listen on the proxy T get value { if(RxInterface.proxy ! =null) { RxInterface.proxy.addListener(subject); } return _value; } Copy the code
As you can see from the above code, RxObjectMixin provides methods such as data binding and easy refreshing.
If the subject does not belong to the RxObjectMixin class, it must come from RxNotifier.
The important thing to note here is rxinterface.proxy in the get method of value, which we’ll come back to later.
-
The RxNotifier has only two parent classes, so the RxNotifier has only two parent classes, so the RxNotifier has only two parent classes, so the RxNotifier has only two parent classes.
The figure shows that RxNotifier implements the RxInterface interface. The code for RxNotifier is as follows:
class RxNotifier<T> = RxInterface<T> with NotifyManager<T>; Copy the code
As we can see, RxNotifier has no entity of its own, so methods defined by RxInterface are implemented by NotifyManager.
And NotifyManager class does realize RxInterface function definition, general source code is as follows:
// The low-level listener uses streams GetStream<T> subject = GetStream<T>(); // Stream subscribers final _subscriptions = <GetStream, List<StreamSubscription>>{}; bool get canUpdate => _subscriptions.isNotEmpty; // Add the add callback of the current stream to the event listener of the input stream rxGetx. When rxGetx changes it calls back subject.add and adds subscribers to _SUBSCRIPTIONS for closure and resource recycling. void addListener(GetStream<T> rxGetx) { if(! _subscriptions.containsKey(rxGetx)) {final subs = rxGetx.listen(subject.add); final listSubscriptions = _subscriptions[rxGetx] ??= <StreamSubscription>[]; listSubscriptions.add(subs); } } // Add a listener when the event flow changes StreamSubscription<T> listen( void Function(T) onData, { Function onError, void Function() onDone, bool cancelOnError = false, }) => subject.listen(onData, onError: onError, onDone: onDone, cancelOnError: cancelOnError); // Close resources such as subscribers void close() { .... } Copy the code
As you can see from the source code above, RxNotifier provides two things: the real stream GetStream, and forwarding event changes from other GetStreams to the currently held GetStream.
GetStream provides management and distribution of all stream events.
-
GetStream GetStream provides a set of listener methods to listen for the state of the stream. Provides a set of management methods for managing subscribers and event distribution. The source code is as follows:
class GetStream<T> { // Callback method when a new listener is added void Function() onListen; // Callback when the subscriber pauses void Function() onPause; // Callback when the subscriber resumes void Function() onResume; // The current stream cacenl callback FutureOr<void> Function() onCancel; // Remove the subscriber for the current stream FutureOr<bool> removeSubscription(LightSubscription<T> subs); // Add a subscriber to the current stream FutureOr<void> addSubscription(LightSubscription<T> subs); Update all subscribers when data changes void _notifyData(T data); // Notify all subscribers when an error occurs void _notifyError(Object error, [StackTrace stackTrace]); // Notify the subscriber when the stream is complete void _notifyDone(); // The last event object in the current stream T _value; // Add events void add(T event) { _value = event; // Forward to _notifyDate() _notifyData(event); } // Add a new listener to the current stream LightSubscription<T> listen(void Function(T event) onData,{Function onError, void Function() onDone, bool cancelOnError}) { finalsubs = LightSubscription<T>(removeSubscription,onPause: onPause,onResume: onResume,onCancel: onCancel,) .. onData(onData) .. onError(onError) .. onDone(onDone) .. cancelOnError = cancelOnError; addSubscription(subs); onListen? .call();return subs; } // Return GetStream as a stream. Stream<T> get stream => GetStreamTransformation(addSubscription, removeSubscription); } Copy the code
In the source code above, LightSubscription is used, which is a simple wrapper for subscribers and can be viewed by those interested.
All observable classes are subclasses of **_RxImpl **, which provides capabilities for data binding (RxObjectMixin) and event management and distribution of data changes (RxNotifier)**.
But to whom does the event go? We would hazard a guess based on the counter functionality that data change events might be distributed to Obx(), prompting Obx to update and ultimately the UI.
Obx(() => Text(“Clicks: ${model.count}”))
In Flutter, if the UI needs to be updated, we assume that GetX is also using the StatefulWidget internally, without using dark magic.
Obx -> ObxWidget -> StatefulWidget.
As you can see, the Obx update is indeed based on StatefulWidget.
ObxWidget (**_ObxState**)
class _ObxState extends State<ObxWidget> {
// The observer object currently held
RxInterface _observer;
// The current subscriber object
StreamSubscription subs;
_ObxState() {
// RxNotifier, mentioned above, provides listening management and distribution of events
_observer = RxNotifier();
}
@override
void initState() {
// Add a listener to the observer and trigger _updateTree when the _observer content changes
subs = _observer.listen(_updateTree);
super.initState();
}
// Call setState to trigger the update
void _updateTree(_) {
if(mounted) { setState(() {}); }}// Close the observer and cancel the subscriber
@override
void dispose() {
subs.cancel();
_observer.close();
super.dispose();
}
// Call build in widget to update child.
Widget get notifyChilds {
final observer = RxInterface.proxy;
RxInterface.proxy = _observer;
final result = widget.build();
if(! _observer.canUpdate) {// Throw an error, omit...
}
RxInterface.proxy = observer;
return result;
}
@override
Widget build(BuildContext context) => notifyChilds;
}
Copy the code
** _ObxState ** obxstate ** obxstate ** obxstate
Update the current widget when the _observer changes. When the current widget is reclaimed, close the _Observer and unsubscribe the subscriber.
However, it has not been discovered how to trigger widget changes when counter changes.
RxObjectMixin = getvalue (); RxObjectMixin = getvalue (); RxObjectMixin = getvalue ();
// When the value is obtained, check whether the proxy is null. If the value is not null, use subject to listen on the proxy
T get value {
if(RxInterface.proxy ! =null) {
RxInterface.proxy.addListener(subject);
}
return _value;
}
Copy the code
The notifyChilds method in _ObxState is:
// Call build in widget to update child.
Widget get notifyChilds {
final observer = RxInterface.proxy;
RxInterface.proxy = _observer;
final result = widget.build();
if(! _observer.canUpdate) {// Throw an error, omit...
}
RxInterface.proxy = observer;
return result;
}
Copy the code
In this method, there are also operations on rxInterface.proxy. He stores the original proxy into temporary variables, sets the current _observer to global proxy, and calls Build. Then restore the original proxy. A honey operation…
Let’s look at the call link to notifyChilds as follows:
As you can see, in (3) _ObxState sets proxy to _observer, and in (6) Text(“Clicks: ${model.count}”) converts String to Text(“Clicks: ${model.count.toString()}”) triggers toString() of (7), and toString() is implemented as: Value.tostring (), which uses the value object, triggers the get methods (8) and (9) of value. In the get method, the _observer(object in _ObxState) set in (3) is used to listen for event changes on the current counter. After a series of calls, the proxy is restored to the original proxy in (13).
This also explains the honey operation of replacing the proxy in the notifyChilds() method.
When _ObxState is reclaimed, the event subscriber is reclaimed and the current _Observer object is closed.
conclusion
Let’s go back to the drawing board.
Count extends an Rx object that supports data binding (RxObjectMixin) and data event distribution (RxNotifier).
When the system builds the UI, the _ObxState build() method implicitly calls count.tostring (), and the rxObjectMixin.tostring () method implicitly calls the value get method. In the get method, due to the previous proxy exchange operation, the current proxy is the current ** _obxstate._observer **, and finally the data change flow of value is integrated into ** _obxstate._observer **.
When count changes, it fires its own event stream, along with the _obxstate._observer event stream, which ultimately updates the UI.
This step proxy conversion and value get operation is a masterstroke, admire ~
At the same time, according to the source code, it is not easy to change the proxy in the BUILD callback of Obx, otherwise it will lead to the object that the proxy links to is wrong, so that the event cannot be correctly distributed, and the UI cannot be updated.
But in Obx you can nest Obx multiple layers at the same time, because each build reverts to the previous proxy.
Source code GetX(two) : GetxController and GetX controls