flutter
/

GetX Dynamic UI Rendering: Reactive, Conditional & Animated Widgets

Last Sync: Today

On this page

17
0%
5 min read
Remaining
5 minleft

Click any section to jump — progress syncs automatically

flutter

GetX Dynamic UI Rendering: Reactive, Conditional & Animated Widgets

Introduction

Dynamic UI rendering is the ability to change what the user sees based on application state. With GetX, you can build highly interactive UIs where widgets appear, disappear, or change their content reactively – all with minimal code. This guide covers the most common patterns: conditional visibility, reactive lists, state‑driven widget switching, and combining animations with GetX's reactive state.

Real-World Use Cases

  • Form Builders (like Revochamp) – Show/hide fields based on user selection
  • E-commerce Apps – Update cart items dynamically without reload
  • Dashboards – Live data updates with charts and KPIs
  • Authentication Flow – Switch between login, loading, and home screens
  • API-driven UI – Render UI dynamically from backend JSON

How Dynamic UI Works in GetX

Reactive Variable (Rx) → Observed by Obx → UI rebuilds automatically when value changes. No setState required. This unidirectional flow keeps your code predictable and easy to debug.

Recommended Architecture for Dynamic UI

For scalable applications, keep dynamic UI logic inside controllers and expose computed values (getters). Avoid placing business logic inside widgets. A clean approach is: Controller → Reactive Variables → Computed Getters → UI (Obx).

  1. Conditional Visibility with Obx

The simplest form of dynamic UI is showing or hiding widgets based on a boolean reactive variable. Use Obx to wrap the widget you want to show/hide, and the UI will update automatically when the boolean changes.

DARTRead-only
1
class MyController extends GetxController {
  var isVisible = true.obs;

  void toggle() => isVisible.toggle();
}

// In UI
Obx(() => controller.isVisible.value
    ? Text('Visible')
    : SizedBox.shrink()  // or an empty container
);

// Or using Offstage
Obx(() => Offstage(
  offstage: !controller.isVisible.value,
  child: Text('Visible'),
));

  1. Dynamic Lists

Reactive lists (RxList) automatically update the UI when items are added, removed, or modified. Use Obx with a ListView.builder to render dynamic collections efficiently.

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

  void addTodo(String text) => todos.add(text);
  void removeTodo(int index) => todos.removeAt(index);
}

// UI
Obx(() => ListView.builder(
  itemCount: controller.todos.length,
  itemBuilder: (_, i) => ListTile(
    title: Text(controller.todos[i]),
    trailing: IconButton(
      icon: Icon(Icons.delete),
      onPressed: () => controller.removeTodo(i),
    ),
  ),
));

  1. State‑Driven Widget Switching

Often you need to display different widgets based on loading, error, or success states. You can handle this with Obx and conditionals, or use the built‑in StateMixin for a cleaner pattern.

DARTRead-only
1
class DataController extends GetxController with StateMixin<String> {
  @override
  void onInit() {
    super.onInit();
    fetchData();
  }

  Future<void> fetchData() async {
    change(null, status: RxStatus.loading());
    try {
      await Future.delayed(Duration(seconds: 2));
      change('Hello, World!', 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'),
  onEmpty: Text('No data'),
);

  1. Reactive Form Fields

Form fields can be made dynamic by binding their value to a reactive variable. The UI will update as the user types or as you programmatically change the variable.

DARTRead-only
1
class FormController extends GetxController {
  var name = ''.obs;
  var greeting = ''.obs;

  void updateGreeting() {
    greeting.value = 'Hello, ${name.value}';
  }
}

// UI
TextField(
  onChanged: (value) => controller.name.value = value,
  decoration: InputDecoration(labelText: 'Name'),
),
Obx(() => Text(controller.greeting.value)),
ElevatedButton(
  onPressed: controller.updateGreeting,
  child: Text('Say Hello'),
)

  1. Animated UI with GetX

GetX works seamlessly with Flutter's built‑in animations. You can trigger animations by changing a reactive variable and using AnimatedBuilder or implicit animations like AnimatedContainer.

DARTRead-only
1
class AnimatedController extends GetxController {
  var isExpanded = false.obs;
  void toggle() => isExpanded.toggle();
}

// UI
Obx(() => AnimatedContainer(
  duration: Duration(milliseconds: 300),
  height: controller.isExpanded.value ? 200 : 100,
  width: controller.isExpanded.value ? 200 : 100,
  color: Colors.blue,
  child: Center(child: Text('Click me')),
));

ElevatedButton(
  onPressed: controller.toggle,
  child: Text('Animate'),
);

  1. Conditional Widgets Based on Multiple States

When you have more than two possible states, you can use switch or if-else inside the Obx builder. For complex logic, consider computing a widget in a getter.

DARTRead-only
1
class ThemeController extends GetxController {
  var theme = 'light'.obs;

  Widget get themeWidget {
    switch(theme.value) {
      case 'light': return Icon(Icons.wb_sunny, color: Colors.yellow);
      case 'dark': return Icon(Icons.nightlight_round, color: Colors.grey);
      default: return Icon(Icons.device_unknown);
    }
  }
}

// UI
Obx(() => controller.themeWidget);
ElevatedButton(
  onPressed: () => controller.theme.value = 'dark',
  child: Text('Dark'),
);

  1. Computed UI (Derived State)

Instead of writing conditions inside the UI, compute derived values inside the controller. This keeps UI clean and improves performance.

DARTRead-only
1
class CartController extends GetxController {
  var items = <int>[].obs;

  bool get isCartEmpty => items.isEmpty;
}

// UI
Obx(() => controller.isCartEmpty
    ? Text('Cart is empty')
    : Text('Items available'));

  1. Performance Tips

  • Keep Obx granular – Wrap only the part that changes, not the whole screen.
  • Use assignAll for lists – Replacing an entire list with list.value = newList may not trigger UI updates correctly; use assignAll instead.
  • Avoid heavy computations inside Obx – Move logic to getters or computed values.
  • Use GetX widget for complex builders – It isolates rebuilds and can accept a controller instance.
  • Don't use Obx around expensive widgets that rarely change – Use GetBuilder with manual update() for better control.

When NOT to Use Obx

  • Static UI that never changes – No reactivity needed, just build once.
  • Heavy widgets like large lists without need for reactivity – Overhead of tracking may outweigh benefits.
  • Complex layouts where manual control (GetBuilder) is better – For precise rebuild control.

Best Practices

  • Keep state in controllers – UI should only react to state, not manage it.
  • Use StateMixin for loading/error/success – Reduces boilerplate.
  • Test dynamic UI changes – Simulate state changes and verify that the correct widgets appear.
  • Provide feedback for empty states – Show a message when a list is empty.
  • Use Obx sparingly on large trees – Granularity improves performance.

Common Mistakes

  • ❌ Placing Obx too high – Rebuilds too much of the tree. ✅ Move Obx inside the widget where the reactive variable is used.
  • ❌ Not using assignAll for lists – UI may not update. ✅ Always use assignAll to replace list contents.
  • ❌ Mutating reactive variables inside Obx – Can cause infinite loops. ✅ Never modify reactive state inside the Obx builder; do it in callbacks.
  • ❌ Forgetting to wrap widgets with Obx – UI doesn't react to changes. ✅ Always wrap dynamic parts with Obx or GetX.

Next Steps

  • 👉 Learn GetX Reactive State for deeper understanding
  • 👉 Explore GetX Rebuild Optimization for performance tuning
  • 👉 Master GetX Computed State for advanced patterns

Conclusion

Dynamic UI rendering is at the heart of reactive Flutter apps. GetX makes it straightforward: use reactive variables, Obx, and StateMixin to build UIs that respond instantly to state changes. By following the patterns and performance tips in this guide, you can create fluid, interactive experiences with minimal code.

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

class DynamicController extends GetxController {
  var isVisible = true.obs;
  var items = <String>[].obs;
  var text = ''.obs;

  void toggle() => isVisible.toggle();
  void addItem() => items.add('Item ${items.length + 1}');
  void updateText(String value) => text.value = value;
}

class DynamicUIDemo extends StatelessWidget {
  final controller = Get.put(DynamicController());

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Dynamic UI Demo')),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          children: [
            // Conditional visibility
            Obx(() => controller.isVisible.value
                ? Card(child: Padding(
                    padding: const EdgeInsets.all(16.0),
                    child: Text('This text can be hidden'),
                  ))
                : const SizedBox.shrink()),
            ElevatedButton(
              onPressed: controller.toggle,
              child: Obx(() => Text(controller.isVisible.value ? 'Hide' : 'Show')),
            ),
            const Divider(),
            // Dynamic list
            Row(
              children: [
                Expanded(
                  child: TextField(
                    onChanged: controller.updateText,
                    decoration: const InputDecoration(labelText: 'Type something'),
                  ),
                ),
                IconButton(
                  icon: const Icon(Icons.add),
                  onPressed: () {
                    if (controller.text.value.isNotEmpty) {
                      controller.addItem();
                      controller.updateText('');
                    }
                  },
                ),
              ],
            ),
            Expanded(
              child: Obx(() => ListView.builder(
                itemCount: controller.items.length,
                itemBuilder: (_, i) => ListTile(
                  title: Text(controller.items[i]),
                ),
              )),
            ),
          ],
        ),
      ),
    );
  }
}

Test Your Knowledge

Q1
of 4

What widget should you use to make a part of the UI react to reactive variables?

A
Obx
B
GetBuilder
C
Both A and B
D
StatefulWidget
Q2
of 4

How do you replace the entire contents of an RxList?

A
list.value = newList
B
list.assignAll(newList)
C
list.clear() then list.addAll
D
Both B and C
Q3
of 4

What is the main benefit of keeping `Obx` as granular as possible?

A
Better performance (fewer rebuilds)
B
Easier to read code
C
Automatic error handling
D
It's required by GetX
Q4
of 4

Which is the best practice for handling complex UI logic in GetX?

A
Write logic inside Obx
B
Use controller getters (computed state)
C
Use setState
D
Use StatefulWidget

Frequently Asked Questions

How do I update only a part of a list item without rebuilding the whole list?

If each item has its own reactive state, you can wrap the specific widget inside the item with Obx. Or use GetBuilder with IDs.

Can I animate a widget when its reactive variable changes?

Yes, combine Obx with an AnimatedWidget or implicit animation widgets like AnimatedContainer, AnimatedOpacity, etc.

How do I conditionally show a different layout (e.g., login vs home) without rebuilding everything?

You can have a root Obx that switches between two Scaffold widgets based on isLoggedIn. The previous tree is replaced, but that's acceptable.

How do I prevent flickering when the list updates?

Use ListView.builder with a key if needed, but the default behavior should be smooth. Large updates may cause a slight rebuild; consider using animateItem if needed.

What's the difference between `Obx` and `GetBuilder` for dynamic UI?

Obx automatically rebuilds when any observed reactive variable changes. GetBuilder rebuilds only when you call update(). Use Obx for most dynamic UI; use GetBuilder for performance-critical areas where you want to control rebuilds manually.

Previous

getx network retry

Next

getx offline support

Related Content

Need help?

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