I’m participating in nuggets Creators Camp # 4, click here to learn more and learn together!

Obx is a Widget component of GetX library that combines Rx observable to implement state management. It makes Flutter development faster and easier to implement state management, and makes code more concise.

For more on the use of GetX, see the following article

  • GetX integration and Usage of Flutter application framework

  • Flutter dissects the implementation of Getx dependency management step by step through the source code

  • Use of Flutter GetX dependency injection in detail

  • The use of Flutter GetX dependency injection tag is explained

  • Detailed explanation of Flutter GetX dependency injection Bindings usage

  • The GetBuilder for Flutter state management uses detailed explanation and source code analysis

use

Start with a simple example of how Obx is used to implement the official counter effect of Flutter:

class CounterPage extends StatelessWidget {

  var count = 0.obs;
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text("Counter")),
      body: Center(
        child: Obx(() {
          return Text("${count.value}", style: const TextStyle(fontSize: 50)); }), ), floatingActionButton: FloatingActionButton( child:const Icon(Icons.add),
        onPressed: (){
          count += 1; },),); }}Copy the code

Effect:

Remove the other interfering codes above, and the core codes are as follows:

var count = 0.obs;

Obx(() {
  return Text("${count.value}");
});
  
count += 1;
Copy the code

The whole implementation is divided into three steps: create data objects, use data in Obx, and update data

1. Create a data object

Obs is of type Rx. Obs can also be used directly to create an observable. Obs is of type Rx.

var count = Rx(0);
/// or
Rx<int> count = Rx(0);
Copy the code

Rx is a container that holds a generic value, that is, it can create observable objects of any type.

In addition to using Rx directly, GetX also provides Rx encapsulation for common types such as RxInt, RxDouble, RxBool, RxString, RxNum, RxList, RxMap, etc. The type created using 0.obs in the above example is actually RxInt. The encapsulated Rx types extend common methods and operators, and can be used as if they were real values. Here’s the difference between RxInt and Rx

:

/// create
RxInt a = RxInt(0);
/// or
RxInt a = 0.obs;

Rx<Int> b = Rx(0);

/// use
a += 1;

b += 1; /// An error
b.value += 1;

print(a > 0); /// true
print(b > 0); /// false
Copy the code

As you can see from the example above, RxInt can use the += operator and the > operator directly, while Rx

cannot. This is the convenience of the encapsulated Rx class. About RxInt, RxDouble, RxBool, RxString, RxNum, RxList, RxMap more functions we can refer to the source code view, because the implementation is very simple here will not be repeated.

2. Data used in Obx

Rx is simple to use, calling.value to get the actual data. Rx can be used in isolation, but more often in Obx to implement state management capabilities.

var count = 0.obs;

int count_value = count.value;
Copy the code

3. Update data

Rx objects can be reassigned using.value or updated using update. For underlying data types: Int, bool, String, Double, num, and Rx< Int > can only be updated with a. Value assignment.

var count = 0.obs;

count.value = 1;
count += 1;

var user = User("loongwind".0).obs;

user.value = User("loongwind".1); user.update((value) { value? .age =20;
});
Copy the code

When data is updated with.value or update, controls in Obx refresh the interface data.

The principle of analytic

Before the use of Obx and Rx, the following will be through the source analysis of Obx and Rx to achieve the principle of state management.

Obx must subscribe to the Rx object, listen for changes in the Rx value, and refresh the interface when listening for changes in the Rx value. So how does Obx listen for changes in Rx data? How does Rx tell Obx to refresh when data changes? The following step by step through the source code analysis.

To help you better sort out the relationship between Obx and Rx source code, I drew a UML diagram as follows:

Since Obx’s state management is divided into subscriptions and notifications, the next step is to analyze by subscriptions and notifications respectively.

To subscribe to

From the UML diagram, Obx inherits from ObxWidget, and ObxWidget inherits from familiar StatefulWidget. Since it is a StatefulWidget, there must be State. This is the _ObxState in the UML diagram. Take a look at the source code for Obx and ObxWidget:

class Obx extends ObxWidget {
  final WidgetCallback builder;

  const Obx(this.builder);

  @override
  Widget build() => builder();
}
Copy the code

The code in Obx is relatively simple. The builder passed in to the constructor is the method to build the Widget, and the build calls the builder method passed in.

abstract class ObxWidget extends StatefulWidget {
  const ObxWidget({Key? key}) : super(key: key);

  @override
  _ObxState createState() => _ObxState();

  @protected
  Widget build();
}
Copy the code

ObxWidget is an abstract class with only one build abstract method, implemented in Obx. The _ObxState class was created in createState.

Then look at the source of _ObxState:

class _ObxState extends State<ObxWidget> {
  final _observer = RxNotifier();
  late StreamSubscription subs;

  @override
  void initState() {
    super.initState();
    subs = _observer.listen(_updateTree, cancelOnError: false);
  }

  void _updateTree(_) {
    if(mounted) { setState(() {}); }}@override
  void dispose() {
    subs.cancel();
    _observer.close();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) =>
      RxInterface.notifyChildren(_observer, widget.build);
}
Copy the code

The RxNotifier object, the notifier, is created in _ObxState, and its LISTEN method is called in initState to listen. The callback passed in is _updateTree, which calls setState to refresh the Widget. RxNotifier. Listen

  StreamSubscription<T> listen(
    void Function(T) onData, {
    Function? onError,
    void Function()? onDone,
    bool? cancelOnError,
  }) =>
      subject.listen(
        onData,
        onError: onError,
        onDone: onDone,
        cancelOnError: cancelOnError ?? false,);Copy the code

RxNotifier. Listen calls the subject.listen method directly, and subject is of type GetStream.

  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;
  }
Copy the code

The getstream. listen method wraps the passed argument as LightSubscription, and the onData callback is passed to the onData method.

void onData(OnData<T>? handleData) => _data = handleData;
Copy the code

It is assigned directly to the _data variable.

The addSubscription method is called after wrapping the LightSubscription object:

  FutureOr<void> addSubscription(LightSubscription<T> subs) async {
    if(! _isBusy!) {return_onData! .add(subs); }else {
      await Future.delayed(Duration.zero);
      return _onData!.add(subs);
    }
  }
Copy the code

AddSubscription adds encapsulated LightSubscription objects to _onData, which is a List: List >? _onData = < LightSubscription < T > > [].

The sequence diagram is as follows:

RxNotifier () ¶ RxNotifier () ¶ RxNotifier () ¶

Widget build(BuildContext context) => RxInterface.notifyChildren(_observer, widget.build);
Copy the code

Direct call RxInterface notifyChildren method, this is a static method, the source code:

 static T notifyChildren<T>(RxNotifier observer, ValueGetter<T> builder) {
    final _observer = RxInterface.proxy;
    RxInterface.proxy = observer;
    final result = builder();
    if(! observer.canUpdate) { RxInterface.proxy = _observer;throw """ [Get] the improper use of a GetX has been detected. You should only use GetX or Obx for the specific widget that will be updated. If you are seeing this error, you probably did not insert any observable variables into GetX/Obx or insert them outside the scope that GetX considers suitable for an update (example: GetX => HeavyWidget => variableObservable). If you need to update a parent widget and a child widget, wrap each one in an Obx/GetX. """;
    }
    RxInterface.proxy = _observer;
    return result;
  }
Copy the code

RxInterface. Proxy is a static variable defined as follows: static RxInterface? proxy; , RxNotifier implements RxInterface.

The builder method is then called, and the builder passed in is the same parameter as when using Obx, which is the method that actually builds the Widget. The observer is then checked to see if it can be updated. If it can’t be updated, the exception is thrown. Finally, the temporary _observer that was saved at the beginning is reassigned to rxInterface.proxy and the Widget built by Builder is returned.

You may have the same question as I did when I first looked at the source code. How do YOU associate Obx with Rx? Rxinterface. proxy is a public static variable, indicating that it may be called elsewhere. So I looked up the source code and found that it was called in the get value of RxObjectMixin.

T getvalue { RxInterface.proxy? .addListener(subject);return _value;
}
Copy the code

This is where rxinterface.proxy is called? AddListener addListener, follow up UML diagram and source code, it is found that Rx is finally mixed with RxObjectMixin class, that is, RxInterface. Proxy is called in the data of Rx? .addListener, so when to get Rx data? Take a look at the initial sample code:

Obx(() {
  return Text("${count.value}");
});
Copy the code

Yes, it is in Obx builder method, it is clear why the RxInterface. NotifyChildren method is first introduced to the observer to the proxy then call builder method, Because Rx. Value is called when the Builder method is called and rxInterface.proxy is called when the get value is called? AddListener, and the subject passed by addListener is the GetStream of Rx and proxy is the RxNotifier created in _ObxState.

AddListener is implemented in NotifyManager.

  void addListener(GetStream<T> rxGetx) {
    if(! _subscriptions.containsKey(rxGetx)) {final subs = rxGetx.listen((data) {
        if(! subject.isClosed) subject.add(data); });final listSubscriptions =
          _subscriptions[rxGetx] ??= <StreamSubscription>[];
      listSubscriptions.add(subs);
    }
  }
Copy the code

Check whether the rxGetx (GetStream) passed in is included or not. If it is not, call its Listen method, which is described in the RxNotifier subscription. When data is updated, call the subject.add method. Note that the subject is in the RxNotifier of _obxstate. observer. The _updateTree method that notifies _ObxState when Rx data is updated finally calls back to _ObxState.

The subscription sequence diagram is as follows:

notice

After the subscription analysis, we will look at how the notification is implemented. In the previous section, when the Rx value assignment or update method is called, the interface refresh will be triggered, so we will look at the source code of these two methods.

Update is implemented in _RxImpl:

  void update(void fn(T? val)) {
    fn(_value);
    subject.add(_value);
  }
Copy the code

The assignment implementation of value is in RxObjectMixin:

  set value(T val) {
    if (subject.isClosed) return;
    sentToStream = false;
    if(_value == val && ! firstRebuild)return;
    firstRebuild = false;
    _value = val;
    sentToStream = true;
    subject.add(_value);
  }
Copy the code

Add (_value); add(_value); Method, passing in the latest value as a variable, and subject is a GetStream. The source code is as follows:

  void add(T event) {
    assert(! isClosed,'You cannot add event to closed Stream');
    _value = event;
    _notifyData(event);
  }
Copy the code

Call the _notifyData method.

  void _notifyData(T data) {
    _isBusy = true;
    for (final item in _onData!) {
      if(! item.isPaused) { item._data? .call(data); } } _isBusy =false;
  }
Copy the code

Notice that the final iteration of _onData calls _data.call of the item, and we know that _onData is the LightSubscription object that we added to it earlier via the addListener call to listen method. _data is the callback method passed in by _ObxState._observer.listen, which is _updateTree, and the setState method is called to refresh the interface.

The timing diagram for the notification is as follows:

conclusion

Through the subscription and notification of Obx and Rx source analysis understanding, deepen the understanding of Obx state management, in the development process can be more flexible to its use. This article only explains the Obx and Rx about subscription and notification part of the source code, about its more source code we can be interested in further study, through the source code learning can make us a better understanding of GetX library, also can let us learn more knowledge.