[GetX] State management

2022-01-24 hit count image

Let's see how to manage the state by using GetX in Flutter

Outline

In Flutter, to manage the state, we should use the StatefulWidget or InheritedWidget. However, when we mange the complex state, we should use the Bloc pattern or packages like the Provider.

In this blog post, I will introduce the GetX package that is most used package for state management in Flutter. You can see full source code of this blog post on the link below.

GetX

GetX is best known as a state management package, but it actually has many other features. If you use GetX in Flutter, you can use Route, localization, getting screen size, and calling API features.

In this blog post, I will show you how to manage the state by GetX in Flutter.

GetX installation

To check how to use GetX in Flutter, execute the command below to create a new Flutter project.

flutter create state_management

And then, execute the command below to install the GetX package.

flutter pub add get

In this blog post, we’ll refactor the basic project that is created by the Flutter command to manage the state by GetX.

GetX configuration

To use GetX in Flutter, we should use GetMaterialApp instead of MaterialApp. To check this, open the lib/main.dart file and modify it like the below.

import 'package:get/get.dart';
...
class MyApp extends StatelessWidget {
  ...
  @override
  Widget build(BuildContext context) {
    return GetMaterialApp(
      ...
    );
  }
}

State management

GetX provides two ways to manage the state.

  • Simple state management
  • Responsive state management

Simple state management

To use the simple state management in GetX, create the lib/controller/count_controller.dart file and modify it like the below.

import 'package:get/get.dart';

class CountController extends GetxController {
  int count = 0;

  void increment() {
    count++;
    update();
  }
}

To manage the state by GetX, we should create a class that extends GetxController.

class CountController extends GetxController {
  ...
}

And then, define a variable that is for the state.

class CountController extends GetxController {
  int count = 0;
  ...
}

Lastly, make a function to update the state.

class CountController extends GetxController {
  ...
  void increment() {
    count++;
    update();
  }
}

In the simple state management, after updating the state, you should call update() function to notify the state is changed. If you don’t call update() function, the state is changed but the screen that uses the state is not updated.

Next, let’s use the GetX controller that we’ve made. Open the lib/main.dart file and modify it like the below.

import 'package:get/get.dart';

import 'controller/count_controller.dart';

...

class MyHomePage extends StatelessWidget {
  final String title;

  const MyHomePage({Key? key, required this.title}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    final controller = Get.put(CountController());

    return Scaffold(
      appBar: AppBar(
        title: Text(title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              'You have pushed the button this many times:',
            ),
            GetBuilder<CountController>(builder: (controller) {
              return Text(
                '${controller.count}',
                style: Theme.of(context).textTheme.headline4,
              );
            }),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: controller.increment,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
    );
  }
}

We’ll manage the state by GetX, so we don’t need to use the StatefulWidget anymore. So, we can change the MyHomePage to the StatelessWidget.

class MyHomePage extends StatelessWidget {
  final String title;

  const MyHomePage({Key? key, required this.title}) : super(key: key);
  ...
}

To manage the state by GetX, we should create a GetX controller, and then, we should register it by using Get.put. After registring, we can use the controller to manage the state.

class MyHomePage extends StatelessWidget {
  ...
  @override
  Widget build(BuildContext context) {
    final controller = Get.put(CountController());
    ...
  }
}

In the simple state management, you should use GetBuilder to detect the state is changed and apply it to the screen. If you don’t use GetBuilder, the widget can’t detect the state is changed, so the widget will not be updated.

GetBuilder<CountController>(builder: (controller) {
  return Text(
    '${controller.count}',
    style: Theme.of(context).textTheme.headline4,
  );
}),

To change the state value created by GetX, we need to call the increment function. To call the increment function, bind it to the onPressed of the FloatingActionButton widget like the below.

floatingActionButton: FloatingActionButton(
  onPressed: controller.increment,
  tooltip: 'Increment',
  child: const Icon(Icons.add),
),

Now, when you start the project and press the action button, you can see the count value is increased well on the screen. Like this, you can decide the updating timing of the state by using the update() function in the simple state management of GetX.

Responsive state management

The responsive state management detects the state is changed by the internal logic and appy it to the screen unlike the simple state management that we need to use the update function to notify the state is changed.

To check this, modify the lib/controller/count_controller.dart file like the below.

import 'package:get/get.dart';

class CountController extends GetxController {
  final count = 0.obs;

  void increment() {
    count.value++;
    // count(count.value + 1);
  }
}

Unlike the simple state management, we need to use .obs to define the state variable. The variable type won’t be the simple type like int or string. It will be the responsive variable type like RxInt or RxString.

you can use two ways to update responsive state variables like the below.

count.value++;
// or
count(count.value + 1);

Next, to use the responsive state management, modify the lib/main.dart file like the below.

class MyHomePage extends StatelessWidget {
  final String title;

  const MyHomePage({Key? key, required this.title}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    final controller = Get.put(CountController());

    return Scaffold(
      appBar: AppBar(
        title: Text(title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              'You have pushed the button this many times:',
            ),
            Obx(
              () => Text(
                "${controller.count.value}",
                style: Theme.of(context).textTheme.headline4,
              ),
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: controller.increment,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
    );
  }
}

To detect the state is changed, we should use Obx instead of GetBuilder in the responsive state management.

Obx(
  () => Text(
    "${controller.count.value}",
    style: Theme.of(context).textTheme.headline4,
  ),
),

Now, when you execute the program and press the action button, you can see the count value is increased well on the screen.

LifeCycle

In the StatefulWidget, you can use the lifecycle methods. As same it, you can use the lifecycle methods in the GetXController like the below.

class CountController extends GetxController {
  @override
  void onInit() {
    super.onInit();
  }
  @override
  void onClose() {
    super.onClose();
  }
}
  • onInit: When the controller is created, it is called.
  • onClose: When the controller is removed from the memory, it is called.

Worker

Worker detects the state is changed, and calls a callback function when the state is changed.

ever(count, (_) => print("called every update"));
once(count, (_) => print("called once"));
debounce(count, (_) => print("called after 1 second after last change"), time: Duration(seconds: 1));
interval(count, (_) => print("called every second during the value is changed."), time: Duration(seconds: 1));
  • ever: Called whenever the responsive state value changes.
  • once Called only once, when the responsive state value changes for the first time.
  • debounce: It works like debounce. Called if there is no change for a certain amount of time since the last change.
  • interval: It works like interval. Called at regular intervals while the status value is being changed.

You can use Worker when the class or controller is created. So, you can use it on onInit of the controller, class construct, and initState of the StatefulWidget.

Get.find

In above examples, we use the controller created by Get.put to use the state value.

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

If you want to use the state value in the child widget or change it, how can we do it? Of course, we can pass the controller via the parameter of the widget like the below.

CustomWidget(controller: controller)

However, GetX provides Get.find to access the controller easily like the below.

Get.find<CountController>()

To check this, modify the lib/main.dart file like the below.

class MyHomePage extends StatelessWidget {
  final String title;

  const MyHomePage({Key? key, required this.title}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    Get.put(CountController());

    return Scaffold(
      appBar: AppBar(
        title: Text(title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              'You have pushed the button this many times:',
            ),
            Obx(
              () => Text(
                "${Get.find<CountController>().count.value}",
                style: Theme.of(context).textTheme.headline4,
              ),
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: Get.find<CountController>().increment,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
    );
  }
}

Before, we’ve used the controller that is created by Get.put(CountController()); to access the state value, but now, we use Get.find<CountController>() to access the state value like the below.

Obx(
  () => Text(
    "${Get.find<CountController>().count.value}",
    style: Theme.of(context).textTheme.headline4,
  ),
),

Also, we can use Get.find to call the function that changes the state.

floatingActionButton: FloatingActionButton(
  onPressed: Get.find<CountController>().increment,
  tooltip: 'Increment',
  child: const Icon(Icons.add),
),

You can use Get.find anywhere to access the controller that is created by Get.put. In the example here, we use Get.find to access the controller in the same file, but you can use it in the other child widgets. The important point is, you should register the controller by Get.put first, and then, you can use Get.find to access the controller. If you try to access the controller that is not registered, the error will occur.

Get.isRegistered

We can use Get.find to use the controller that is created by Get.put, and if the controller is not registerd, the error will occur. To solve this error, you can use Get.isRegistered to check the controller is registered or not.

Get.isRegistered<CountController>()

If the controller is registered, it returns true. If the controller is not registered, it returns false.

static get to

When you manage the state by GetX, you’ll often access state values by using Get.find. So, the below pattern that uses static is often used in GetX.

static CountController get to => Get.find<CountController>();

To check this, modify the lib/controller/count_controller.dart file like the below.

import 'package:get/get.dart';

class CountController extends GetxController {
  static CountController get to => Get.find<CountController>();

  final count = 0.obs;

  void increment() {
    count.value++;
    // count(count.value + 1);
  }
}

And then, modify the lib/main.dart file like the below.

class MyHomePage extends StatelessWidget {
  final String title;

  const MyHomePage({Key? key, required this.title}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    Get.put(CountController());

    return Scaffold(
      appBar: AppBar(
        title: Text(title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              'You have pushed the button this many times:',
            ),
            Obx(
              () => Text(
                "${CountController.to.count.value}",
                style: Theme.of(context).textTheme.headline4,
              ),
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: CountController.to.increment,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
    );
  }
}

Before we’ve accessed the state value by the controller variable, now we can access it by the static like the below.

Obx(
  () => Text(
    "${CountController.to.count.value}",
    style: Theme.of(context).textTheme.headline4,
  ),
),
...
floatingActionButton: FloatingActionButton(
  onPressed: CountController.to.increment,
  tooltip: 'Increment',
  child: const Icon(Icons.add),
),

In GetX, this pattern is often used, so it’s better to remember it well.

Complated

Done! we’ve seen how to manage the state by GetX in Flutter. Now, let’s try to manage the state By GetX instead of StatefulWidget.

Was my blog helpful? Please leave a comment at the bottom. it will be a great help to me!

Posts