Welcome to Our Website

State Management in Flutter with GetX

Flutter with GetX

State management enables you to pass data from one user interface to another. When the state of your application changes, the system rebuilds the user interface.

Flutter traditionally gives the Stateful Widget to manage the state. However, this is quite difficult to implement in a large application.

Stateful widgets pass data through the constructors of child widgets. Though this feature is useful, it causes data to be passed to widgets that don’t need it.

Another disadvantage is that the business logic is tightly coupled to the user interface. This can lead to puzzlement.

You can check my other post on Flutter with Provider State management and Riverpod State management in Flutter previously.

What is GetX?

GetX is a micro-framework that aims to provide top notch development experience by minimizing the boiler plate combined with neat syntax and simple approach. When developing with GetX, everything feels self evident and practical. It’s a solid blend of simplicity and power. It’s one of those rare packages that try to do everything and actually do it.

Performance

GetX provides high performance and minimum consumption of resources. GetX does not use Streams or ChangeNotifier.

Decoupling

Business logic is separated from UI, and even dependencies can be separated using GetX Bindings.

Internationalization

GetX provides i18n out of the box allowing you to write applications with various language support.

State Management

GetX is its intuitive state management feature. State management in GetX can be achieved with little or no boilerplate.

Route Management

GetX provides API for navigating within the Flutter application. This API is simple and with less code needed.

Dependency Management

GetX provides a smart way to manage dependencies in your Flutter application like the view controllers. GetX will remove any controller not being used at the moment from memory automatically.

Change Theme

While we are using GetX we don’t need to use any higher level widget than the “GetMaterialApp” widget and also we don’t need to use the “ThemeProvider” widget for changing themes.

Installation Of GetX

To integrate GetX into the application. go to the GetX Documentation, copy get: "latest_package", and add it to the project pubspec.yaml file, under the dependencies section and then run the pub get command.

This will install the GetX ecosystem to your project.

State Management

State Management is still the hottest topic in Flutter Community. There are tons of choices available and it’s super intimidating for a beginner to choose one. Also, all of them have their pros and cons. So, what’s the best approach? Answer is simple — The one you feel comfortable with. Can GetX be the one? Let’s take a look!

GetMaterialApp

Use State Management GetMaterialApp instead of MaterialApp

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return GetMaterialApp(); // instead of MaterialApp
  }
}

Note: GetMaterialApp is optional if you’ll be using GetX only for state management, and actually used when managing routes, which we’ll cover in future articles.

GetxController

Controllers are classes where all our business logic goes. All the variables and methods are placed here and can be accessed from the view. While we can just create a simple class for this purpose, GetX provides a class called GetxController which extends DisposableInterface.

This means that our controller will get deleted from memory as soon as the widgets using it are removed from the navigation stack. We don’t have to manually dispose anything and the memory consumption is reduced, resulting in high performance.

GetxController comes with onInit() and onClose() methods which essentially replace the initState() and dispose() methods of the StatefulWidget. This allows us to completely avoid using StatefulWidget and write highly organized code.

class Controller extends GetxController {
	
  @override
  void onInit() { // called immediately after the widget is allocated memory
    fetchApi();
    super.onInit();
  }

  @override
  void onReady() { // called after the widget is rendered on screen
    showIntroDialog();
    super.onReady();
  }

  @override
  void onClose() { // called just before the Controller is deleted from memory
    closeStream();
    super.onClose();
  }
}

GetBuilder

GetBuilder can be wrapped over any widget to make it interact with the methods and variables of the controller. We’ll be able to call functions, listen to state changes, etc.

First create a Controller:

class Controller extends GetxController {

  int counter = 0;

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

}

We use update() method inside any method in the controller so that our widgets can listen to the changes made by the method. If you have used Provider package, it’s just like notifyListeners().

Now to make it work on the UI side, we wrap our widget with a GetBuilder.

GetBuilder<Controller>( // specify type as Controller
  init: Controller(), // intialize with the Controller
  builder: (value) => Text(
    '${value.counter}', // value is an instance of Controller.
  ),
),

GetBuilder<Controller>( // no need to initialize Controller ever again, just mention the type
  builder: (value) => Text(
    '${value.counter}', // counter is updated when increment() is called
  ),
),

You need to initialize Controller only the first time it’s used in GetBuilder. All other GetBuilders will automatically share the state of the first one, no matter where they are in the app.

GetBuilder essentially replaces the StatefulWidget. You can keep all the pages Stateless and wrap specific widgets in GetBuilder. It’s a nice way to manage ephemeral state, while keeping the code organized.

We also get a refined control over which widgets to update by assigning unique ID

GetBuilder<Controller>(
  id: 'aVeryUniqueID', // here
  init: Controller(),
  builder: (value) => Text(
    '${value.counter}', // this will update
  ),
),

GetBuilder<Controller>(
  id: 'someOtherID', // here
  init: Controller(),
  builder: (value) => Text(
    '${value.counter}', // this won't update
  ),
),

class Controller extends GetxController {

  int counter = 0;

  void increment() {
    counter++;
    update(['aVeryUniqueID']); // and then here
  }

}

GetX

GetBuilder is fast and has low memory footprint, but, it’s not reactive. This means we’ll be missing out on the power of streams. Well, for this very reason, GetX (class) was created.

GetX is very similar to GetBuilder in terms of syntax, but the approach is purely stream based.

class Controller extends GetxController {

  var counter = 0.obs;

  void increment() => counter.value++;

}

Add .obs to any variable and you make it observable. Basically you’re turning counter into a stream of type int. This means that we can listen to the changes made to counter from our view using GetX.

GetX<Controller>(
  init: Controller(),
  builder: (val) => Text(
    '${val.counter.value}',
  ),
),

GetX<Controller> is basically a StreamBuilder without boilerplate.

counter is of type RxInt and to actually use it, we need to add .value to it. We have to do this with any kind of observable variable.

What if we want to make a class object observable? Well, it’s the same process. In one line of code, we can make all the variables of the class observable. Neat. I’ll show that while implementing Obx.

Obx

This one is a personal favorite. It has the simplest syntax and implementation. All we’ve to do is wrap the widget in Obx(() ⇒ ) and done!

class User {
  String name;
  User({this.name});
}

class Controller extends GetxController {
	
  var user = User(name: "Aachman").obs; // declare just like any other variable
	
  void changeName() => user.value.name = "Garg"; // use .value and access any variables of the class

}

Obx(() => Text(
  '${controller.user.value.name}'
  ),
),

The syntax is shorter than setState, though there’s one extra step here. We need to initialize the Controller to use our variables and methods.

Go it with GetX:

class PageOne extends StatelessWidget {

  Controller controller = Get.put(Controller());
  // instead of Controller controller = Controller();

}

What’s happening here? Putting it this way makes the controller available in all the child routes of this class. We’ll be able to get the same instance from anywhere in the app in a simple way. This will help us make multiple widgets interact with same controller in an organized way.

Use the same instance in another class:

class PageSeven extends StatelessWidget {

  Controller controller = Get.find();

}

GetX will automatically find the instance we used previously, no matter where it is in the widget tree. Now, if we change the value of a controller variable from PageSeven, it’ll be updated in PageOne as well.

Which one to choose?

GetBuilder

  • When maintaining ephemeral state. Which basically means use it wherever you would use setState.
  • If performance is the top most priority. State is shared among builders and much RAM is not consumed.
  • If you don’t want to work with streams.

GetX

  • When you want the power of reactive programming.
  • It redraws the widget only when the value of variable is actually changed. Say value of variable is changed from "Garg" to "Garg", then widgets will not be redrawn.
  • If you don’t want to instantiate controllers.

Obx

  • If you prefer a simple syntax.
  • If you’ll be using with multiple controllers in the same widget. Obx doesn’t need a type, so it can be used with any number of controllers. Something like this:
  • When using Bindings (we’ll discuss in the next article), always prefer Obx

That’s it!

This was the GetX is on State Management, This way we can implement GetX in out project ho