flutter
/

GetX Loading Indicator: Show Loading States in Flutter

Last Sync: Today

On this page

11
0%
5 min read
Remaining
5 minleft

Click any section to jump — progress syncs automatically

flutter

GetX Loading Indicator: Show Loading States in Flutter

Introduction

Loading indicators are essential for providing visual feedback to users while async operations (like API calls, database queries, or file uploads) are in progress. GetX makes it easy to manage loading states using reactive variables, the StateMixin, or even modal overlays. This guide covers the most common patterns for showing loading indicators in GetX-powered Flutter apps.

  1. Basic Loading with Reactive Bool

The simplest way is to use a reactive boolean (isLoading.obs) in your controller. Set it to true before the async operation and false after (including in finally). Then in the UI, use Obx to conditionally show a loading widget.

DARTRead-only
1
class MyController extends GetxController {
  var isLoading = false.obs;
  var data = ''.obs;

  Future<void> fetchData() async {
    isLoading.value = true;
    try {
      // simulate async work
      await Future.delayed(Duration(seconds: 2));
      data.value = 'Data loaded';
    } finally {
      isLoading.value = false;
    }
  }
}

// UI
Obx(() => controller.isLoading.value
    ? CircularProgressIndicator()
    : Text(controller.data.value));

  1. Using StateMixin for Clean Loading/Error/Success

StateMixin provides built‑in loading, success, and error states. It reduces boilerplate and gives you a standard way to handle all states.

DARTRead-only
1
class MyController extends GetxController with StateMixin<String> {
  Future<void> fetchData() async {
    change(null, status: RxStatus.loading());
    try {
      await Future.delayed(Duration(seconds: 2));
      change('Data loaded', status: RxStatus.success());
    } catch (e) {
      change(null, status: RxStatus.error(e.toString()));
    }
  }
}

// UI
controller.obx(
  (data) => Text(data!),
  onLoading: CircularProgressIndicator(),
  onError: (error) => Text('Error: $error'),
);

  1. Loading Overlay with Get.dialog

Sometimes you want to block the whole screen with a loading indicator while an operation is in progress. You can use Get.dialog to show a modal loading overlay and dismiss it when done.

DARTRead-only
1
class MyController extends GetxController {
  Future<void> performAsyncTask() async {
    // Show loading dialog
    Get.dialog(
      Center(child: CircularProgressIndicator()),
      barrierDismissible: false, // prevent tap to close
    );
    try {
      await Future.delayed(Duration(seconds: 2));
      Get.back(); // close dialog
      Get.snackbar('Success', 'Task completed');
    } catch (e) {
      Get.back(); // close dialog on error
      Get.snackbar('Error', e.toString());
    }
  }
}

  1. Button Loading State

A common pattern is to disable a button and show a progress indicator inside it while loading. You can achieve this with a reactive boolean and a custom button widget.

DARTRead-only
1
class MyController extends GetxController {
  var isLoading = false.obs;

  Future<void> submit() async {
    isLoading.value = true;
    try {
      await Future.delayed(Duration(seconds: 2));
      Get.snackbar('Success', 'Submitted');
    } finally {
      isLoading.value = false;
    }
  }
}

// In UI
Obx(() => ElevatedButton(
  onPressed: controller.isLoading.value ? null : controller.submit,
  child: controller.isLoading.value
      ? SizedBox(
          width: 20,
          height: 20,
          child: CircularProgressIndicator(strokeWidth: 2),
        )
      : Text('Submit'),
));

  1. Using Workers for Loading Side Effects

You can use workers to react to loading state changes. For example, you could show a snackbar when loading finishes (success or error) or log the duration.

DARTRead-only
1
class MyController extends GetxController {
  var isLoading = false.obs;

  @override
  void onInit() {
    super.onInit();
    ever(isLoading, (loading) {
      if (!loading) {
        Get.snackbar('Info', 'Loading finished');
      }
    });
  }
}

  1. Skeleton Screens (Placeholder Loading)

For a more advanced loading experience, you can show skeleton screens while content loads. This can be combined with reactive loading flags: show skeleton when loading, actual content when loaded.

DARTRead-only
1
Obx(() => controller.isLoading.value
    ? SkeletonItem()
    : ContentWidget());

Best Practices

  • Always set loading to false in finally – Ensures the loading state is reset even if an error occurs.
  • Disable buttons while loading – Prevents multiple submissions.
  • Provide visual feedback – Users should know something is happening.
  • Use StateMixin for consistency – It standardises loading/error/success across your app.
  • Avoid showing loading indicators for very fast operations – A flickering indicator is worse than none. Consider a minimum duration.
  • Test loading states – Ensure the UI doesn't freeze and the loading indicator appears/disappears correctly.

Common Mistakes

  • ❌ Not resetting loading state on error – The UI stays stuck in loading. ✅ Use finally or catch blocks to set loading false.
  • ❌ Putting loading logic directly in UI – Mixing concerns. ✅ Keep loading state in the controller.
  • ❌ Allowing multiple submissions – Tapping the button again while loading starts another operation. ✅ Disable the button or use a debounce.
  • ❌ Using Obx with StateMixin incorrectly – obx already handles loading, no need for extra Obx.

FAQ

  • Q: How do I show a loading indicator for a specific part of the UI, not the whole screen?
    A: Use a local isLoading variable and wrap only that part with Obx. For example, show a CircularProgressIndicator inside a Card while that section loads.
  • Q: Can I use Get.dialog with a custom loading widget?
    A: Yes, pass any widget to Get.dialog. For a custom loader, you can use AlertDialog with a progress indicator or a full‑screen overlay.
  • Q: How to handle loading state for multiple independent operations?
    A: Create separate reactive booleans for each operation, or use a map of loading flags.
  • Q: How do I show a loading indicator with a text (e.g., 'Loading...')?
    A: Use Obx and return a Column with a CircularProgressIndicator and a Text widget.
  • Q: What's the difference between isLoading and StateMixin loading?
    A: StateMixin encapsulates loading, success, and error in one object, and provides .obx helper. A plain boolean gives you more flexibility but requires manual handling of error and success states.

Conclusion

Loading indicators are a fundamental part of user experience. With GetX, you can implement them cleanly and reactively. Whether you use a simple boolean, StateMixin, or a modal overlay, GetX gives you the tools to keep your UI responsive and your users informed.

Try it yourself

import 'package:flutter/material.dart';
import 'package:get/get.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return GetMaterialApp(
      home: LoadingDemo(),
    );
  }
}

class LoadingController extends GetxController {
  var isLoading = false.obs;
  var data = ''.obs;

  Future<void> loadData() async {
    isLoading.value = true;
    data.value = '';
    try {
      await Future.delayed(Duration(seconds: 2));
      data.value = 'Data loaded successfully!';
    } finally {
      isLoading.value = false;
    }
  }

  Future<void> loadWithOverlay() async {
    Get.dialog(
      Center(child: CircularProgressIndicator()),
      barrierDismissible: false,
    );
    await Future.delayed(Duration(seconds: 2));
    Get.back();
    Get.snackbar('Done', 'Operation completed');
  }
}

class LoadingDemo extends StatelessWidget {
  final controller = Get.put(LoadingController());

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Loading Indicators')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Obx(() => controller.isLoading.value
                ? CircularProgressIndicator()
                : Text(controller.data.value, style: TextStyle(fontSize: 20))),
            SizedBox(height: 20),
            Obx(() => ElevatedButton(
              onPressed: controller.isLoading.value ? null : controller.loadData,
              child: controller.isLoading.value
                  ? SizedBox(
                      width: 20,
                      height: 20,
                      child: CircularProgressIndicator(strokeWidth: 2),
                    )
                  : Text('Load Data'),
            )),
            SizedBox(height: 10),
            ElevatedButton(
              onPressed: controller.loadWithOverlay,
              child: Text('Load with Overlay'),
            ),
          ],
        ),
      ),
    );
  }
}

Test Your Knowledge

Q1
of 3

What is the simplest way to show a loading indicator in GetX?

A
Use a StatefulWidget
B
Use a reactive boolean and Obx
C
Use setState
D
Use a FutureBuilder
Q2
of 3

How do you ensure the loading state is reset even if an error occurs?

A
Set isLoading false in catch block
B
Set isLoading false in finally block
C
It's automatic
D
Use StateMixin only
Q3
of 3

Which GetX feature provides built‑in loading, success, and error states?

A
GetxController
B
Obx
C
StateMixin
D
GetBuilder

Previous

getx error handling

Next

getx refresh indicator

Related Content

Need help?

Explore our comprehensive docs or start a chat with our tech experts.