Search code examples
flutterflutter-getx

When using GetX observables, which UI widgets need to be wrapped in Obx?


To learn GetX I created a simple controller class:

class MyDataController extends GetxController {
  RxString aString = ''.obs;

  void updateString(String s) {
    aString.value = s;
  }
}

aString's value is displayed in two classes: the AppBar (not discussed here) and another class in which aString is both set and displayed:

class Level1 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final MyDataController controller = Get.find();
    final textController = TextEditingController();
    return Column(
      children: [
        TextField(
          controller: textController,
          onChanged: (_) {
            controller.updateString(textController.text);
          },
        ),
        Text(controller.aString.value),
      ],
    );
  }
}

I'm confused about which widgets need to be wrapped in an instance of Obx().

If I wrap only the Text() (display widget) in an Obx instance, it's updated when the TextField() (input widget) changes. And if I wrap only the TextField() widget in an Obx instance, I get an error message:

The following message was thrown building Obx(has builder, dirty, state: _ObxState#019a0): [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.

Everything seems clear: widgets displaying state must be wrapped in Obx() instances to display updated variables. That makes perfect sense. And widgets that change state don't need to be wrapped in Obx() instances.

I'm confused, though, because if I wrap both widgets in separate Obx() instances, I get the error message. But if I wrap the entire Column() in an Obx() instance, the text is properly updated when the TextField() changes. ... What am I missing in my understanding?


Solution

  • You got it all up to the point when you wrapped the Text widget in an Obx. In actual fact it is best to wrap only the smallest widget that would need updating in Obx, the Text widget in this case.

    Let me explain what happened the cases in which you tried testing it out:

    Case 1: When you wrapped only the Text widget in Obx (The best thing to do)

    This case happen to be the best and most encouraged approach. In this scenario when the value of aString changes in the controller (MyDataController) the Obx is notified to re-build only the affected Text widget from scratch, and this is exactly the aim of GetX.

    Case 2: When you wrapped both the Text && the TextField widget in Obx (This will throw an error).

    In this case you have wrapped both the Text and TextField widget in Obx, we can therefore let case 1 account for the Text widget. Now, moving unto the TextField widget, an error will occur because the TexField widget is not in any way dependent on any obs-value (observable value). It is important to note that in the onChanged callback provided to the textField, the method updateString called on the controller have no effect whatsoever on TextField's parameter and thus this leads GetX to throw an error since you are trying to forcibly update/re-build a widget that needs no rebuilding.

    Case 3: When you wrapped the whole column in Obx (Will not throw an error but not the best practice).

    In this case the widget will be built with no error whatsoever since the Text widget (which is inside the Column) is dependent on the value of aString. So, let's see what happens when the method updateString is called. When the updateString is called the whole Column is re-built (along with the TextField and the Text widgets and this action will as well cause the value in the Text widget to be updated. Now, you can see why this third case can be detrimental, if you try wrapping your whole app in Obx, your whole app will then have to get re-built (which can really affect your app's performance negatively. Of course GetX has a way of disallowing that and thus it throws an error when you try wrapping an HeavyWidget in Obx or GetX.