Complete guide to GetX in Flutter

In this article, I have explained GetX in Flutter in detail. We looked at State Management, Dependency Management and Route Management.
GetX in Flutter

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 which 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 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 resources usage and highly efficient applications.
  • Productivity: GetX reduces the boilerplate code present in Flutter. Also GetX automatically releases the controllers when not in use which reduces the headache of disposing instances.
  • Organization: GetX follows a model where the View, presentation logic, and buisness logic are completely decoupled. This results in easy to understand project and easy management.

Three Pillars of GetX 🕒

GetX mainly resolves three issues. These are:

  1. 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.
  2. 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.
  3. 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 using Get.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();
  }
}

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 :

  1. Using Rx{Type}
final counter = RxInt(0);
final message = RxString("BrewYourTech");
final list = RxList<int>([]);
  1. 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>();
  1. 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(),
)

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:

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?

Leave a Reply

Your email address will not be published.

Previous Post

App Lifecycle in Flutter

Next Post
Featured image for Testing in Flutter.

Testing in Flutter – Complete Guide

Related Posts