State Management with GetX – powerful micro framework for Flutter

State Management with GetX – powerful micro framework for Flutter

Flutter is Google’s mobile UI framework for crafting high-quality native interfaces on iOS, Android, Web and Desktop. It has already been proven as a successful and fairly reliable choice in the world of open-source. However, that’s not where it ends. Flutter is progressively becoming more relevant and recognised. For example, Canonical – publisher of Ubuntu, made Flutter the default choice for their future mobile and desktop applications.

Explaining GetX

GetX is a Flutter microframework that speeds up the app development by shortening the code length in comparison to the standard variant. The main features of GetX microframework are:

  • State Management
  • Navigation Management
  • Dependency Management
  • Storage
  • Internationalization
  • Validation

This blog post will elaborate the differences between three variants of State Management.

Business Logic inside GetxController

In the GetX ecosystem, controllers are classes where all the business logic of an application is being executed. All the methods and variables defined in GetX ecosystem can be shown in the UI layer of the application.

Every class that inherits GetxController, also inherits DisposableInterface. By removing the view from the navigation stack, in which the controller is located, that exact controller gets deleted from the memory. This process allows lesser and otherwise unnecessary usage of memory, which consequently makes it more efficient.

GetxController comes with three built-in methods: “onInit”, “onReady” and “onClose”.

class HomeController extends GetxController {

  @override

  void onInit() {

  }

  @override

  void onReady() {

  }

  @override

  void onClose() {
  
  }

}

After the controller’s memory allocation, method “onInit” is being called which is used for further calling API from the backend service or local database. Method “onReady” is called upon UI widgets appearing on screen. Method “onClose” is called before the controller gets deleted from the memory.

State Management inside GetX library

GetX supports two modes of “State Management”;
Simple State Manager (“GetBuilder”)
Reactive State Manager (“GetX” and “Obx”)

A simple application will be made through three types of “State Manager” which will consist of one button and text field where numbers will be printed out. The length and general readability of code will be tracked through each instance of “State Manager” variant. By pressing the button “Add”, the number in the text field will increase by 1.

https://www.ingemark.com/wp-content/uploads/2021/05/screen.png

GetBuilder state manager

The UI layer consists of multiple widgets. Widgets need to be wrapped with the GetBuilder method before their interaction. If “Dependency Management” is not used, the controller has to be initialized via “init” property from the GetBuilder method.

class HomeController extends GetxController {
  int counter = 0;

  void add() {
    counter++;
    update();
  }
}

Widget, which was previously wrapped with the GetBuilder method, checks for changes that are beforehand triggered by the call of update() method. Method update() will automatically change the value of the widget element within the GetBuilder method. GetBuilder is a simple state manager which means that its RAM usage is fairly efficient in comparison to the reactive approach.

HomeController homeController = Get.put(HomeController());
. . .
Container(
  child: GetBuilder<HomeController>(
      builder: (_) => Column(children: [
        Text("Number: ${homeController.counter}"),
        ElevatedButton(
            child: Text("Add"),
            onPressed: () => homeController.add())
      ])),
)

If “Dependency Management” is not defined via (Get.put()), the controller has to be defined using the init parameter within the GetBuilder method.

Container(
  child: GetBuilder<HomeController>(
      init: HomeController(),
      builder: (homeController) => 
        Column(children: [
          Text("Number: ${homeController.counter}"),
          ElevatedButton(
              child: Text("Add"),
              onPressed: () => homeController.add())
        ])),
)

GetX State Manager

GetX and GetBuilder state managers have similar code syntax, however, GetX is strictly based on “streams”. Streams are automatically called when new values are pushed onto them. If new values, that are identical to the old ones are added, change will not occur, screen will not update.

class HomeController extends GetxController {
  var counter = 0.obs;
  //RxInt counter = 0.obs;

  void add() => counter.value++;
}

Every new variable or object needs to end with .obs extension which stands for “observable”. Variable “counter” is an integer type stream. GetX method acts as a stream and listens to every change, therefore the update() method is not required to be called manually. “Breaking changes” happened with the introduction of Flutter 2.0. Names of data types were changed, IntX became RxInt, StringX became RxString, etc.

HomeController hc = Get.put(HomeController());
. . .
Container(
  child: GetX<HomeController>(
      builder: (_) => Column(children: [
        Text("Number: ${hc.counter.value}"),
        ElevatedButton(
            child: Text("Add"),
            onPressed: () => hc.add())
      ])),
)

In the reactive mode, every variable or object has to end with the .value extension.

Obx State Manager

Obx State Manager is the most popular implementation choice because of its simplistic and short syntax. Controller in Obx state manager is identical to that in GetX state manager. The difference is noticeable in the UI layer because Obx does not support init property, therefore the controller has to be initialized via “Dependency Management”. Obx state manager does not require the type of controller to be defined. It can listen to changes from multiple controllers at once. Obx is a widget that contains “StreamSubscription” which receives changed events from its children, in this scenario controllers.

HomeController homeController = Get.put(HomeController());
. . .
Container(
  child: Obx(() => Column(children: [
    Text("Number: ${homeController.counter.value}"),
    ElevatedButton(
        child: Text("Add"), 
        onPressed: () => homeController.add())
  ])),
)

Subscribe on changed event with Get Workers

As mentioned at the beginning of the blog, controller consists of three methods. Controller subscribes to ever() method. Method ever() can be located either within the “onInit” method, which is executed upon the initialization of the controller, or the “onReady” method which is executed when UI widgets appear on screen.

var counter = 0.obs;
. . .
@override
void onInit() {
  ever(counter, (val) => print("Changed value to ${val}"));
  super.onInit();
}

In addition to ever() method, methods everAll() and once() are applicable.

  • everAll([. . .], (val)) – subscription to multiple variables or objects
  • once(counter, (val)) – subscription to only the first changed variable or object

Let’s create sample project with GetX library and Obx State Manager

An example of a simple application that utilises GetX library and Obx state manager will be shown in the upcoming part. That application will call backend service to receive necessary data. The received data from backend in JSON format will be deserialized into “Album” object. ListView widget will be responsible for displaying data on mobile phone screen. Pull to refresh, from the ListView widget, is going to be used for calling the backend service and getting new data.

GetX and Dio (Http client) require dependency within the “pubspec.yaml” file.

dependencies:
  dio: ^4.0.0
  get: ^4.1.4

Internet permissions are required within the “AndroidManifest.xml” file.

<uses-permission android:name="android.permission.INTERNET" />

Model used for mapping JSON into object according to the request from the backend needs to be created.
Album endpoint example: https://jsonplaceholder.typicode.com/albums

import 'package:flutter/material.dart';

class Album {
  final int userId;
  final int id;
  final String title;

  Album({
      @required this.userId, 
      @required this.id, 
      @required this.title});

  factory Album.fromJson(Map<String, dynamic> json) {
    return Album(
      userId: json['userId'],
      id: json['id'],
      title: json['title'],
    );
  }
}

Afterwards, a controller which extends GetxController is created. List with .obs extension needs to be created which then becomes a list of streams. Data is collected via Dio library and is added to the list.

import 'package:get/get.dart';
import 'package:news_app/models/album.dart';
import 'package:dio/dio.dart';

class AlbumController extends GetxController {

  var albums = [].obs;

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

  Future<Album> fetchAlbums() async {
    try {
      var response = await Dio().get('https://jsonplaceholder.typicode.com/albums');
      if(response.statusCode == 200){
        albums.assignAll(response.data.map((album) => Album.fromJson(album)));
        albums.shuffle();
      }
    } catch (e) {
      throw Exception(e);
    }
  }
}

“Dependency Management” is initialized via (Get.put()) method in the UI layer. Widget RefreshIndicator is used for the “PullToRefresh” feature, with which new data is collected and served. New widget Text() gets added in the ListView.builder(. . .) widget, which displays changed data. State manager Obx() listens to changed data on the albums variable.

@override
Widget build(BuildContext context) {
  final AlbumController _ac = Get.put(AlbumController());

  return Scaffold(
    appBar: AppBar(
      title: Text("Albums"),
    ),
    body: Container(
        child: Obx(() => RefreshIndicator(
              onRefresh: _ac.fetchAlbums,
              child: ListView.builder(
                itemCount: _ac.albums.length,
                itemBuilder: (BuildContext context, int index) {
                  final Album album = _ac.albums[index];
                  return Padding(
                      padding: EdgeInsets.all(20),
                      child: Text('Album id: ${album.id} with title: ${album.title}'));
                },
              ),
            ))),
  );
}

Example App

https://www.ingemark.com/wp-content/uploads/2021/05/01.png
Ingemark successfully implemented management system according to ISO 9001:2015 and ISO 27001:2013 standards
Clutch classified Ingemark on the top 100 fastest growing companies

Related posts

App Clips: How you can make your iOS app better

App Clips: How you can make your iOS app better

Ingemark 08.01.2021.
Only constant in software is Change

Only constant in software is Change

Ingemark 07.01.2021.
Why touch-typing is good for programmers

Why touch-typing is good for programmers

Ingemark 14.12.2018.

By Clicking on Accept, you agree to the storing of cookies on your device to enhance site navigation, analyze site usage, and assist in our marketing efforts. For more details, please review our Privacy Policy.

Accept all