GetX State Management – An Interesting Guide for Flutter App

GetX is a trivial and powerful solution for Flutter Apps. It combines high-performance state management, dependency injection, and route management quickly and practically. Furthermore, GetX relies on the concept of 3 pillars i.e. productivity, performance, and organization, which straightforwardly states less consumption of resources, better readable syntax, as well as view decoupling (independent approach methodology). However, in this blog post, we shall cover the core concept of GetX, develop a new project from scratch, and apply all the relevant methodologies present in the plugin, so let’s explore GetX State Management – An Interesting Guide for Flutter App.

Table of Contents

GetX Expanded View

In essence, GetX state management revolves around the concept of an observable state. It allows you to define your application’s state in a way that automatically updates the user interface whenever the state changes. Here’s a breakdown of key components:

Controller

A controller is a class responsible for managing a specific part of an application’s state. Moreover, the controller extends GetxController, which provides functionality to observe the state changes. Furthermore, a controller can hold variables that represent the state of an application.

Obx (Observer)

The Obx widget is a crucial part of GetX state management and is used to listen for changes in the controller’s state. When the state within the controller changes, the Obx widget automatically rebuilds the relevant part of the User interface that depends on the state.

Reactive Programming

GetX relies on reactive programming principles, where changes in the state automatically trigger updates in the UI. Additionally, we can use update() and refresh() methods to trigger updates upon need.

GetX Bindings

GetX introduces the concept of bindings, which is a way to connect controllers to specific parts of an application. Bindings help manage the lifecycle of controllers and ensure they are appropriately initialized and disposed of.

Efficient Memory Management

GetX emphasizes efficient memory management, automatically disposing of controllers that are no longer needed.

Let’s now look at our project overview and get things done.

Project Overview

We shall be developing an app that will consist of an HTTP request. Furthermore, this project would have all the essential structural pattern hierarchies for producing an excellent output.

Folder Structure

lib
- main.dart
- app.dart
- view/
  - widgets/
- view_model/
- models/
- repostitory/
- utils/
- data/
  - network/
    - network_api_service.dart
    - base_api_service.dart
  - app_exceptions.dart

Implementation

Let’s now head over to our project and get things in place.

Installing the plugin

To the pubspec.yaml file, add this plugin:

get: any //choose suitable version from pub.dev or this way...

Run this command:

flutter pub get

GetMaterialApp

Create a new file app.dart and make it the initial one.

Note: To get all the features listed by Get itself, replace MaterialApp with GetMaterialApp.

Once we are done with it, let’s define relevant routes for our app.

GetPages

Create a new file under the res directory app_routes.dart and add the following code:

class AppRoutes {

  static getRoutes() => [
    GetPage(
      name: '/',
      page: () => SplashView(),
    ),
    GetPage(
      name: '/countries',
      page: () => CountriesView(),
    ),
  ];
}

Note: For the sake of simplicity, we have just added two routes for now.

Secondly, GetPage is pre-built with tons of different features as well, which are as follows:

  • maintainState = true
  • curve = Curves.linear
  • alignment
  • transitionDuration
  • transition
  • arguments → dynamic

Let’s now attach this file with app.dart and touch up with the final snippet

Code Snippet

class App extends StatelessWidget {
  const App({super.key});

  @override
  Widget build(BuildContext context) {
    return GetMaterialApp(
      debugShowCheckedModeBanner: false,
      getPages: AppRoutes.getRoutes(),
      initialRoute: '/',
    );
  }
}

Parsing HTTP request

Under the data → network directory, create two files, namely base_api_service and network_api_service respectively.

base_api_service:

This class would be an abstract one, defining only the relevant method.

abstract class BaseApiService {

  Future<void> get();
}

network_api_service:

This class would consist of the actual implementation of the get method.

class NetworkApiService extends BaseApiService {

  @override
  Future<void> get() async {
    dynamic jsonResponse;

    http.Response res = await http.get(Uri.parse(url)).timeout(
      const Duration(seconds: 3),
    );
    jsonResponse = jsonOutput(res);

    return jsonResponse;
  }

  dynamic jsonOutput(http.Response res) {
    switch (res.statusCode) {
      case 200:
        List<dynamic> jsonList = json.decode(res.body);
        return jsonList;

      case 500:
        throw FetchDataException("Internal server error ${res.body}");
      default:
        throw BadRequestException("Bad request ${res.body}");
    }
  }
}

Let’s now create our custom_exceptions.dart.

Note: The entire code snippet related to app_exceptions has been added to the blog post:

Provider State Management – An Awesome Guide for Flutter App

Let’s now resume where we left off.

Next, create a new file app_repository.dart under the repository directory, define the NetworkBaseClass Object, and add the method to fetch the countries list.

class CountryRepository {
  final NetworkApiService service = NetworkApiService();

  getCountries() async {
    var res = await service.get();
    return res;
  }
}

We’re done with the repository stuff.

Let’s now head to the final section before the view arrives.

View Model

Under the view_model directory, add a new file app_view_model.dart and add the following code:

class CountryController extends GetxController {

  RxBool isLoading = false.obs;
  RxList<dynamic> cList = <dynamic>[].obs;

  CountryRepository repository = CountryRepository();

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

  getCountries() async {
    isLoading.value = true;

    var res = await repository.getCountries();
    List<dynamic> countries = res.map((json) => CountryModel.fromJson(json)).toList();
    cList.value = countries;

    isLoading.value = false;
  }
}

In this file, we have the following:

  • A bool to counter API response
  • A list that gets fulfilled on data arrival
  • A method that converts the response to the designated model

Here, we are almost done with our app.

It’s time to get this class called under country_view.dart.

Under the above-mentioned file:

class CountriesView extends StatelessWidget {
  CountriesView({super.key});

  /// Defining the controller
  CountryController countryController = Get.put(CountryController());

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("Country"),
      ),
      body: Obx(() {
        return countryController.isLoading.value ? Shimmer.fromColors(
          baseColor: Colors.grey[300]!,
          highlightColor: Colors.grey[100]!,
          child: ListView.builder(
            itemBuilder: (_, __) => Padding(
              padding: const EdgeInsets.symmetric(
                  horizontal: 4,
                  vertical: 5
              ),
              child: Row(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Container(
                    width: 48.0,
                    height: 48.0,
                    color: Colors.white,
                  ),
                  const Padding(
                    padding: EdgeInsets.symmetric(horizontal: 8.0),
                  ),
                  Expanded(
                    child: Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: <Widget>[
                        Container(
                          width: 150,
                          height: 8.0,
                          color: Colors.white,
                        ),
                        const Padding(
                          padding: EdgeInsets.symmetric(vertical: 2.0),
                        ),
                        Container(
                          width: 80.0,
                          height: 8.0,
                          color: Colors.white,
                        ),
                      ],
                    ),
                  )
                ],
              ),
            ),
            itemCount: 15,
          ),
        ) : ListView.builder(
          shrinkWrap: true,
          itemCount: countryController.cList.length,
          itemBuilder: (context, i) {

            CountryModel item = countryController.cList[i];
            return CountryCustomView(model: item);
          },
        );
      }),
    );
  }
}
Widget View
class CountryCustomView extends StatelessWidget {
  const CountryCustomView({super.key, required this.model,});

  final CountryModel model;

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.symmetric(
        horizontal: 4,
        vertical: 5
      ),
      child: Row(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          SizedBox(height: 50, width: 50,child: SvgPicture.network(model.flags!.svg!, fit: 
          BoxFit.contain)),

          Container(
            margin: const EdgeInsets.only(left: 10),
            width: MediaQuery.of(context).size.width * 0.70,
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(model.name!.official!, style: const TextStyle(
                  fontSize: 16,
                  fontWeight: FontWeight.w700
                ),),

                Text("Commonly known as ${model.name!.common!}", style: const TextStyle(
                    fontSize: 12,
                    fontWeight: FontWeight.w700
                ),),
              ],
            ),
          ),
        ],
      ),
    );
  }
}
Video Preview

Our blog post ends here.

Hope you enjoyed reading it!

Summing Up

GetX State Management – An Interesting Guide for Flutter App was all about compact information regarding GetX methodology. In addition to this, we created an entirely new application with a suitable architectural pattern. Moreover, we break down our snippets into simpler widgets to get our stuff done easily.

However, if you think I missed out on something or want to convey a message, just jot down in the comment section below this blog post. I shall try my best to respond as quickly as possible.

— Link to the App’s repository —

Read out my other blog posts

Firebase Database Series

Local Database Series

Fluro Routing Technique

Thanks for your precious time!

Leave a Comment