flutter
/

GetX Computed State: Derived Values & Reactive Calculations

Last Sync: Today

On this page

11
0%
5 min read
Remaining
5 minleft

Click any section to jump — progress syncs automatically

flutter

GetX Computed State: Derived Values & Reactive Calculations

What is Computed State?

Computed state refers to values that are derived from other reactive state. Instead of storing the computed value separately and manually updating it whenever its dependencies change, you can compute it on the fly. In GetX, you can use simple getters, Rx variables updated with workers, or the GetX widget to efficiently manage computed state.

Comparison of Approaches

ApproachBest ForPerformanceComplexity
Getter in ControllerSimple synchronous computationsRecomputed on every accessLow
Worker + Rx VariableSide effects or memoizationRecomputed only when dependencies changeMedium
GetX WidgetExpensive computations / scoped rebuildsRecomputed only when dependencies changeLow-Medium

  1. Using Getters in Controllers

The simplest way to create a computed value is to use a getter in your controller. The getter will be re-evaluated every time it is accessed, and if you use it inside Obx, it will be re-evaluated whenever any of its dependencies change.

DARTRead-only
1
class ProfileController extends GetxController {
  var firstName = 'John'.obs;
  var lastName = 'Doe'.obs;

  // Computed value using a getter
  String get fullName => '${firstName.value} ${lastName.value}';
}

// In UI
Obx(() => Text(controller.fullName)); // rebuilds when firstName or lastName changes

  1. Reactive Computed Variables with Workers

If you want the computed value to be reactive itself (so you can observe it as an Rx), you can use a worker to update a secondary reactive variable whenever its dependencies change.

DARTRead-only
1
class ProfileController extends GetxController {
  var firstName = 'John'.obs;
  var lastName = 'Doe'.obs;
  var fullName = ''.obs;

  @override
  void onInit() {
    super.onInit();
    everAll([firstName, lastName], (_) {
      fullName.value = '${firstName.value} ${lastName.value}';
    });
  }
}

// UI
Obx(() => Text(controller.fullName.value));

  1. Using the GetX Widget for Efficient Computations

The GetX widget is ideal for computed state because it allows you to recompute only when its observed variables change, without storing an extra reactive variable. It also supports returning a widget directly.

DARTRead-only
1
class ProfileController extends GetxController {
  var firstName = 'John'.obs;
  var lastName = 'Doe'.obs;
}

// In UI
GetX<ProfileController>(
  builder: (controller) {
    // This builder runs only when firstName or lastName changes
    final fullName = '${controller.firstName.value} ${controller.lastName.value}';
    return Text(fullName);
  },
);

  1. Computed Lists and Filtering

A common use case is filtering a reactive list based on a search query. You can compute the filtered list on the fly.

DARTRead-only
1
class TodoController extends GetxController {
  var todos = <String>[].obs;
  var filter = ''.obs;

  List<String> get filteredTodos {
    if (filter.value.isEmpty) return todos;
    return todos.where((todo) => todo.contains(filter.value)).toList();
  }
}

// UI
Obx(() => ListView.builder(
  itemCount: controller.filteredTodos.length,
  itemBuilder: (_, i) => Text(controller.filteredTodos[i]),
));

  1. Performance Considerations

When using getters for computed values, the computation runs every time the getter is accessed. If the computation is expensive, you may want to memoize the result. Using GetX widget with a builder ensures the computation only runs when dependencies change, not on every frame. For lists, consider using RxList and updating it only when needed (e.g., after filter changes) if the list is large.

DARTRead-only
1
// Expensive computation - only recompute when dependencies change
GetX<ExpensiveController>(
  builder: (ctrl) {
    final result = ctrl.expensiveComputation();
    return Text(result);
  },
);

  1. Computed State with StateMixin

You can combine computed state with StateMixin to handle loading/error states alongside derived data.

DARTRead-only
1
class DataController extends GetxController with StateMixin<List<int>> {
  var filter = ''.obs;

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

  Future<void> fetchData() async {
    change(null, status: RxStatus.loading());
    try {
      final data = await api.getNumbers();
      change(data, status: RxStatus.success());
    } catch (e) {
      change(null, status: RxStatus.error(e.toString()));
    }
  }

  List<int> get filteredData {
    final data = state;
    if (data == null || filter.value.isEmpty) return data ?? [];
    return data.where((n) => n.toString().contains(filter.value)).toList();
  }
}

// UI
controller.obx(
  (data) => Column(
    children: [
      TextField(onChanged: (v) => controller.filter.value = v),
      Expanded(
        child: Obx(() => ListView.builder(
          itemCount: controller.filteredData.length,
          itemBuilder: (_, i) => Text('${controller.filteredData[i]}'),
        )),
      ),
    ],
  ),
);

Best Practices

  • Use getters for simple, cheap computations – They are easy to read and maintain.
  • Use GetX widget for expensive computations – It recomputes only when dependencies change, avoiding unnecessary work.
  • Memoize expensive results – Cache computed values if they are reused and recomputed often.
  • Keep computed logic in the controller – Not in the UI, to maintain separation of concerns.
  • Avoid calling getters inside build without Obx – The value won't update when dependencies change.
  • Use everAll or debounce for complex derived state – Especially if you need to perform async work after a change.

Common Mistakes

  • ❌ Using a getter inside Obx but the getter accesses non‑reactive variables – The UI won't update. ✅ Ensure all dependencies are reactive (.obs).
  • ❌ Re‑computing expensive values on every rebuild – Causes performance issues. ✅ Use GetX widget to limit recomputation.
  • ❌ Storing computed state in an Rx variable that is manually updated – Leads to duplicate state and potential bugs. ✅ Use getters or workers to derive it automatically.
  • ❌ Not handling null cases in computed values – If the source data can be null, handle it gracefully.

Conclusion

Computed state is a powerful pattern in reactive programming. GetX offers multiple ways to implement it, from simple getters to reactive workers and the GetX widget. By choosing the right approach based on complexity and performance needs, you can keep your controllers clean and your UI responsive.

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

class TodoController extends GetxController {
  var todos = <String>[].obs;
  var filter = ''.obs;

  void addTodo(String text) {
    todos.add(text);
  }

  List<String> get filteredTodos {
    if (filter.value.isEmpty) return todos;
    return todos.where((todo) => todo.contains(filter.value)).toList();
  }
}

class ComputedDemo extends StatelessWidget {
  final controller = Get.put(TodoController());
  final TextEditingController textController = TextEditingController();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Computed State Demo')),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          children: [
            Row(
              children: [
                Expanded(
                  child: TextField(
                    controller: textController,
                    decoration: InputDecoration(labelText: 'New todo'),
                  ),
                ),
                IconButton(
                  icon: Icon(Icons.add),
                  onPressed: () {
                    if (textController.text.isNotEmpty) {
                      controller.addTodo(textController.text);
                      textController.clear();
                    }
                  },
                ),
              ],
            ),
            TextField(
              decoration: InputDecoration(labelText: 'Filter'),
              onChanged: (value) => controller.filter.value = value,
            ),
            SizedBox(height: 10),
            Expanded(
              child: Obx(() => ListView.builder(
                itemCount: controller.filteredTodos.length,
                itemBuilder: (_, i) => ListTile(
                  title: Text(controller.filteredTodos[i]),
                ),
              )),
            ),
          ],
        ),
      ),
    );
  }
}

Test Your Knowledge

Q1
of 3

How do you create a computed value that automatically updates when its dependencies change?

A
Use a getter that accesses reactive variables
B
Store the computed value in a separate Rx variable and update it manually
C
Use a Worker to update a reactive variable
D
All of the above
Q2
of 3

Which widget is best for expensive computations that should only run when dependencies change?

A
Obx
B
GetBuilder
C
GetX
D
GetView
Q3
of 3

What will happen if you use a getter inside `Obx` that accesses non‑reactive variables?

A
The UI will update when the getter returns a new value
B
The UI will never update because the dependencies are not tracked
C
An error will be thrown
D
The getter will be called only once

Frequently Asked Questions

When should I use a getter vs a worker + Rx variable?

Use a getter for simple synchronous computations. Use a worker + Rx when you need to perform side effects or when the computed value is expensive and you want to memoize it.

Does Obx work with getters?

Yes, as long as the getter accesses reactive variables, Obx will listen to them and rebuild when they change.

Can I combine multiple reactive sources in a computed value?

Yes, the getter can access any number of Rx variables; Obx will track all of them.

How to compute a value that depends on an async operation?

Use a worker to listen to the async result (e.g., after a future completes) and update a reactive variable. Or use StateMixin and compute based on the state.

What's the difference between GetX widget and Obx with a getter?

GetX widget rebuilds only the widget returned by the builder, and it can access the controller via a callback, which can be more efficient if the computation is inside the builder. Obx with a getter also works, but the getter is called on every frame where the widget is built (though still efficient if the computation is cheap).

Previous

getx refresh indicator

Next

getx state persistence

Related Content

Need help?

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