flutter
/

GetX Custom Rx Models: Reactive Objects & Nested Properties

Last Sync: Today

On this page

12
0%
5 min read
Remaining
5 minleft

Click any section to jump — progress syncs automatically

flutter

GetX Custom Rx Models: Reactive Objects & Nested Properties

Introduction

In GetX, you can make any class reactive by wrapping it with Rx<T> or using the .obs extension. This is essential when you need to react to changes in complex data structures. This guide covers how to create reactive custom models, update nested properties, and handle lists of custom objects effectively.

  1. Making a Custom Class Reactive

To make a custom class reactive, you have two options: use the .obs extension or the Rx<T> constructor. Both create an observable variable that notifies listeners when its value changes.

DARTRead-only
1
class User {
  String name;
  int age;
  User(this.name, this.age);
}

class MyController extends GetxController {
  // Option 1: Using .obs
  var user = User('Alice', 30).obs;

  // Option 2: Using Rx<T>
  late Rx<User> user2;

  @override
  void onInit() {
    super.onInit();
    user2 = Rx<User>(User('Bob', 25));
  }
}

  1. Accessing and Modifying the Object

To read or update the entire object, use the .value property. Assigning a new object to .value triggers a UI update.

DARTRead-only
1
// Reading
User currentUser = controller.user.value;

// Updating (replaces the whole object)
controller.user.value = User('Charlie', 35);

  1. Updating Nested Properties

If you modify a property of the object directly, the UI will not update because the object reference hasn't changed. To fix this, you have two options: use the update method or call refresh() after modification.

DARTRead-only
1
// ❌ Does NOT trigger UI update
controller.user.value.name = 'David';

// ✅ Using update() – recommended
controller.user.update((user) {
  user?.name = 'David';
});

// ✅ Using refresh()
controller.user.value.name = 'David';
controller.user.refresh();

  1. The update Method Explained

The update method accepts a callback where you can modify the object. It automatically triggers a UI update after the callback finishes. This is the cleanest way to modify nested properties.

DARTRead-only
1
controller.user.update((user) {
  user?.name = 'Eve';
  user?.age = 28;
}); // UI updates once

Comparison: update() vs refresh() vs replace

MethodSyntaxWhen to UseUI Update
`update()``rx.update((obj) => obj?.prop = val)`Modifying nested propertiesAutomatic after callback
`refresh()``rx.refresh()`After manual modifications or when object reference unchangedManual trigger
Replace object`rx.value = newObject`When you have a new instance (immutable models)Automatic on assignment

  1. Reactive Lists of Custom Objects

Use RxList<T> to create a reactive list of custom objects. You can add, remove, or modify items, and the UI will update automatically when the list structure changes. However, modifying a property of an item inside the list still requires a refresh.

DARTRead-only
1
class MyController extends GetxController {
  var users = <User>[].obs;

  void addUser(String name, int age) {
    users.add(User(name, age)); // UI updates
  }

  void removeUser(int index) {
    users.removeAt(index); // UI updates
  }

  void updateUserName(int index, String newName) {
    // Modifying a property of an existing item: need to refresh
    users[index].name = newName;
    users.refresh(); // or users[index] = users[index] (replace) also works
  }

  // Alternative: replace the item
  void updateUser(int index, User newUser) {
    users[index] = newUser; // UI updates because the reference changed
  }
}

  1. Using Workers with Custom Models

You can use workers to react to changes in the whole object or specific properties. Since workers only track the object reference, they will not fire when a nested property changes unless you call refresh() or update().

DARTRead-only
1
class MyController extends GetxController {
  var user = User('Alice', 30).obs;

  @override
  void onInit() {
    super.onInit();
    ever(user, (user) {
      print('User changed: ${user?.name}');
    });
  }

  void updateName(String newName) {
    user.update((u) => u?.name = newName); // triggers worker
  }
}

  1. Immutability vs Mutability

To avoid having to call refresh() on nested changes, you can make your model immutable and always replace the entire object when something changes. This is often easier and less error‑prone.

DARTRead-only
1
class User {
  final String name;
  final int age;
  const User(this.name, this.age);

  User copyWith({String? name, int? age}) {
    return User(name ?? this.name, age ?? this.age);
  }
}

// Updating
controller.user.value = controller.user.value.copyWith(name: 'Bob');
// Or using update
controller.user.update((u) => u = u?.copyWith(name: 'Bob'));

Best Practices

  • Use immutable models when possible – Replace the whole object to avoid manual refresh() calls.
  • Use update for nested modifications – It’s concise and ensures the UI updates.
  • Avoid storing large objects as reactive – If the object is large and changes frequently, consider splitting it into smaller reactive parts.
  • Use refresh() sparingly – It forces a rebuild even if no change occurred; prefer update or value assignment.
  • Keep your models simple – Business logic should stay in controllers, not in models.

Common Mistakes

  • ❌ Modifying a property without update or refresh – UI does not update. ✅ Use update or refresh() after modification.
  • ❌ Using .value repeatedly inside Obx – Can cause unnecessary rebuilds if not needed. ✅ Use local variables or computed getters.
  • ❌ Forgetting that workers only react to reference changes – Nested changes won’t trigger workers. ✅ Call refresh() or use update.
  • ❌ Not initializing Rx variables – Trying to use null Rx can cause errors. ✅ Always initialize with a value.

Conclusion

Making custom models reactive in GetX is straightforward. By understanding how to update nested properties, when to use update, and how to work with reactive lists, you can build reactive UIs that react to changes in complex data structures. Adopting immutable models can simplify the pattern further and reduce the risk of missing updates.

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

class User {
  String name;
  int age;
  User(this.name, this.age);

  // Immutable copyWith method (optional)
  User copyWith({String? name, int? age}) {
    return User(name ?? this.name, age ?? this.age);
  }
}

class UserController extends GetxController {
  var user = User('Alice', 30).obs;

  void updateName(String newName) {
    // Using update – triggers UI update
    user.update((u) => u?.name = newName);
  }

  void incrementAge() {
    // Using copyWith (immutable approach)
    user.value = user.value.copyWith(age: user.value.age + 1);
  }

  void resetUser() {
    user.value = User('Alice', 30);
  }
}

class CustomRxDemo extends StatelessWidget {
  final controller = Get.put(UserController());
  final TextEditingController textController = TextEditingController();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Custom Rx Model')),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Obx(() => Text(
              'Name: ${controller.user.value.name}\nAge: ${controller.user.value.age}',
              style: TextStyle(fontSize: 24),
              textAlign: TextAlign.center,
            )),
            SizedBox(height: 20),
            TextField(
              controller: textController,
              decoration: InputDecoration(labelText: 'New name'),
            ),
            SizedBox(height: 10),
            ElevatedButton(
              onPressed: () {
                if (textController.text.isNotEmpty) {
                  controller.updateName(textController.text);
                  textController.clear();
                }
              },
              child: Text('Update Name'),
            ),
            ElevatedButton(
              onPressed: controller.incrementAge,
              child: Text('Increment Age'),
            ),
            ElevatedButton(
              onPressed: controller.resetUser,
              child: Text('Reset'),
            ),
          ],
        ),
      ),
    );
  }
}

Test Your Knowledge

Q1
of 3

What happens if you modify a property of a custom object inside an Rx variable without calling update or refresh?

A
The UI updates automatically
B
The UI does not update
C
An error is thrown
D
The object becomes immutable
Q2
of 3

Which method is recommended for modifying nested properties of an Rx object?

A
refresh()
B
update()
C
assign()
D
set()
Q3
of 3

How can you make a list of custom objects reactive?

A
Use RxList<T> or .obs on a List<T>
B
It's not possible
C
Use only primitive lists
D
Use GetBuilder

Frequently Asked Questions

When should I use `update` vs `refresh`?

Use update when you want to modify the object and automatically notify listeners. Use refresh after manual modifications to the object’s properties.

Can I use `Rx` with freezed classes?

Yes, freezed classes work perfectly with Rx because they are immutable. Simply assign a new instance to .value.

How do I make a reactive list of custom objects that each have their own reactive properties?

You can make each item itself reactive (e.g., Rx<User> inside a RxList<Rx<User>>). But that’s complex; usually it’s easier to refresh the whole list when an item changes.

What’s the difference between `RxList` and `List` with `.obs`?

They are the same: var list = <User>[].obs creates an RxList<User>.

How to listen to changes in a specific property of a custom model?

You can use workers on the whole object, or split the property into its own Rx variable inside the controller. If you need fine-grained reactivity, consider using computed values.

Previous

getx cli tools

Next

getx rx workers advanced

Related Content

Need help?

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