DEV Community

Cover image for The Flutter GetX State Management
Hashan Gunathilaka
Hashan Gunathilaka

Posted on

The Flutter GetX State Management

Flutter is without a doubt the best framework for developing cross-platform applications. Application development with Flutter is truly awesome and easy because it provides a rich set of customizable widgets. However, some state management options won't allow you to feel the true power of the flutter framework, since you have to waste your development time to implement unnecessary boilerplate. When I started to learn the Bloc pattern, I was confused by the bloc concepts. It was difficult to understand. On the other hand, the provider is easy to understand, but we have to be very careful when avoiding the unnecessary rebuilds. Since It directly affects your application's performance. However, all the state management options have their pros and cons.

GetX has a different philosophy. It wants to manage your application state in a simple and well organized fashion while improving performance. So let’s see how GetX has achieved it.

In the article, I will discuss,

  1. Why is GetX so special?
  2. State management using GetX
  3. GetxController
  4. The Reactive State Manager in GetX
  5. The Simple State Manager in GetX
  6. MixinBuilder : Mix your both state managers
  7. StateMixin

Why is GetX so special?

GetX is more than just a state management library. It is, in fact, a small flutter framework capable of handling route management and dependency injection in flutter applications. But in this article, I will only discuss its state management capabilities.
GetX is a very lightweight and powerful state management solution for flutter. So why is GetX so superior?

High performance: GetX uses fewer resources as possible. It does not depend on Streams or ChangeNotifier. Instead, it uses low latency GetValue and GetStream to improve performance.

Less code: You may be tired of implementing boilerplate in the bloc pattern and waste development time on unnecessary codes. Time is money, right? In GetX, you are not going to write any boilerplate. You can achieve the same thing much faster, with less code in GetX. No need to create classes for the state and event, since these boilerplates do not exist in GetX.

No code generation: There is no need to use code generators at all. So your valuable development time is not going to waste any more on running code generators(build_runner) every single time when you change your code. cool right?

Don't worry about context: Your application context is very important. But sending the context from your view to the controller can be, sometimes cumbersome. In GetX, you don't need to do this. You can access controllers within another controller without any context. cool right?

No unnecessary rebuilds: Unwanted rebuilds are a problem of state managers based on ChangeNotifier. When you make a change in your ChangeNotifier class, all widgets that depend on that ChangeNotifier class are rebuilt. Some rebuilds may be unnecessary and costly. It may also reduce the application's performance as well. You don't have to worry about this in GetX since it does not use the ChangeNotifier at all.

Code organization is simple: Bloc's popularity comes from its superior code organizing capabilities. It makes it easier to separate your business logic from the presentation layer. GetX is a natural evolution for this as official documentation says. In GetX, you can separate not just the business logic but also the presentation layer. Powerful right?

So, what do you think about GetX? Can I say superior for it? I think I can.

State management using GetX

GetX provides two kinds of state managers: The reactive state manager and the simple state manager. If you have used Bloc before, then you should have some experience in reactive programming. In GetX, you can have far more superior and easier reactive experience, unlike Bloc. The simple state manager is just like using setState in StatefulWidget, but in a cleaner way. Before discussing these two state managers, it is essential to know about GetxController in GetX.

GetxController

Your controllers contain all of your business logic. GetX has an important class called GetxController. It is useful to enable reactive and simple state manager functionality in your controllers. All you have to do is to extend your controllers from GetxController.

Let's take a simple example from your shopping app.

class ProductController extends GetxController {
    // your state variables
    // your methods
}
Enter fullscreen mode Exit fullscreen mode

You can completely remove StatefulWidget by using GetxController. Since GetxController has onInit() and onClose() methods. So you can replace initState() and dispose() methods in StatefulWidget. Pretty clever right? When your controller is created in memory, the onInit() method is called immediately, and the onClose() method is called when it is removed from memory.

You can also use the onReady() method in GetxController. The onReady() method will be called soon after the widget has been rendered on the screen.

class ProductController extends GetxController {

     @override 
    void onInit() {
       // Here you can fetch you product from server
       super.onInit();
    }

    @override 
    void onReady() {
       super.onReady();
    }

    @override
    void onClose() { 
          // Here, you can dispose your StreamControllers
          // you can cancel timers
          super.onClose();
    }
}
Enter fullscreen mode Exit fullscreen mode

Thanks to the DisposableInterface, GetxController can dispose of your controllers from memory on its own. So you don't need to dispose of anything manually anymore. GetxContoller will take care of it for you. As a result, it will help to reduce the memory consumption and improve the application performance.

The Reactive State Manager in GetX

The reactive state manager implements reactive programming in an easier and cleaner way. You may have used StreamContollers and StreamBuilder in your reactive programming approaches. But in GetX, you won't need to create such things. Furthermore, unlike Bloc, there is no need to create separate classes for each state. You can remove these boilerplates and do the same thing with just a few lines of code using Getx.

Create Reactive Variables

In the Reactive approach of GetX, first you need to create observable variables(reactive variables). In simple terms, your widgets can watch changes of your variables. And widgets can update their UI according to these changes in variables. There are three different ways to create reactive variables.

1. Attaching Rx to variable type, Rx{Type}

class CounterController extends GetxController {
    var counter = RxInt(0);  // You can add 0 as the initial value
} 
Enter fullscreen mode Exit fullscreen mode

2. Using Rx and Dart Generics, Rx<Type>

class CounterController extends GetxController {
    var counter = Rx<Int>(0);  // You can add 0 as the initial value
} 
Enter fullscreen mode Exit fullscreen mode

3. Adding .obs to the end

class CounterController extends GetxController {
    var counter = 0.obs; 
}
Enter fullscreen mode Exit fullscreen mode

That's it. Simple right, You can use any approach you like.

Using GetX and Obx

You can use GetX or Obx to listen to changes of your reactive variables from your widgets.
GetX<Controller> is just like StreamBuilder, but without a boilerplate.
Obx is much more simple than GetX. You just have to wrap your widget from it.

class CounterController extends GetxController {
    var counter = 0.obs; 
}  
Enter fullscreen mode Exit fullscreen mode

Using GetX:

GetX<CounterController>(
    init: CounterController(),
    builder: (controller) => Text(
         'Counter is ${controller.counter.value}'
    ),
),
Enter fullscreen mode Exit fullscreen mode

Using Obx:

Obx(() => Text(
        'Counter is ${controller.counter.value}'
    ),
),
Enter fullscreen mode Exit fullscreen mode

You have to use the value property in the reactive variable when accessing its value, like controller.counter.value.

class CounterPage extends StatelessWidget {

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


     @override
     Widget build(BuildContext context) {
        return Scaffold(
          body: Container(
             child: Center(
                child: Obx(() => Text(
                      'Counter is ${controller.counter.value}'
                    ),
                    ),
              ),
            ),
           );
        }
}
Enter fullscreen mode Exit fullscreen mode

When using the Obx, you can take advantage of GetX dependency injection. The Put method in GetX is used to manage your dependencies in your flutter project. And it will help you to use the same controller instance across all your child routes. After getting your CounterController instance into your widget, you can use it as the controller for the Obx.

The Simple State Manager in GetX

The Simple state manager uses extremely low resources, since it does not use Streams or ChangeNotifier. But your widgets can listen to changes of your state, thanks to the update() method. After doing some changes to your state in your controller, you have to call the update method to notify the widgets, which are listening to the state.

class CounterController extends GetxController {
    int counter = 0;

    void increment() {
        counter++;
       update(); // Tell your widgets that you have changed the counter
    }
}
Enter fullscreen mode Exit fullscreen mode

You can see, we have to simply declare the state variable as we normally do. Unlike reactive variables, you don't need to transform your state variables into something else (In reactive approach we need to declare reactive variables using .obs).

GetBuilder

The GetBuilder widget will update your view based on your state changes.

GetBuilder<CounterController>(
    init: CounterController(),
    builder: (controller) => Text(
             'Counter: ${controller.counter.value}'
    ),
), 
Enter fullscreen mode Exit fullscreen mode

If you use a controller for the first time in your GetBuilder, then you have to initialize it first. After that you don't have to start the same controller again in another GetBuilder. Because all GetBuilders that depend on the same controller will share the same controller instance across your application. This is how the simple state manager consumes extremely less memory. In simple terms, if 100 GetBuilders use the same controller, they will share the same controller instance. There won't be 100 instances for the same controller.

class CounterController extends GetxController {
    int counter1 = 0;
    int counter2 = 0;

    void incrementCounter1() {
        counter1++;
        update();
    }

    void incrementCounter2() {
        counter2++;
        update();
    }
}
Enter fullscreen mode Exit fullscreen mode
GetBuilder<CounterController>(
    init: CounterController(), /* initialize CounterController if you use 
                                     it first time in your views */
    builder: (controller) {
             return Text('Counter 1: ${controller.counter1.value}'); 
       }
    ),



   /* No need to initialize CounterController again here, since it is 
      already initialized in the previous GetBuilder */
GetBuilder<CounterController>(                            
    builder: (controller) {
             return Text('Counter 2: ${controller.counter2.value}'); 
       }
    ),
Enter fullscreen mode Exit fullscreen mode

If you use GetBuilder, you no longer need to use StatefulWidgets in your application. You can handle your ephemeral state(UI state) in a cleaner and easy way using GetBuilder than SetState. In simple terms, you can make your class as StatelessWidget and update the specific components by only wrapping them in GetBuilder. That's all. You don't need to waste your resources by making the whole class as a StatefulWidget.

MixinBuilder : Mix your both state managers

The MixinBuilder mixes the both state managers. So you can use both Obx and GetBuilder together. But keep it mind, MixinBuilder consumes more resources than the other two approaches. If you really care about your application performance, try to use the MixinBuilder as little as possible. The use cases of MixinBuilder, on the other hand, are rare.

class CounterController extends GetxController {
    var counter1 = 0.obs; // For reactive approach
    int counter2 = 0;    // For simple state management approach

    void incrementCounter1() {
      counter1.value++;
    }

   void incrementCounter2() {
       counter2++;
       update();
    }
}
Enter fullscreen mode Exit fullscreen mode
MixinBuilder<CounterController>(
    init: CounterController(),
    builder: (controller) => Column(
       children: [
           Text('Counter 1: ${controller.counter1.value}), 
                         // For reactive approach

           Text('Counter 2: ${controller.counter2}') 
                       // For simple state management approach
        ]
    ),
),
Enter fullscreen mode Exit fullscreen mode

StateMixin

You can use the StateMixin to handle your UI state in a more efficient and clean way, when you perform asynchronous tasks.
Let's say your application is going to fetch some products from a cloud server. So this asynchronous task will take a certain amount of time to complete. So your application status and the state will be changed according to the response of your asynchronous task.

Loading status : Until you get the response, you have to wait.
Success status : You get the expected response.
Error status : Some errors can be happened when performing the asynchronous task

These are the main status of your application when performing an asynchronous task. So the StateMixin helps to update your UI according to these status and state changes.

You have to simply add StateMixin to your controller using with keyword. You should also specify the type of state to be handled by the StateMixin, such as StateMixin<List<Product>>.

class ProductController extends GetxController with StateMixin<List<Product>> {}
Enter fullscreen mode Exit fullscreen mode

And also RxStatus class provides defined status to use with the StateMixin.

RxStatus.loading();
RxStatus.success();
RxStatus.empty();
RxStatus.error('error message');
Enter fullscreen mode Exit fullscreen mode

The StateMixin provides the Change() method and it changes the State according to our asynchronous task response. You have to just pass the new state and the status.

change(newState, status: RxStatus.success());
Enter fullscreen mode Exit fullscreen mode

Let's take an example.

class ProductController extends GetxController with StateMixin<List<Product>>{

    @override
    void onInit() {
        fetchProducts();
        super.onInit();
    }


    void fetchProducts() async {
          // You can  fetch products from remote server
        final response = await fetchProductsFromRemoteServer(); 

        If(response.hasData) {
            final data = response.data;
           //..
              // Successfully fetched products data
          change(data, status: RxStatus.success());     

          } else if(response.hasError) {
              // Error occurred while fetching data
          change(null, status: RxStatus.error('Something went wrong')); 

          } else {
              // No products data
          change(null, status: RxStatus.empty());
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

As you can see, after getting a successful response with data, I have changed the state by passing the response data and the RxStatus.success to the change() method. Likewise I have changed the state according to error response and empty response data.

class ProductsPage extends StatelessWidget {


    // Get a ProductController instance
    final ProductController controller = Get.put(ProductController());


    @override 
    Widget build(BuildContext context) {
        return Scaffold(
            // app bar

            body: controller.obx(

                (productsState) => ShowProductList(productsState),

            onLoading: CustomLoadingIndicator(),

            onEmpty: Text('No products available'),

            onError: (error) => Text(error),

            )
        );
    }
}
Enter fullscreen mode Exit fullscreen mode

controller.obx() widget will change your UI according to the changes of the status and the state.
When an asynchronous task starts (fetching products from the server), the obx widget will show the default loading indicator. You can also set the CustomLoadingIndicator() to the onLoading property.
After successfully fetching data, the obx widget will render the data to the UI using the ShowProductList() custom widget.
If something goes wrong, by default obx widget will render a Text widget to show the error. And also you can provide the custom error widget to the onError property. (Note that the controller.obx() widget in here is completely different from what you have learned in reactive Obx()).

Conclusion

When you use GetX in your next project, you will realize how awesome it is. The primary goal of this article is to provide a quick overview of GetX. The top priority of the GetX is to improve your application performance while managing the state in a simple and well organized way.

You can read more about Getx from official documentation.

Discussion (2)

Collapse
tomyem profile image
Tom-Yem

Great! I like the StateMixin.
I've been using getX since 4 months ago & i like it a lot. One thing i don't like about it is that it lacks --or i can't find-- a good documentation that describes every and each of it's capabilities. I keep getting surprised of it's features from blogs like this. I am only aware of the readme file on it's repo, is there any other good resources that documents getX?

Collapse
faststare08 profile image
faststare08

how I stream API on statemixin with getx? I tired getx with timer.periodic but my UI is not updating with newly fetch data.