Skip to content

State management

State management is a critical aspect of any application, as it is responsible for controlling and maintaining data consistency over time. It facilitates updating and tracking changes in the state, ensuring a smooth user experience and stable application operation.

At Reactter, we understand the importance of state management, which is why we have designed a state management system that is efficient, reactive, and easy to use.

To continue, we will show you the mechanisms that Reactter offers for state management and how they work.

API

Reactter provides a variety of mechanisms for state management, including classes, hooks, and methods:

How it works

Reactter’s state management system is based on the concept of reactivity. Contrary to the prevailing notion that implementing reactive programming in Dart can be challenging, Reactter greatly simplifies this process. To dive into the concept, let’s start by exploring what constitutes a state in Reactter.

State

All states in Reactter are classes that inherit RtState , which encapsulates the data and behavior of a particular state, and provides a way to notify observers when the state changes.

State methods

RtState class provides some methods for managing states. Let’s know about them:

  • update : Executes a callback function and notify its observers that the state has changed. When it is invoked, it emits two lifecycle events to signal the state transition:
    • Lifecycle.willUpdate is emitted first, indicating the impending update.
    • Lifecycle.didUpdate is emitted once the update process is complete.
  • notify : Forces the state to notify its observers. Unlike update , it emits only the Lifecycle.didUpdate event, as it doesn’t involve any preparatory steps before the notification.
  • bind : Establishes a connection between the state and a specific instance. This connection allows the instance to reactively update based on changes to the state. By binding the state, the instance becomes aware of changes to the state and can appropriately reflect those changes in its behavior.
  • unbind : Releases the connection between the state and the instance. When unbinding, the instance will no longer receive updates from the state. This can be useful when an instance is no longer actively using the state or when it needs to detach from the state temporarily or permanently.
  • dispose : Is responsible for cleaning up the state and any associated observers or resources. Disposing of the state ensures that it is properly released and no longer consumes memory or processing resources unnecessarily.

Example

Let’s see an countdown example using Signal and understand what happens under the hood.

main.dart
1
import 'dart:async';
2
import 'package:reactter/reactter.dart';
3
4
// Create a reactive state called `count` using the `Signal` class
5
final count = Signal(10);
6
7
void main() async {
8
// Liste n to the `didUpdate` event of the `count` state
9
// and print the `value` of `count` each time it changes
10
Rt.on(
11
count,
12
Lifecycle.didUpdate,
13
(_, __) => print('Count: $count')
14
);
15
16
// Create a timer that invoked the `countdown` function every second
17
await Timer.periodic(Duration(seconds: 1), countdown);
18
}
19
20
// Decrement the `value` of `count` by 1 each time the timer ticks
21
// and cancel the `timer` when the `value` of `count` reaches 0
22
void countdown(Timer timer) {
23
count.value -= 1;
24
25
if (count.value == 0) {
26
timer.cancel();
27
}
28
}

Now let’s see what the Signal class contains and how the count state is updated in the example above.

signal.dart
1
class Signal<T> with RtState {
2
// State value
3
T _value;
4
5
T get value => _value;
6
7
set value(T val) {
8
if (_value == val) return;
9
10
// Notify listeners that the state has changed,
11
// triggering the `Lifecycle.willUpdate` and `Lifecycle.didUpdate` events in order.
12
update((_) => _value = val);
13
}
14
15
// Private constructor, only a `Signal` instance can be created through the factory.
16
Signal._(this._value);
17
18
factory Signal(T value) {
19
// Register a new state in the Reactter context
20
return Rt.registerState(
21
() => Signal._(value),
22
);
23
}
24
[...]
25
}

During the process, as the value of count changes, the Lifecycle.didUpdate event is triggered, which is fired by the update method (signal.dart, line 12). This event is listened to by the Rt.on method (main.dart, line 10), which prints the value of count.

This occurs thanks to the reactivity of Reactter, which is responsible for notifying listeners by emitting events related to the lifecycle of the state.