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,
- Why is GetX so special?
- State management using GetX
- GetxController
- The Reactive State Manager in GetX
- The Simple State Manager in GetX
- MixinBuilder : Mix your both state managers
- 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
}
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();
}
}
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
}
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
}
3. Adding .obs
to the end
class CounterController extends GetxController {
var counter = 0.obs;
}
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;
}
Using GetX:
GetX<CounterController>(
init: CounterController(),
builder: (controller) => Text(
'Counter is ${controller.counter.value}'
),
),
Using Obx:
Obx(() => Text(
'Counter is ${controller.counter.value}'
),
),
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}'
),
),
),
),
);
}
}
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
}
}
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}'
),
),
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();
}
}
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}');
}
),
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();
}
}
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
]
),
),
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>> {}
And also RxStatus
class provides defined status to use with the StateMixin.
RxStatus.loading();
RxStatus.success();
RxStatus.empty();
RxStatus.error('error message');
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());
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());
}
}
}
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),
)
);
}
}
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.
Top comments (6)
Great article. Would love a tutorial on GetX MVC or Clean Architecture with AppWrite at the backend 🤓. Perhaps an Instagram clone or an e-commerce app; if you’ll be so kind so that we see all of these features in action.
GetX is a great application, but there is no power resource center with complete education and the documentation is sketchy. Bloc has one big advantage because of persons like Resocoder who have intellectually strong resources that can transform a Beginner to an intermediate programmer. As a beginner you can’t even be confident to find enough resources explaining theory and practicals to solve problems with GetX while implementing software engineering principles like SOLID, Clean Architecture, DDD…
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?
Great package Getx
great post
getX + Hindu = love (hello shit code)
how I stream API on statemixin with getx? I tired getx with timer.periodic but my UI is not updating with newly fetch data.