ReactiveController
classCore controller class for managing reactive state in your Flutter applications
Signature
class ReactiveController extends ChangeNotifier {
ReactiveController();
@protected
T state<T>(T initialValue);
@protected
void setState(VoidCallback fn);
@override
void dispose();
}Examples
ReactiveController
The ReactiveController class is the foundation of reactiv's reactive state management system. It provides a simple and intuitive API for managing application state with automatic dependency tracking and efficient re-rendering.
Overview
ReactiveController extends Flutter's ChangeNotifier and provides reactive state management capabilities. When you modify reactive state within a controller, all dependent widgets automatically rebuild.
Signature
class ReactiveController extends ChangeNotifier {
ReactiveController();
@protected
T state<T>(T initialValue);
@protected
void setState(VoidCallback fn);
@override
void dispose();
}
Constructor
ReactiveController()
Creates a new instance of ReactiveController.
Parameters: None
Example:
class TodoController extends ReactiveController {
TodoController() {
_todos = state<List<Todo>>([]);
_filter = state<TodoFilter>(TodoFilter.all);
}
late final Reactive<List<Todo>> _todos;
late final Reactive<TodoFilter> _filter;
}
Methods
state<T>(T initialValue)
Creates a reactive state variable with the given initial value. The state is automatically tracked, and widgets that read this state will rebuild when it changes.
Type Parameters:
T- The type of the state value
Parameters:
initialValue(T, required) - The initial value for the state
Returns: Reactive<T> - A reactive wrapper around the state value
Example:
class CounterController extends ReactiveController {
CounterController() {
_count = state<int>(0);
}
late final Reactive<int> _count;
int get count => _count.value;
void increment() {
_count.value++;
}
}
See Also:
setState(VoidCallback fn)
Executes the given callback and notifies listeners of state changes. This is useful when you need to perform multiple state updates as a single transaction.
Parameters:
fn(VoidCallback, required) - Callback function containing state updates
Returns: void
Example:
void addMultipleTodos(List<String> titles) {
setState(() {
for (final title in titles) {
_todos.value = [..._todos.value, Todo(title: title)];
}
});
}
Note: While setState is available, in most cases you can simply update reactive state directly and changes will be automatically tracked.
dispose()
Disposes of the controller and cleans up resources. This method is automatically called when the controller is no longer needed.
Returns: void
Example:
@override
void dispose() {
// Clean up any subscriptions or resources
_subscription?.cancel();
super.dispose();
}
Important: Always call super.dispose() when overriding this method.
Properties
value
Access the current value of a reactive state. Setting this property automatically triggers a rebuild of dependent widgets.
Type: T (generic type of the state)
Example:
// Reading state
final currentCount = _count.value;
// Updating state
_count.value = currentCount + 1;
// Or directly
_count.value++;
Usage Examples
Basic Counter
class CounterController extends ReactiveController {
CounterController() {
_count = state<int>(0);
}
late final Reactive<int> _count;
int get count => _count.value;
void increment() => _count.value++;
void decrement() => _count.value--;
void reset() => _count.value = 0;
}
// In your widget
class CounterView extends StatelessWidget {
@override
Widget build(BuildContext context) {
final controller = use.controller(() => CounterController());
return Scaffold(
body: Center(
child: Text('Count: ${controller.count}'),
),
floatingActionButton: FloatingActionButton(
onPressed: controller.increment,
child: Icon(Icons.add),
),
);
}
}
Todo List with Filtering
enum TodoFilter { all, active, completed }
class TodoController extends ReactiveController {
TodoController() {
_todos = state<List<Todo>>([]);
_filter = state<TodoFilter>(TodoFilter.all);
}
late final Reactive<List<Todo>> _todos;
late final Reactive<TodoFilter> _filter;
List<Todo> get filteredTodos {
switch (_filter.value) {
case TodoFilter.all:
return _todos.value;
case TodoFilter.active:
return _todos.value.where((t) => !t.completed).toList();
case TodoFilter.completed:
return _todos.value.where((t) => t.completed).toList();
}
}
void addTodo(String title) {
_todos.value = [
..._todos.value,
Todo(
id: DateTime.now().toString(),
title: title,
completed: false,
),
];
}
void toggleTodo(String id) {
_todos.value = _todos.value.map((todo) {
return todo.id == id
? todo.copyWith(completed: !todo.completed)
: todo;
}).toList();
}
void setFilter(TodoFilter filter) {
_filter.value = filter;
}
}
Async Data Fetching
class UserController extends ReactiveController {
UserController(this._api) {
_user = state<User?>(null);
_loading = state<bool>(false);
_error = state<String?>(null);
}
final ApiService _api;
late final Reactive<User?> _user;
late final Reactive<bool> _loading;
late final Reactive<String?> _error;
User? get user => _user.value;
bool get loading => _loading.value;
String? get error => _error.value;
Future<void> fetchUser(String id) async {
_loading.value = true;
_error.value = null;
try {
final user = await _api.getUser(id);
_user.value = user;
} catch (e) {
_error.value = e.toString();
} finally {
_loading.value = false;
}
}
@override
void dispose() {
// Clean up if needed
super.dispose();
}
}
Best Practices
-
Initialization: Always initialize reactive state in the constructor using
state<T>(initialValue) -
Immutability: When updating complex objects like lists or maps, create new instances instead of mutating:
// Good _todos.value = [..._todos.value, newTodo]; // Bad _todos.value.add(newTodo); // Won't trigger updates! -
Computed Values: Use getters for derived state:
List<Todo> get completedTodos => _todos.value.where((t) => t.completed).toList(); -
Cleanup: Override
dispose()to clean up subscriptions and resources -
Single Responsibility: Keep controllers focused on a single feature or domain
Performance Considerations
- Reactive state automatically tracks dependencies and only rebuilds affected widgets
- Batch multiple updates together when possible to minimize rebuilds
- Use
constwidgets where possible to prevent unnecessary rebuilds - Consider breaking large controllers into smaller, focused ones
Common Pitfalls
-
Forgetting to use
.value: Always access reactive state through the.valueproperty// Wrong final count = _count; // Correct final count = _count.value; -
Mutating collections: Create new instances instead of mutating
-
Not calling super.dispose(): Always call the parent's dispose method
Related APIs
- Reactive - The reactive wrapper type
- use.controller - Hook for creating controllers
- ReactiveBuilder - Widget for reactive state
See Also
Was this page helpful?
Help us improve our documentation by providing feedback.