State Management in Flutter with GetX
![Flutter with GetX](https://devblog.link/wp-content/uploads/2021/11/flutter_getX.png)
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