Reactive Loading Management in Flutter with GetX: A Cleaner Approach

Muhammet Aydın
4 min readSep 29, 2024

--

Made it with ChatGpt. “prompt: Here is your updated Medium cover image in a 16:9 aspect ratio for your article “Reactive Loading Management in Flutter with GetX: A Cleaner Approach.”

When working on Flutter applications, managing loading states is a crucial part of the user experience, especially for actions like logging in, registering, or sending a password reset email. Traditionally, handling multiple loading states can lead to a lot of boilerplate code, which in turn makes the codebase harder to maintain.

In this article, I’ll explain how I tackled this problem using GetX, an excellent state management tool, and how my approach simplifies loading management, resulting in cleaner and more maintainable code. I’ll also showcase how much more code I’d have written if I hadn’t adopted this cleaner approach.

The Problem

Imagine you are building an app that requires user authentication. Every time the user interacts with a button that triggers an async operation (login, register, or password reset), you need to display a loading indicator. This is easy to manage with a single loading state, but what happens when you have several buttons? Managing multiple loading states can quickly become repetitive and bloated.

The Cleaner Approach

I wanted a way to manage multiple loading states using GetX, but in a manner that would require minimal code and prevent redundant reactivity. Here’s how I accomplished it:

Step-by-Step Breakdown

1. Enum for Loading States

First, I created an enum to represent the different types of buttons that require loading states:

enum LoadingWidgetEnum {
login,
register,
forgotPassword,
}

2. RxMap for Centralized Loading Management

I used a reactive map (RxMap) to store loading states for different buttons, reducing the need for individual RxBool variables for each button.

final RxMap<LoadingWidgetEnum, bool> _loadingStates = <LoadingWidgetEnum, bool>{}.obs;

3. Method to Update Loading State

I implemented a method to set the loading state for any button based on the enum:

void _setLoading(LoadingWidgetEnum buttonId, bool isLoading) {
_loadingStates[buttonId] = isLoading;
}

4. Checking the Loading State

To avoid multiple clicks when an operation is ongoing, I created a method to check the current loading state of a button:

bool isloadingStates(LoadingWidgetEnum buttonId) {
return _loadingStates[buttonId] ?? false;
}

5. Performing Actions with Loading State Management

I wrapped async actions like logging in, registering, or resetting a password inside a performAction method. This way, I handle the loading state before and after the action, preventing multiple clicks and redundant code:

Future<void> performAction(
LoadingWidgetEnum buttonId, Future<void> Function() action) async {
if (isloadingStates(buttonId)) return; // Prevent multiple clicks
_setLoading(buttonId, true);
try {
await action();
} catch (e) {
handleError(e);
} finally {
_setLoading(buttonId, false);
}
}

6. Button UI

Here’s how I integrated the loading state with the UI using Obx, which observes reactive variables and rebuilds the widget when changes occur:

Obx(
() => MyTextButton(
isLoading: controller.isloadingStates(LoadingWidgetEnum.forgotPassword).obs,
text: sC.send_password_to_email,
onPressed: () async {
await controller.performAction(
LoadingWidgetEnum.forgotPassword,
() => controller.sendPasswordResetEmail(_emailController.text));
}),
),

This setup automatically updates the button’s loading state when the async action starts and ends, keeping the UI responsive and avoiding multiple clicks.

The Alternative: More Code, More Complexity

Without the centralized approach, you would end up writing a separate loading state for each button, resulting in a lot of repetitive code. Here’s what it would look like without my approach:

1. Separate Reactive Variables

final RxBool _isLoginLoading = false.obs;
final RxBool _isRegisterLoading = false.obs;
final RxBool _isForgotPasswordLoading = false.obs;

2. Updating Loading States Manually

void login() async {
_isLoginLoading.value = true;
try {
await _authService.signIn(email, password);
Get.offAllNamed('/home');
} finally {
_isLoginLoading.value = false;
}
}


void register() async {
_isRegisterLoading.value = true;
try {
await _authService.register(email, password, name);
Get.offAllNamed('/home');
} finally {
_isRegisterLoading.value = false;
}
}

void sendPasswordResetEmail() async {
_isForgotPasswordLoading.value = true;
try {
await _authService.sendPasswordResetEmail(email);
Get.snackbar('Success', 'Password reset email sent');
} finally {
_isForgotPasswordLoading.value = false;
}
}

3. UI for Each Button

Obx(
() => MyTextButton(
isLoading: controller._isLoginLoading.value,
text: 'Login',
onPressed: controller.login),
),
Obx(
() => MyTextButton(
isLoading: controller._isRegisterLoading.value,
text: 'Register',
onPressed: controller.register),
),
Obx(
() => MyTextButton(
isLoading: controller._isForgotPasswordLoading.value,
text: 'Send Password Reset Email',
onPressed: controller.sendPasswordResetEmail),
),

As you can see, you’d end up with more repetitive code, separate reactive variables for each button, and additional methods to handle each state. This would not only increase complexity but also make future maintenance difficult as the app scales.

The Payoff

By using a centralized RxMap and an enum, I reduced the codebase complexity while keeping everything modular and maintainable. Any additional buttons or loading states can now be added with minimal effort and without duplicating code.

Conclusion

We explored how to efficiently manage loading states in Flutter using GetX with RxMap and enums. This approach reduces boilerplate, improves readability, and simplifies scaling. Instead of repetitive loading flags and conditions for each action, we centralized the logic with the performAction method. This structure not only keeps the code clean but also makes it easier to add new features, enhancing both maintainability and scalability.

--

--

No responses yet