The introduction

As awareness of the Get framework grows within the Flutter development community, more and more people choose to try it. It’s not unusual to get beaten up when trying out a new framework, but it’s necessary to make sure it’s used properly.

Get provides various widgets for building widgets based on Rx variables, such as Obx, GetX, GetBuilder, and so on. The simplest of these is Obx, but for novices, there is a good chance that they have encountered this error:

The following message was thrown building Obx(dirty, state: _ObxState#777c8): [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.Copy the code

The error description is clear: the Obx or GetX components were incorrectly used because the Observable variable was detected not being used in them. Once you understand the meaning, you can use the corresponding variables to resolve the error. But have you ever wondered how Get knows we’re not using the Observable variable?

A preliminary study

class Obx extends ObxWidget {
  final WidgetCallback builder;

  const Obx(this.builder);

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

The definition of Obx is simple; it only takes a closure that returns the Widget as an argument. Since we can’t see a so here, let’s continue analyzing its parent class:

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

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

  @protected
  Widget build();
}

class _ObxState extends State<ObxWidget> {
  RxInterface? _observer;
  late StreamSubscription subs;

  _ObxState() {
    _observer = RxNotifier();
  }

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

  void _updateTree(_) {
    if(mounted) { setState(() {}); }}// ...

  Widget get notifyChilds {
    final observer = RxInterface.proxy; / / 2
    RxInterface.proxy = _observer; / / 3
    final result = widget.build();
    / / 1
    if(! _observer! .canUpdate) {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; / / 4
    return result;
  }

  @override
  Widget build(BuildContext context) => notifyChilds;
}
Copy the code

In the ObxWidget, we immediately see the final error that was thrown at us at 1, and it looks like we can find the answer we are looking for there. ObxWidget inherits the StatefulWidget, but it also includes a build() method in the widget part, minus the BuildContext parameter, in a different way than we would normally use it. Save the current proxy in two places, restore it in four places, and switch to the current RxNotifier in three places. The functions of these operations will be analyzed later.

You can see that due to checking the _observer! CanUpdate is false, so Get must use it to check whether observable variables are used. The name canUpdate confirms our guess (no observable variables, no responsive updates). An error is thrown here because Obx is specifically designed for responsive updates and we are using it in a way that is not intended.

Moving on to the definition of canUpdate, we may be taken by the IDE to the definition of RxInterface, which is an abstract class that is inherited by all Reactive classes and is not implemented. Looking back at the previous code, we can see that _observer is actually an RxNotifier:

class RxNotifier<T> = RxInterface<T> with NotifyManager<T>;

mixin NotifyManager<T> {
  GetStream<T> subject = GetStream<T>();
  final _subscriptions = <GetStream, List<StreamSubscription>>{};

  bool get canUpdate => _subscriptions.isNotEmpty;

  /// This is an internal method.
  /// Subscribe to changes on the inner stream.
  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

As you can see, canUpdate is finally implemented by NotifyManager. CanUpdate checks whether the number of streams in the current instance is 0, that is, whether the Observable variable is listened on. Looking further, addListner was able to add new entries to _SUBSCRIPTIONS, so who used this method? Use IDE to search Usage (IDEA using Alt/Option+F7).

To Get back to the original question, Get checks whether we use an Observable variable within the component. Does it call addListener itself?

When we use the extension method provided by Get. Obs to create an Observable variable, we actually create an Rx

variable. Rx is the core of Get responsive components. Following an Rx variable such as RxString, we can trace it up to RxString -> Rx -> _RxImpl -> RxObjectMixin and find the following code:

mixin RxObjectMixin<T> on NotifyManager<T> {
  // ...
  T get value {
    if(RxInterface.proxy ! =null) { RxInterface.proxy! .addListener(subject); }return _value;
  }
  // ...
}
Copy the code

Here is the code that is called each time it is used, as we expected, where variables are recorded under _SUBSCRIPTIONS.

Review the entire build process for proper use:

  1. Create Obx, save the current proxy, and switch the RxNotifier held by the current proxy for Obx

  2. The.value method is used, the proxy is used to record the usage record, and listen to the variable.

  3. The variables that are being watched are detected during the build check and passed the check.

  4. Restore the proxy saved in the first step

Knowing the full picture of the build, we can guess that steps 1 and 4 ensure that the right proxy records the right variables during the recursive build process.

Common Error Cases

final controller = Get.put(HomeController());

@override
Widget build(BuildContext){
  final goods = controller.goods.value;
  return Obx(() => Text(goods.name));
}
Copy the code

The problem with this code is that when a value is accessed, the Obx has not yet been created and there is no corresponding RxNotifier available for recording and listening, so the error at the beginning of the article is thrown.

Simple modification:

final controller = Get.put(HomeController());

@override
Widget build(BuildContext){
  return Obx(() => Text(controller.goods.value.name));
}
Copy the code

This is the first time for new entrants to contribute, please correct any mistakes