flutter
/

GetX Reactive State: .obs, Obx, Workers & Advanced Patterns

Last Sync: Today

On this page

14
0%
5 min read
Remaining
5 minleft

Click any section to jump — progress syncs automatically

flutter

GetX Reactive State: .obs, Obx, Workers & Advanced Patterns

What is Reactive State in GetX?

Reactive state is the core of GetX's powerful state management system. It uses observable variables (marked with .obs) that automatically notify the UI when they change. This eliminates the need for manual setState calls and reduces boilerplate code. Reactive state is ideal for real-time updates, form inputs, and any scenario where you want the UI to react to changes instantly.

  1. Reactive Variables with .obs

To make any variable reactive, simply append .obs to it. GetX provides .obs extensions for primitive types, collections, and custom types.

DARTRead-only
1
class MyController extends GetxController {
  // Primitive types
  var count = 0.obs;
  var name = 'John'.obs;
  var isLogged = false.obs;

  // Collections
  var items = <String>[].obs;
  var userMap = <String, dynamic>{}.obs;

  // Custom types
  var user = User(name: 'Alice', age: 30).obs;
}

  1. Rx Types

GetX provides dedicated Rx types for better performance and IDE support. You can also use the generic Rx<T> type.

DARTRead-only
1
// Rx types
RxInt count = 0.obs;
RxString name = 'John'.obs;
RxBool isLogged = false.obs;
RxList<String> items = <String>[].obs;
RxMap<String, dynamic> userMap = <String, dynamic>{}.obs;

// Generic Rx
Rx<User> user = User(name: 'Alice', age: 30).obs;

  1. The Obx Widget

Obx is a widget that listens to changes in reactive variables and automatically rebuilds its child when any observed variable changes. You only need to wrap the parts of the UI that depend on the reactive data.

DARTRead-only
1
class HomeView extends StatelessWidget {
  final controller = Get.put(MyController());

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Obx(() => Text('Count: ${controller.count}')),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => controller.count++,
        child: Icon(Icons.add),
      ),
    );
  }
}

  1. Updating Reactive Values

There are several ways to update reactive values, depending on the type.

DARTRead-only
1
// For primitives: direct assignment
controller.count.value = 10;
// or just assign (if using .obs on var)
controller.count = 10;

// For collections: use methods
controller.items.add('new item');
controller.items.removeAt(0);
controller.items.assignAll(['a', 'b']);

// For custom types: use update or assign
controller.user.update((user) {
  user?.name = 'Bob';
});
// or assign a new instance
controller.user.value = User(name: 'Bob', age: 31);

  1. Workers – Reacting to Changes

Workers let you execute code when reactive variables change, without needing an Obx widget. They are perfect for side effects like logging, API calls, or navigation. You typically set them up inside onInit() of a controller.

DARTRead-only
1
class MyController extends GetxController {
  var count = 0.obs;
  var name = ''.obs;

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

    // ever: triggers on every change
    ever(count, (_) => print('Count changed to $count'));

    // once: triggers only the first time
    once(count, (_) => print('Count changed for first time'));

    // debounce: waits for 1 second of inactivity
    debounce(name, (_) => print('User stopped typing'), time: Duration(seconds: 1));

    // interval: triggers every second while changing
    interval(count, (_) => print('Count changed every second'), time: Duration(seconds: 1));
  }
}

  1. Advanced Rx Patterns

6.1 refresh() – Force Update

Sometimes you modify an object's properties directly and need to notify the UI. Use refresh() to force an update.

DARTRead-only
1
var user = User(name: 'Alice', age: 30).obs;

void changeName(String newName) {
  user.value.name = newName;
  user.refresh(); // Manually trigger UI update
}

6.2 assign and assignAll

For collections, assign replaces the entire list, while assignAll replaces with an iterable. Both trigger UI updates.

DARTRead-only
1
var items = <String>[].obs;

items.assign(['a', 'b']); // replaces entire list
items.assignAll(['c', 'd']); // also replaces

6.3 Nested Objects

When dealing with nested objects, you need to ensure reactivity propagates. The simplest way is to update the whole object or use refresh().

DARTRead-only
1
class Address {
  String street;
  Address(this.street);
}

class User {
  String name;
  Address address;
  User(this.name, this.address);
}

var user = User('Alice', Address('Main St')).obs;

void updateStreet(String newStreet) {
  user.update((user) => user?.address.street = newStreet);
  user.refresh(); // necessary because we modified a nested property
}

  1. Performance Optimization Tips

  • Granular Obx: Wrap only the parts that change, not the whole screen. This avoids unnecessary rebuilds.
  • Avoid heavy computations inside Obx: Keep the builder simple; move heavy logic to controllers or use computed values.
  • Use GetX widget for more control: The GetX widget provides a builder that gives you the controller instance and allows fine-grained rebuilds.
  • Use GetBuilder when you need manual control: For performance-critical sections where you want to call update() explicitly.

  1. Common Mistakes

  • ❌ Using .obs inside build method – Creates new reactive variables on every rebuild. ✅ Declare reactive variables inside the controller only.
  • ❌ Not using Obx – The UI won't update because it doesn't listen to changes. ✅ Always wrap dependent widgets with Obx or GetX.
  • ❌ Modifying nested objects without refreshing – UI doesn't react because the reference didn't change. ✅ Use refresh() or update the entire object.
  • ❌ Creating controllers inside build – Causes duplicate controllers and memory leaks. ✅ Use Get.put outside build or use bindings.

FAQ

  • Q: What's the difference between .obs and Rx<T>?
    A: .obs is an extension that returns an Rx<T> instance. Both are equivalent; .obs is just syntactic sugar for brevity.
  • Q: Can I use reactive variables without a controller?
    A: Yes, but it's not recommended. Controllers provide lifecycle management and a natural place to organize logic.
  • Q: Does GetX support ValueListenable?
    A: Yes, you can use ValueListenable with GetX, but it's not needed. GetX's reactive system is built on Rx and works seamlessly.
  • Q: How do I dispose workers?
    A: Workers are automatically disposed when the controller is disposed. You don't need to manually remove them.
  • Q: Can I use Obx with multiple reactive variables?
    A: Yes, Obx listens to any reactive variable accessed inside its builder. If any of them change, the widget rebuilds.
  • Q: What's the performance impact of using many Obx widgets?
    A: GetX's reactivity is highly optimized. Using many granular Obx widgets is actually better than one large Obx because it limits rebuilds to the smallest necessary areas.
  • Q: How do I combine reactive state with API calls?
    A: Use a Rx variable for loading state, data, and error. Set them asynchronously and the UI will react automatically. Consider using StateMixin for even cleaner patterns.

Conclusion

GetX reactive state management is intuitive, powerful, and highly performant. By leveraging .obs, Obx, and workers, you can build responsive Flutter apps with minimal code. Combine these techniques with dependency injection and bindings for a complete, scalable architecture.

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: ReactiveDemo(),
    );
  }
}

class ReactiveController extends GetxController {
  // Reactive variables
  var count = 0.obs;
  var name = ''.obs;
  var items = <String>[].obs;

  @override
  void onInit() {
    super.onInit();
    // Worker: ever
    ever(count, (_) => print('Count changed to $count'));
    // Worker: debounce
    debounce(name, (_) => print('User stopped typing: $name'), time: Duration(seconds: 1));
  }

  void increment() => count++;
  void decrement() => count--;
  void updateName(String newName) => name.value = newName;
  void addItem() => items.add('Item ${items.length + 1}');
  void removeItem(int index) => items.removeAt(index);
}

class ReactiveDemo extends StatelessWidget {
  final controller = Get.put(ReactiveController());

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Reactive State Demo')),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          children: [
            // Counter section
            Card(
              child: Padding(
                padding: const EdgeInsets.all(12.0),
                child: Column(
                  children: [
                    Text('Counter', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
                    Obx(() => Text(
                      '${controller.count}',
                      style: TextStyle(fontSize: 32),
                    )),
                    Row(
                      mainAxisAlignment: MainAxisAlignment.center,
                      children: [
                        ElevatedButton(onPressed: controller.decrement, child: Text('-')),
                        SizedBox(width: 10),
                        ElevatedButton(onPressed: controller.increment, child: Text('+')),
                      ],
                    ),
                  ],
                ),
              ),
            ),
            SizedBox(height: 16),
            // Text input with debounce
            Card(
              child: Padding(
                padding: const EdgeInsets.all(12.0),
                child: Column(
                  children: [
                    Text('Debounced Input', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
                    TextField(
                      onChanged: controller.updateName,
                      decoration: InputDecoration(labelText: 'Type something...'),
                    ),
                    Obx(() => Text('You typed: ${controller.name}')),
                  ],
                ),
              ),
            ),
            SizedBox(height: 16),
            // List section
            Card(
              child: Padding(
                padding: const EdgeInsets.all(12.0),
                child: Column(
                  children: [
                    Text('Dynamic List', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
                    ElevatedButton(onPressed: controller.addItem, child: Text('Add Item')),
                    SizedBox(height: 8),
                    Obx(() => Expanded(
                      child: ListView.builder(
                        shrinkWrap: true,
                        itemCount: controller.items.length,
                        itemBuilder: (_, index) => ListTile(
                          title: Text(controller.items[index]),
                          trailing: IconButton(
                            icon: Icon(Icons.delete),
                            onPressed: () => controller.removeItem(index),
                          ),
                        ),
                      ),
                    )),
                  ],
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

Test Your Knowledge

Q1
of 4

How do you make a variable reactive in GetX?

A
Use `GetX` widget
B
Append `.obs` to the variable
C
Use `StatefulWidget`
D
Call `update()`
Q2
of 4

Which widget listens to reactive changes and rebuilds automatically?

A
Obx
B
GetBuilder
C
StatefulBuilder
D
ReactiveWidget
Q3
of 4

What worker method is used to react to changes after a pause in activity?

A
ever
B
once
C
debounce
D
interval
Q4
of 4

When updating a nested object property, what must you call to notify the UI?

A
update()
B
setState()
C
refresh()
D
rebuild()

Previous

getx state management

Next

getx simple state

Related Content

Need help?

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