Image credit: flutter.dev

Flutter : State management with GetX

What is GetX?

GetX is a mini framework built on top of flutter and comes with state, dependency and route management.

Installing

Add get to your pubspec.yaml file:

dependencies: 
get: ^4.6.5

Import get in files that it will be used:

import 'package:get/get.dart';

State Management with GetX:

When it comes to state management GetX has two different approaches

  1. Reactive state management
  2. Simple state management

With reactive state management, we work with observables(a data stream) and observers(the components that listen to changes in data).

GetxController:

Before going into deep, let’s see one more concept called Controller. The business logic of an app lives inside of a controller and GetX comes with a special class called GetXConroller. GetXController class has three methods that can be overwritten and the methods are onInit() which is called when the controller is created, onReady() which is called after the widget is rendered on the screen and onClose() which is called when the controller is freed from memory. By using GetX controller you basically eliminate the need for a stateful widget. because you can initialize data by calling onInit() or onReady() and you can free resources by calling onClose().

class MyController extends GetxController{

@override
void onInit(){
// Get called when controller is created
super.onInit();
}

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

@override
void onClose(){
//Get called when controller is removed from memory
super.onClose();
}
}

Creating observables:

You have 3 ways to turn a variable into an “observable”.

a . The first is using Rx{Type}.

// initial value is recommended, but not mandatory
final name = RxString('');
final isLogged = RxBool(false);
final count = RxInt(0);
final balance = RxDouble(0.0);
final items = RxList<String>([]);
final myMap = RxMap<String, int>({});

b . The second is to use Rx and use Darts Generics, Rx<Type>

final name = Rx<String>('');
final isLogged = Rx<Bool>(false);
final count = Rx<Int>(0);
final balance = Rx<Double>(0.0);
final number = Rx<Num>(0)
final items = Rx<List<String>>([]);
final myMap = Rx<Map<String, int>>({});
// Custom classes — it can be any class,
final user = Rx<User>();

c . The third, more practical, easier and preferred approach, just add .obs as a property of your value :

final name = ''.obs;
final isLogged = false.obs;
final count = 0.obs;
final balance = 0.0.obs;
final number = 0.obs;
final items = <String>[].obs;
final myMap = <String, int>{}.obs;
// Custom classes — it can be any class,
final user = User().obs;

Let’s imagine that you have a count variable and want that every time you change it, all widgets that use it are automatically changed.

This is your count variable:

var count = 0;

To make it observable, you just need to add “.obs” to the end of it:

var count = 0.obs;

That’s all. It’s that simple.

Now let’s check how we can display changes on those observables in the UI.

Let's create a controller and add observable in that controller.

class MyController extends GetxController{
var count = 0.obs;
}

You have two different ways to display changes on this observable in UI. Using GetX and using Obx

class DemoGetx extends StatelessWidget {
@override
Widget build(BuildContext context) {
return GetX<MyController>(
init: MyController(),
builder: (myController)=>
Text("Total Count = ${myController.count.value}")
);
}
}

This is your normal stateless widget, the only difference is that it return GetX. For this GetX you pass controller that we want to use. The properties of GetX are init to initialize a controller,

init: MyController(),

& builder to define the method which takes controller as parameter and returns a widget. In above example we are returning Text widget.

Here .value is the observalable property.

Let’s see another example using Obx()

class DemoObx extends GetView<MyController>{
const DemoObx({Key? key}):super(key:key);

@override
Widget build(BuildContext context) {
return Obx(()=> Text("Total Count = ${controller.count.value}"));
}
}

Here for DemoObx class, instead of extending a stateless widget, I’ve extended GetView and passed the controller that we want to use. This will return Obx().

Obx() takes a callback method that returns a widget, here it is Text widget, and inside Text widget we are calling a controller which is controller.h which is observable.value that we want to display on screen.

This is all about reactive state management.

Let’s see simple state management with GetX

In simple state management we don’t have to work with observables but we need to work with normal variables.

class CounterController extends GetxController {
int counter = 0;

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

In this example we have simple counter variable of type int. The main difference is that when we change the value of this variable or when we change the state, we have to call modify() method so that the change can be reflected in UI. With simple state management to show the changes in the UI we use GetBuilder.

Add one more var say total to MyController:

class MyController extends GetxController {
var count = 0.obs;
var total = 0;
}

and

class DemoGetBuilder extends StatelessWidget {
@override
Widget build(BuildContext context) {
return GetBuilder<MyController>(
init: MyController(),
builder: (controller) {
return Text("Total = ${controller.total}");
});
}
}

Above syntax of GetBuilder is very similar to one of GetX.

We are returning GetBuilder and to this we pass a controller which is the MyController that we already define, using init we initialize MyController, then we have builder that takes controller and returns a widget.

But the difference here is to access count we use controller.total, and not a .value property because it’s not a observable, it is a normal variable.

Now to work with reactive and simple state management you can use mixin builder.

Suppose you have controller which has two variables, one is operable by using .obs and second one is normal variable. And this controller also has two methods, one to increase value of observable and other is to increase value of normal variable.

class AnotherController extends GetxController {
var count1 = 0.obs;
int count2 = 0;

void incrementcount1() {
count1.value++;
}

void incrementcount2() {
count2++;
modify();
}
}

To display the changes on the UI you wold have something like this MixinBuilder

class MyWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Page2')),
body: Container(
child: MixinBuilder<AnotherController>(
init: AnotherController(),
builder: (controller) => Column(children: [
Text("Count 1 : ${controller.count1.value}"),
Text("Count 2 : ${controller.count2}")
]))
));
}
}

In this MixinBuilder you pass the type of the controller here AnotherController. We initialise the controller with init, then we have builder that takes the parameter and return a widget. Inside this widget we have Text widget to print count1 value that uses the observable that's why its called as controller.count1.value, again value is the property of observable. and another Text widget just print normal variable with controller.count2

Thank You!

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Yogita Kumar

Yogita Kumar

311 Followers

Google Developer Expert Flutter| Cloud Enthusiast | Full Stack Developer | .NET Developer |Coach and Trainer