Skip to content

State Management

Janina Krueger edited this page Jun 22, 2022 · 8 revisions

General Information

We are using the state management package Get. Next to functionalities such as route and dependency management, it brings two different state managers: a simple state manager (GetBuilder) and a reactive state manager (GetX/Obx). We decided to use GetBuilder throughout our project, since it's extremely lightweight and simple as well as beginner-friendly.

Different types of state management in GetX

Obx vs. GetBuilder vs. GetX
class Log2Page extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    Controller c = Get.put(Controller());

    return Scaffold(
      body: SafeArea(
        child: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.spaceEvenly,
            children: [
              Obx(
                      () => Text('Obx: ${c.log2.value}')
              ),
              // ↓ requires manual controller.update() call
              GetBuilder<Controller>(
                builder: (_c) => Text('GetBuilder: ${_c.log2.value}'),
              ),
              // ↓ controller instantiated by Get widget
              GetX<Controller>(
                init: Controller(),
                builder: (_c) => Text('GetX: ${_c.log2.value}'),
              ),
              RaisedButton(
                child: Text('Add +1'),
                onPressed: c.change,
              ),
              RaisedButton(
                child: Text('Update GetBuilder'),
                onPressed: c.update, // rebuild GetBuilder widget
              ),
            ],
          ),
        ),
      ),
    );
  }
}

Obx

Listens to observable (obs) changes. Controller needs to already be declared/initialized elsewhere to use.

GetX

Listens to observable (obs) changes. Can initialize controller itself using init: constructor argument, if not done elsewhere. Optional argument. Safe to use init: if Controller already instantiated. Will connect to existing instance.

GetBuilder

Does not listen to obs changes. Must be rebuilt manually by you, calling controller.update(). Similar to a setState() call. Can initialize controller itself using init: argument, if not done elsewhere. Optional.

In our app the default way of handling state managment is to use GetBuilder. If you use more than one controller in your widget you can use the GetX (for building a widget using builder) or OBX approach (for updating variables)

GetBuilder (see documentation here)

  • widgets are updated manually using ID of GetBuilder => very low on memory usage
  • Observed variables are stored in Controllers, i.e. in classes inheriting from GetXController
  • no StatefulWidget required, components where the state is relevant can be simply wrapped into a GetBuilder
  • no streams and stream handling required as if with reactive state management

GetBuilders and Controllers in our project

For every widget that is depending on the state/content of a specific variable, we are using a GetBuilder and store the respective variable in a Controller. Currently, we have two distinct Controllers: PersistentController and TempController. In general, every variable stored in a controller should be private (naming convention: _myPrivateVariable) and provide a getter and (one or more) setter(s). The setter should not only set the variable, but also update the widgets depending on this variable, and update the database (if necessary for the respective variable). The controllers are initialized once at app startup (in our case: in the MainScreen-class) using the put()-method:

final PersistentController persistentController = Get.put(PersistentController());

Afterwards, they (and all their class methods and variables, i.e. the observed variables of interest) can be accessed everywhere throughout he codebase using

PersistentController persistentController = Get.find<PersistentController>();

However, if you need to access a controller and its functions within a GetBuilder, be aware that using find() is not required (and not recommended as it seems to cause problems during updating). Instead, you can use the function parameter of the builder, which is the instance of the corresponding controller:

return GetBuilder<TempController>( // GetBuilder relying on TempController
      id: "my-example-screen",
      builder: (tempController) { // receives an instance of TempController as parameter
          tempController.getMyVariable(); // can be used to access getters/setters from TempController      
      }

How do I decide if I store a new variable to PersistentController or TempController?

  • will your variable be initialized on startup/first build of a screen?
  • will your variable persist and not change significantly for a longer period of time/throughout many screen changes?
  • => store in PersistentController
  • will your variable be changing frequently or not persist during the runtime of the app?
  • => store in TempController

What do I need to pay attention to when creating a new controller variable?

  • make it private (naming starting with underscore "_")
  • add a getter (name starting with "get", return value is the value of the variable)
  • add at least one setter (taking the new value and other required information as function parameter, returning void)
  • make sure the setter calls update(["get-builder-1","get-builder-2"]) for all GetBuilders relying on the variable
  • make sure the setter performs the required database updates, if any

What do I need to pay attention to when creating a new GetBuilder?

  • give a meaningful name to the controller instance that the builder receives as function parameter (e.g. tempController or persistentController)
  • give a meaningful id to the builder (make sure it isn't used by another builder yet => use Strg+F)
  • when calling a getter: make sure the widget is updated when the respective variable changes
    • find all setters of the variable
    • make sure they call update()
    • add the ID string of your builder to the update method to ensure it's updated when the variable changes: update(["id-of-my-get-builder"])

GetView and controller.refresh() (issue #181)

  • When a widget extends from GetView instead of StatelessWidget, GetBuilder or Get.find() is not needed. Instead, variables in the controller can be accessed directly. In our project we don't use it as we use Get.find() and GetBuilder. [1] [2]
  • controller.refresh() is to manually notify a variable change in the controller. As in our cases when we need to notify a class about a change in a controller variable we always use GetBuilder, where a notification is done automatically, we don't need to call refresh().[1] [2]

Clone this wiki locally