In Flutter, you may have come across several State Management Techniques like BLoC, Provider, RiverPod, ChangeNotifier, MobX, etc. GetX is a fast, lightweight, and stable State Management package that provides many additional features as well. In this article, we will take a deep dive into using GetX in Flutter.
Introduction to GetX in Flutter 🌃
GetX is an extra light and powerful solution to the problem of State Management in Flutter. It comprises less boilerplate code, highly efficient state management, and easy routing. GetX has 3 basic principles:
- Performance: GetX focuses on minimal resource usage and highly efficient applications.
- Productivity: GetX reduces the boilerplate code present in Flutter. Also, GetX automatically releases the controllers when not in use, reducing the headache of disposing of instances.
- Organization: GetX follows a model in which the View, presentation, and business logic are entirely decoupled. This results in easy-to-understand projects and easy management.
Three Pillars of GetX 🕒
GetX mainly resolves three issues. These are:
- State Management: GetX has two ways to manage the state. One is a simple way another is the reactive way. We will discuss this is in detail in the next section.
- Route Management: If we want to navigate to another page, show a snack bar, show dialog, etc we can do all this easily using GetX, without caring about context.
- Dependency Management: Get has a powerful dependency manager that allows you to retrieve your controller anywhere you want.
Now that we have a brief overview of the objectives of GetX. Let’s discuss how does it achieve such reactive and dynamic state management. For that, we will first briefly discuss GetxController in GetX, as everything revolves around those GetxControllers!
Exploring GetxController ✨
The GetxController class is responsible for the presentation part and for the backend part. We keep all the variables, controllers, and backend logic inside the controller class. The update()
method in GetxController class is responsible for rebuilding the GetBuilder widget. We will discuss the GetBuilder widget in detail at a later point.
We create a Controller class as follows:
import 'package:get/get.dart';
class HomeController extends GetxController{}
Now, let’s take a look at what the controller of the counter app would look like:
import 'package:get/get.dart';
class HomeController extends GetxController{
int counter = 0;
void increment(){
counter++;
// Re-runs the builder function of GetBuilder widget
update();
}
}
import 'package:get/get.dart';
class HomeController extends GetxController {
// We will discuss .obs later in the State Mangement Section
var count = 0.obs;
increment() => count++;
}
We will later see how to use these controllers later. To create an instance of the HomeController class we have 2 ways:
- Simple Instance: This instance is only available till the current widget is mounted. Just like any other variable, which is bounded to the lifecycle of the widget.
HomeController controller = HomeController();
- Persistent Instance: This instance is created using
Get.put()
and can be accessed anywhere once created. You can have a lot of controllers of different pages and access them anywhere once created. We access the controller usingGet.find<T>()
. We will discuss this in a moment.
HomeController controller = Get.put(HomeController());
The GetxController also provides us with app lifecycle methods that we can implement so we can dispose of and initiate objects separately from the UI part:
import 'package:get/get.dart';
class HomeController extends GetxController {
late int count ;
increment() {
count++;
update();
}
@override
void onInit() {
// Init variables and subscribe to streams here
count = 0;
super.onInit();
}
@override
void onReady() {
// Called 1 Frame after onInit
super.onReady();
}
@override
void onClose() {
// Dispose Controllers or remove listeners to streams here
super.onClose();
}
}

You might also be interested in:
Dependency Management 👩💼
When we say dependency management we refer to our controllers as dependencies. Using GetX we can make our dependencies persistent. Persistent here means to not be bounded by the boundaries of the widget lifecycle. If we want our controllers to be accessible throughout the application or access and share the same controller across two or more screens it’s easily achievable using GetX.
Now we will discuss the ways to make a persistent dependency:
1. Get.put & Get.putAsync
Both these functions are majorly the same, just that putAsync provides an asynchronous function to save an instance. These functions immediately initialize the instance as a Singleton instance into the memory and return the object. As soon as we use the put or putAsync, GetX internally calls Get.find()
and returns the object. Both have one Positional argument which is responsible for providing the instance.
S put<S>(
S dependency, {
String? tag,
bool permanent = false,
S Function()? builder,
})
Future<S> putAsync<S>(
Future<S> Function() builder, {
String? tag,
bool permanent = false,
})
The functions, also provide two named parameters tag and permanent. Let’s say we want to save multiple instances of the same class type, in that case, we differentiate the instances using the tag parameter.
The dependency gets disposed of if you remove the route which used Get.put
to save the dependency. However, if you want the dependency to persist for the lifetime of the app, we set the permanent parameter to true:
HomeController controller = Get.put(HomeController(), permanent: true);
Now, this particular instance will be available throughout the app’s lifetime. The syntax for putAsync
will also be similar:
HomeController controller = Get.putAsync(
() async {
// await any task here
return HomeController();
},
permanent: true,
);
2. Get.create
It creates your dependency, similar to Get.put
but here the permanent parameter is true by default. The important thing to note here is that every time you use Get.find()
it runs the builder method and returns a new instance every time. It does not follow the singleton pattern. Also, it doesn’t immediately create the instance and return the object, like the put
method. We can at a later point get a new instance of the controller using the Get.find()
.
void create<S>(
S Function() builder, {
String? tag,
bool permanent = true,
})
You might not use this function as frequently as put()
because it creates a new instance every time we use Get.find()
. While in most cases we want a single instance across multiple screens.
3. Get.lazyPut
We use this when we want to lazily inject a dependency. The dependency is created but loaded into memory only once we use Get.find()
to search the dependency.
Get.lazyPut(() => HomeController(), fenix: true);
Like the permanent parameter in Get.put
, the Fenix property ensures that the dependency can exist for the lifetime of the app.
Another way of dependency injection is using the Binding class in GetX. You can read more about Bindings in GetX here.
State Management using GetX in Flutter👨💼
GetX doesn’t use Streams or Value Notifier like other state management techniques. The issue with these is that we need to subscribe and cancel subscriptions manually. GetX internally uses GetValue and GetStream which maintain performance while using fewer resources. GetX has 2 types of State Managers:
- Simple Way using GetBuilder
- Reactive Way using GetX & Obx
Exploring GetBuilder
GetBuilder is a widget that is used to consume the GetxController class instance. Whenever the update()
is called in the controller, the GetBuilder rebuilds. GetBuilder is complementary to the update()
function. The GetBuilder is a powerful widget that provides the init state, dispose, and other properties of a stateful widget. Thus, we don’t even need to use Stateful Widget when using GetX.
GetBuilder<HomeController>(
init: HomeController(),
initState: (controller) {},
dispose: (controller) {},
builder: (controller) {},
),
If you mention the dependency in the init
parameter it gets disposed of automatically when you move to another page. The init
parameter is not mandatory if you have already injected the dependency in the route.
import 'package:get/get.dart';
import 'package:flutter/material.dart';
class HomePage extends StatelessWidget {
Get.put(HomeController());
@override
Widget build(context) {
return Scaffold(
body: GetBuilder<HomeController>(
builder: (HomeController controller) {
// Automatically gets the controller
return Container();
},
),
);
}
}
The GetBuilder also supports the tag
argument using which you can specify which dependency to consume.
GetBuilder<HomeController>(
tag: "tagForController",
builder: (HomeController controller){
return Container();
},
),
If you don’t want to define the lifecycle methods in the controller class, you can define them in the GetBuilder as parameters:
GetBuilder<HomeController>(
initState: (controller) => controller.getData(),
dispose: (controller) => controller.closeStreams(),
builder: (controller) => Container()),
),
We will later see an example of how to use GetBuilder with a complete example. For now, let’s see how to create reactive variables in Flutter using GetX.
Declaring Reactive Variables
A reactive variable is a variable that has the capability of automatically notifying listeners when its value changes. We have 3 ways to declare a variable as a Reactive Variable in GetX :
- Using Rx{Type}
final counter = RxInt(0);
final message = RxString("BrewYourTech");
final list = RxList<int>([]);
- Using Rx<Type>
final counter = Rx<Int>(0);
final message = Rx<String>("BrewYourTech");
final list = Rx<List<int>>([]);
// Can be used on custom classes too
final auth = Rx<Auth>();
- Using .obx (Most Preferred)
final counter = 0.obs;
final message = "BrewYourTech".obs;
final list = [].obs;
// Custom Class
final auth = Auth().obs;
Simply add .obs behind your traditional variable to make it reactive.
Exploring GetX and Obx
The GetBuilder gets updates when you want to update the UI, however, the GetX and Obx get updates whenever the value changes. When we use Getx and Obx we say that we are using reactive methods. This means that we are using a stream of data that changes value and updates all listeners. We don’t need to update notifiers manually using update()
here.
GetX
GetBuilder is fast and efficient but behaves more like a ChangeNotifier, as it only gets notified when manually said to. The GetX widget is used to achieve reactiveness, which receives events like a stream of data.
GetX is still more economical than any other reactive state manager, but it consumes a little more RAM than GetBuilder.
GetX syntax is very similar to GetBuilder, with only the change in absolute reactiveness. You can assume it to be like StreamBuilder with some Syntactic sugar and optimizations:
class HomeController extends GetxController {
var counter = 0.obs;
increase() => counter.value++;
}
// GetX
GetX<HomeController>(
init: HomeController(),
builder: (val) => Text(
'${val.counter.value}',
),
),
In GetX widget also you don’t need to provide the init
argument, you can pre initialize the dependency and it will automatically use the Get.find()
to get the dependency. GetX is very optimized and makes sure that if the value is the same, the build is skipped.
Obx
Obx is very similar to GetX, however, it only has one parameter. It only has a positional argument method that returns the widget to be built. Every time the reactive variable changes the Obx builder rebuilds.
Obx(() => Widget);
Obx doesn’t need any type, like GetBuilder or GetX. Thus we can use multiple controllers within the Obx widget. It also doesn’t provide any lifecycle methods like GetBuilder or GetX widgets.
Obx(() => Text(controller1.id.value + controller2.name.value);
It is more economical than GetX, but loses to GetBuilder, which was to be expected, since it is reactive, and GetBuilder has the most simplistic approach that exists.
Now that we have discussed all the State Management Techniques in GetX let’s see what the implementation of the counter app would look like in all the State Management Techniques.
There are some additional widgets that GetX provides but we won’t be talking about those here. To use those widgets we must have knowledge of Bindings. We will discuss bindings in detail in another post.
Route Management using GetX in Flutter🛣
GetX simplifies the Routing in Flutter, using GetX you don’t need context to route to different pages, show snack bars, show dialog, show bottom sheets, etc. To make use of GetX routing capabilities it is a must to replace your MaterialApp
with GetMaterialApp
:
GetMaterialApp(
home: HomePage(),
)
Navigation Functions
To navigate to a new screen:
Get.to(HomePage());
// OR
Get.toNamed("/HomePage");
To close sheets, dialogs, snack bars, etc. Replacement of Navigator.pop(context)
:
Get.back();
To go to the next screen with no option to go back, Replacement of Navigator.pushReplacement()
:
Get.off(HomePage());
// OR
Get.offNamed("/HomePage");
To go to the next screen and clear the route stack, Replacement of Navigator.pushAndRemoveUntil()
:
Get.offAll(HomePage());
// OR
Get.offAllNamed("/HomePage");
Defining Named Routes
We can easily define named routes in the GetMaterialApp using the getPages
parameter, like this:
GetMaterialApp(
getPages: [
GetPage(name: "/", page: ()=> const SplashPage()),
GetPage(name: "/HomePage", page: () => const HomePage()),
],
);
To handle the unknown route case do the following:
GetMaterialApp(
unknownRoute: GetPage(name: '/notfound', page: () => UnknownRoutePage()),
);
You can also pass arguments in the named route, Get accepts all data types including custom classes. Simply do:
Get.toNamed("/HomePage", arguments: "Any Argument type");
To receive the parameters in the class or class controller, simply:
var args = Get.arguments;
GetX has some routing functionalities which help when developing for the web. Now we will see how to show dialogs, snack bars, and sheets.
Snackbar, Dialog, and Sheets
To show snack bar, dialog, or sheets in Flutter we require context and sometimes a GlobalKey attached with the Scaffold. However, when using GetX we can easily show any of these without any context.
Let’s see how to show the snack bar using GetX:
Get.snackBar("Title", "Content");
// This by default is not so appealing, So you may use
Get.showSnackBar();
// Or
Get.rawSnackBar();
final snackBar = SnackBar(
content: Text('Content of Snackbar'),
action: SnackBarAction(
label: 'Ok',
onPressed: (){}
),
);
// Find the right context or use a Global Key attached to Scaffold
Scaffold.of(context).showSnackBar(snackBar);
Now let’s see how to show a dialog in Flutter using GetX:
Get.dialog(YourDialog);
showDialog(
context: context,
builder: (_) => YourDialog,
);
At last, let’s see how to show a bottom sheet in Flutter using GetX:
Get.bottomSheet(YourSheetWidget);
showModalBottomSheet(
context: context,
builder: (context) => YourSheetWidget,
);
Conclusion 🔚
In this article, I have explained GetX in Flutter in detail. We looked at each of the pillars of GetX in detail. GetX also provides a wide variety of additional features that you can check out. I hope now you can easily use GetX as your state management solution in your next project. You might be looking to read on these topics next:
- App Lifecycle in Flutter
- Complete Guide to ValueNotifier in Flutter
- Null Safety in Dart, Flutter Essentials
- Complete Guide to StreamBuilder
- Streams in Flutter, Simplified
If you have any queries you can comment below and I will be happy to help you. If you are new to Flutter check my post on how to install Flutter on windows?
Thank you very much