Flutter Getx App Tutorial Explained

Created At: 2021-08-15 21:54:06 Updated At: 2023-11-15 01:38:36

There are many packages to main flutter state managent. Getx and Bloc are two them and they are both very poplular.There are others like Provider state management and Riverpod state management .Getx is relatively new but easy to use for beginners and Bloc is more mature since it's older but it's also a little difficult to start with.

In this tutorial you will learn how to build a flutter getx app using flutter getx package step by step. We have covered getx route, state management, passing arguments, named routes, creating controllers and dependency injection. 

Download the starter code from the link below

https://github.com/dastagir-ahmed/flutter-getx-app

Let's look at the index of this tutorial

Basics of Getx

GetX Controller

Dependency injection

onInit() vs onReady()

Obx() or GetBuilder()

GetX makes variable observable

GetX add two or more variables

GetX makes List observable

GetX makes a Map observable

GetX refresh a List

GetX routes

Getx pass arguments

GetX pass screen or widgets as arguments

GetX controllers deleted

GetX GetView

GetX GetBuilder with ID

GetX make model Obs

The video tutorial

Basics of Getx

Getx Controller

Take a look at the common type of controller in Getx. It's just like any other controller in programming language. If you are familiar with PHP, C or C++, Java, this controller would look very familiar to you.

class HomeController extends GetxController {
  final count = 0.obs;

  @override
  void onInit() {
    super.onInit();
  }

  @override
  void onReady() {}

  @override
  void onClose() {}

  void increment() => count.value++;
}

To use GetX, to make your variables reactive or stateful you need your class to extend GetxController. Like you saw in the above example. 

We may override onInit(), onReady() and onClose() method. These three methods play an important role in GetX App lifecycle.

In controller's onInit() method you do initialization like data from network or loading data from JSON files or navigation.

You may also override onReady() method. onReady() method gets called after onInit() method. In this simple example, we are not going to use them.

In the controller your varables could be obs or non obs type. In the above controller, our count variable is obs type. We also declared a custom method name increment(), it increases the value of count variable when you will call this method from the UI.

To increase or decrease the value of a obs type variable you need to call variableName.value. That's what we have done in the increment() method. We have done count.value++ which increases the value of count variable. 

If you want to decrease you may do count.value--

See GetX Advanced Tips Here

Dependency Injection

I think you have seen the below syntax many times in flutter Gext. If not see now. It will save your day.

AnyController controller = Get.put(AnyController());

So here we will replace the syntax with our HomeController

HomeController controller = Get.put(HomeController());

This is called dependency injecting. It means putting or injecting a class or a controller (controllers are class too) into another class so that we can use this controller later. 

In GetX view you can inject the controller like below

// home_view
class HomePage extends StatelessWidget {
  HomePage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {

  //dependency injection
 HomeController controller=Get.put(HomeController());
    return Scaffold(
      body: Container(
        child: Obx(() => Center(child: Text(controller.count.toString()))),
      ),
    );
  }
}

Because we injected the dependency, so now we can access the count variable using controller instance of HomeController. 

At the same time, you see Obx() which is coming from GetX package. This class gets the latest value of your variable from the controller. In our case, it will get the count variable. And this is exactly we have inside the Text() widget.

Because of GetX, you see that we don't need to use a StatefulWidget class anymore. Our extended UI class is StatelessWidget class.

onInit() vs onReady()

onInit() gets called at the very early stage of GetX life cycle. It also gets called before the build method of Flutter framework. 

onInit() gets called once the controller is created

onReady() gets called after onInit() and it also gets called after build method gets called first.

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

  @override
  Widget build(BuildContext context) {
    print("on build method....${controller.name.value}");

    return Scaffold(
        body:SafeArea(
          child:  Center(
            child: Obx(()=>Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Text(
                  "${controller.name.value}",
                  style: TextStyle(fontSize: 25),
                ),
                Text(
                  "${controller.description.value}",
                  style: TextStyle(fontSize: 25),
                )
              ],
            ))

          ),
        ));
  }
}

class UserController extends GetxController{
  var name = "".obs;
  var description ="He is a freelancer".obs;

  @override
  void onInit(){
    super.onInit();
    name.value="ahmed";

    print("on onInit ...${name.value}");
  }

  @override
  void onReady(){
    super.onReady();
    name.value="dylan";

    print("on onReady ...${name.value}");
  }
}

The above two method are very important for GetX lifecycle. Among all the state management, GetX provides the easiest way to maintain App lifecyle. 

Obx() or GetBuilder()

If you have obs variables in your controller then you should use Obx() in your view around obs widgets(the widgets that use the obs variables). If you did not use obs variables, then you have use other mechanism to let the UI know that your variable value has changed.

We do this by calling update() method inside method like increment() or decrement(), then on the UI, you should use GetBuilder() to get the latest updated value. Below is the example for Obx(). 

// home_view
class HomePage extends StatelessWidget {
  HomePage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
  //this is also dependency injection
  var controller=Get.put(HomeController());
    return Scaffold(
      body: Container(
        child: Obx(() => Center(child: Text(controller.count.toString()))),
      ),
    );
  }
}

You don't always have to use arrow function to return inside Obx(). Of course you can also do conditional check in Obx() like below

Obx((){
     if(...){
     return YourWidget();
    }else{
     return YourAnotherWidget();
   }

})

Do remember Obx() only works with the variable of obs type. If your widget contains obs and non obs type variable together, then your Obx() won't be reactive, meaning it won't update the value as you expect.

 

If you did not use obs then you should GetBuilder() around your widget to get access to the controller variable

class HomePage extends GetView<HomeController> {
  HomePage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    HomeController controller = Get.put(HomeController());

    return Scaffold(
      appBar: MyAppBar(
        centerTitle: true,
        title: MyTitle('Home Page'),
        leadingType: AppBarBackType.None,
      ),
      body: Container(
        child: GetBuilder<HomeController>(builder: (controller) {
          return Center(child: Text(controller.count.toString()));
        }),
      ),
    );
  }
}

Of course you need to initialize HomeController first.  You see, how Center widget is wrapped with GetBuilder(). 

Personally I use Obx() more than GetBuilder() since, it's cleaner and with the help of instantiated controller, you can access them anywhere in your view. 

If you want to use Obx(), you must use obs type variable inside your controller. This is also helpful at first glance to know which variables are reactive and which are not.

Anyway, whenever you use obs variable or update method inside your controller, Flutter does the rebuild of ui and state, so you are able to see the changes.

GetX makes variables observable

final name = ''.obs;

final isLogged = false.obs;

final count = 0.obs;

final balance = 0.0.obs;

final number = 0.obs;

These are some of the ways, you can delcare observable variables in Getx.

But if you don't want to use obs end of the variable then you can use update() method inside your custom methods like increment() or decrement().

For exmaple look at this

class HomeController extends GetxController {
  final count = 0;
.......................
 ......................
 ........................

  void increment() {
      count++;
     update();
  }
}

This update() inside increment() function makes it observable. This update() method is avalaible inside GetX library. Just feel free to use it.

Because we used update() method, you should notice that we are not using count.value++ to increase the value.

Getx add two or more variables

class HomeController extends GetxController {
  final count = 0;
  final newCount=0;
.......................
 ......................
 ........................
int get sum=>count.value+newCount.value;

  void increment() {
      count++;
     update();
  }
  void incrementNew() {
      newCount++;
     update();
  }
}

It's pretty easy to add two variables in GetX. In case, you add two obs type variable you can do like below

int get sum=>count.value+newCount.value;

obs type variables setting or getting values is always done with .value;

GetX makes Lists observable

To make a flutter List observable you need to add the obs word with the list as a suffix. You need to declare a list variable first.

There are three different ways you can do it.

var myList = [].obs;
var myList=<Model>[].obs
RxList<Model> myList=<Model>[].obs

Any one of them should work based on your skd version. Learn about Lists and Maps here

GetX makes a Map observable

 var _categories = {
    "Shopping": false,
    "Reading": true,
    "Exercise": true,
  }.obs;

Create a Map, and just use .obs with the curly braces.

And if you want to show the list inside ListView then you need to do like below

Expanded(
      child:Obx((){
        return ListView.builder(
            itemCount: _taskController.myList.length,
            itemBuilder: (_, index){
              print(_taskController.myList.length);
              return Container();
              );
        });
      }),
    );

You must use Obx and wrap ListView.builder using to make the list observable or have state.

Here _taskController is an instance of a controller that extends GetxController. 

GetX refresh a list

If you add things dynamically or remove or update, you might not get new value immediately without refreshing the list. 

To get the most recent value from the list, you need to refresh the list to get updated value. You can do like below

_taskController.myList.refresh();

If you are updating list from the database, then you need to do query first, then replace the old list value with assignAll() function.

void getTasks(){
    List<Map<String, dynamic>> lists= await db.query();
    myList.assignAll(lists.map((e)=>Model.fromJson(e)).toList();
}

You need to modify the above code based on your needs. But to replace the old values you must use assignAll() function.

Also do remember that, if you use Get.back() to go another page and expect the new value in the list, you must call the above code before you use Get.back() to go back to another page.

You can do like this

getTasks();
Get.back();

It will make sure, you get the most recently updated value in Getx List Flutter.

GetX routes

There are a few helper method in Getx for route management. They are 

Get.to()       Get.toNamed()        Get.back()       and GetPage()

In general you can use Get.to() for going to a new page. It takes the page name inside. That's as easy as that.

Get.to(()=>DetailPage());

Now you will go to DetailPage. The file name could be anything. It's the beauty of Getx. It will find the corresponding file and will navigate you there.

So you would wrap it inside onPressed or onTap event.

onPressed:(){
  Get.to(()=>DetailPage());
}

 

You can also specify routes directly inside GetMaterialApp(). All you need to do is to mention initialRoutes and getPages

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return GetMaterialApp(
      title: 'Flutter Demo',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(

        primarySwatch: Colors.blue,
      ),
        initialRoute: "/",
        getPages: [
          GetPage(name: "/", page: ()=>MyHomePage()),
          GetPage(name: "/detail", page: ()=>DetailPage())
        ]

    );
  }
}

initialRoutes refer to the homepage. So do this with a slash "/" .

But you can change to any dart file to the home page. 

Additional routes you can mention within getPages List. It takes a list of routes. If you mention routes in getPages List, you can refer them later in your other pages like below

Get.toNamed("/detail/");

You can also use named route from onTap() or onPressed().

The beautiful thing about named route is that, you can change the page name any time in your application. But you don't need to change the route name.

For big projects It would save your time.

Passing Arguments

If you use flutter GetX it's very easy to send arguments. For passing arguments you can do like below

Get.toNamed('/detail/', arguments:{
  
 });

The field arguments take a dart map

Since it takes a map, you need to send arguments like key pair value. So you can do like below

Get.toNamed('/detail/', arguments:{
       "title":"Page title"
 });

So in detail page you can grab the key which is "title" in our case. So in detail page will do like this to grab the argument value

Get.arguments['title']

If you want to go back to previous page, from your onTap() or onPresses() you can simply call

Get.back()

GetX pass screen or widget as arguments

Getx Controllers Deleted

In general when you load the dependencies, if you mark your controller as fenix:true, your controllers should not get deleted. You may also do permanent:true instead of fenix:true. See it like below

  Get.lazyPut(() => ProductController(productRepo: Get.find()), fenix: true);
 

You can put here any controllers you want. 

But there are times GetX controllers still get deleted. That happens if you use Get.offNamed(your_route) to go or navigate to a different screen. 

Instead of using Get.offNamed(), you may try to use Get.toNamed() or Get.to().

But if you really have to use Get.offNamed(), then you could wrap your GetMaterialApp() inside GetBuilder() like below

      return  GetBuilder<ProductController>(builder:(_){
          return GetBuilder<PopularProduct>(builder: (_){
            return GetMaterialApp(
              scrollBehavior: AppScrollBehavior(),
              debugShowCheckedModeBanner: false,
              title: 'Flutter Demo',
              theme: ThemeData(
                primaryColor: AppColors.mainColor,
                fontFamily: "Lato",
              ),

              initialRoute: RouteHelper.getSplashRoute(),
              getPages: RouteHelper.routes,
              defaultTransition: Transition.topLevel,
            );
          });
        });

 

Try to use your GetBuilder() in the build method of the main function of your app. And then wrap GetMaterialApp using GetBuilder(). But of course, you need to inject controllers inside <> this. Like we did in the above example 

GetBuilder<ProductController>(builder:(_)){.....}

Your problem should be solved.

Getx GetView

GetView class makes it easy to use controller instances without creating or find the controller using below method.

Get.find<YourController>()

In general that's how you find your controller and user it later. But GetView() is an alternative to that.

See the code below to get the controller and replace StatelessWidget 

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

and in your view you just need to write controller keyword and access the properties of the controller.

BackButton(
                color: Colors.white,
                onPressed: (){
                  controller.toogleDrawer();
                },
              )

See that, in the BackButton() Widget we are using controller instance to access the method toogleDrawer(). So GetView helps you reduce of your code and you don't need to keep track of your controller all the time.

GetX GetBuilder With ID

We will see how to update a certain widget in flutter using Getx GetBuilder.

Your certain controller could be used in many different places. And the same controller could be found in many different places.

If you use GetBuilder for that controller in the UI, then when you trigger update using the update() method, controller would update the state in every Ui, that is using the certain controller.

This operation is quite expensive and unnecessary. You may use ID with GetBuilder and the same ID should be mentioned in the update() method. Because update() takes a list of IDs as a string.

Getx Make Model Obs

Here we will see how to make a model obs type or create a custom object and make it observable. It's easy to do. First make sure you have your class or model ready. In this case I will use a UserItem model. Let's take a look

class UserItem {
  String? name;
  String? description;

  UserItem({

    this.name,
    this.description,

  });
}

Now, we will create a controller and in the controller we will make it obs type.

class UserController extends GetxController{
  var _profile = UserItem().obs;

}

Here we will also create a getter to get the _profile variable from the UI. Now here _profile is Rx type. We will create a new UserItem object in onInit() method and change value in onReady() method.

  UserItem get profile => _profile.value;

  @override
  void onInit(){
    super.onInit();
    var profile = UserItem(
      name: "ahmed",
      description: "He is a freelancer"
    );
    _profile(profile);
  }

If you run the app now, you will see name and description on the UI. This is our first successful example how to make a model or object obs type.

After that we also wanna change value from UI. That's why we will do this in our onReady() method.

  @override
  void onReady(){
    super.onReady();
    Future.delayed(Duration(seconds: 2), (){
      _profile.value.name="dbestech";
      print(_profile.value.name);
      _profile.refresh();
    });
  }

This method triggers a change after 2 seconds later and update the name value. And also make sure _profile gets the updated value, so we will refresh() function.

Let's see the complete code

class UserController extends GetxController{
  var _profile = UserItem().obs;
  UserItem get profile => _profile.value;

  @override
  void onInit(){
    super.onInit();
    var profile = UserItem(
      name: "ahmed",
      description: "He is a freelancer"
    );
    _profile(profile);
  }

  @override
  void onReady(){
    super.onReady();
    Future.delayed(Duration(seconds: 2), (){
      _profile.value.name="dbestech";
      print(_profile.value.name);
      _profile.refresh();
    });
  }
}

So onInit() would show the value of the name at first place, and two seconds later, onReady() method would show the udpated value.

Let's take a look at the UI.

void main() {
  Get.put(UserController());
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        primarySwatch: Colors.pink,
      ),
      home: const HomePage(),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: Center(
      child: Obx(
          ()=>Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Text(
                "${controller.profile.name}",
                style: TextStyle(fontSize: 25),
              ),
              Text(
                "${controller.profile.description}",
                style: TextStyle(fontSize: 25),
              )
            ],
          )
      ),
    ));
  }
}

Comment

Add Reviews