A simple but reactful state management library for Flutter.
- Easy to use.
- Not depend on any other libraries.
- No boilerplate code.
- No need to use
setState
.
Rx
is an observable unit. You can .rx()
on any object to create an observable.
final counter = 0.rx();
final text = 'Hello, World!'.rx();
final pi = 3.14.rx();
final list = [1, 2, 3].rx();
final map = {'a': 1, 'b': 2, 'c': 3}.rx();
class User {
final String name;
final int age;
User(this.name, this.age);
}
final user = User('Oh Gihwan', 20).rx();
or you can make every field of your class observable.
class User {
final Rx<String> name;
final Rx<int> age;
User(this.name, this.age);
}
final user = User('Oh Gihwan'.rx(), 20.rx()).rx();
If you want to create observables that depend on other observables,
you can use Rx
class itself.
final hours = 0.rx();
/// minutes will change when hours changes.
final minutes = Rx(() => hours.value * 60);
/// seconds will change when hours and minutes changes.
final seconds = (() => minutes.value * 60).rx(); // same as ComputeRx(() => minutes.value * 60)
final currentHour = 0.rx();
final currentMinute = 0.rx();
final currentSecond = 0.rx();
/// Rx will call `toString()` on a `value`.
/// If you override `toString()` method, you don't need to call `value.toString()`.
/// `currentTime` will change when currentHour, currentMinute, currentSecond changes.
final currentTime = Rx(() => '${currentHour}:${currentMinute}:${currentSecond}');
If you want to delay the createion of the Rx
until you get the required information,
you can use .rxFamily
or RxFamily
.
final _counterTemplate = ((input) => input).rxFamily();
/// ....
final _counterFromZero = _counterTemplate(0);
final _counterFromTen = _counterTemplate(10);
You can change the state of the observable by calling .value = ...
on it.
final counter = 0.rx();
// ...
// this will rebuild the RxObserver widgets that depend on the counter
counter.value++;
If you make the fields of your class observable,
you can change the state of the class by calling .value = ...
on the fields.
But if you don't make the fields observable, but you still want to change the state of the class
with the reactive way, you can use rebuild()
method.
class User {
User(this.name);
var String name;
}
final user = User('Oh Gihwan').rx();
// ...
// this will not rebuild your RxObserver widgets
user.name = 'Gihwan Oh';
// this will rebuild your RxObserver widgets
user.rebuild();
ReactiveBuilder
observes the observables and rebuilds the widget when the observables are changed.
You don't need to explicitly define what you are depending on. Just wrap your widget with ReactiveBuilder
.
class MyWidget extends StatelessWidget {
final counter = 0.rx();
@override
Widget build(BuildContext context) {
return ReactiveBuilder(
builder: (BuildContext context) {
return Text(counter.value.toString());
},
);
}
}
or you can simply use .observe
extension on a widget builder.
A widget builder is a function that returns a widget.
beatx_rx
will pass the BuildContext
to the builder function if needed.
You can omit the BuildContext
parameter if you don't need it.
class MyWidget extends StatelessWidget {
final counter = 0.rx();
@override
Widget build(BuildContext context) {
return (() {
return Text(counter.value.toString());
}).observe;
}
}
Wrapping your widget with ReactiveBuilder
is not the only way to observe the observables.
Sometimes you want to observe the observables in a specific widget,
just like StatelessWidget
and StatefulWidget
.
beatx_rx
provides StatelessReactiveMixin
, StatefulReactiveMixin
, and ReactiveStateMixin
for this purpose.
For example, ReactiveBuilder
is just a wrapper of StatelessReactiveMixin
.
If you don't need to use lifecycle methods, you can mixin StatelessReactiveMixin
.
/// Somewhere in your code
final counter = 0.rx();
/// ...
class MyObserverWidget extends StatelessWidget with StatelessReactiveMixin {
@override
Widget build(BuildContext context) {
return Text('You clicked the button ${counter} times!');
}
}
If you need to use lifecycle methods, you can mixin StatefulReactiveMixin
.
class MyStatefulSomeWidget extends StatefulWidget with StatefulReactiveMixin {
/// your code here
}
/// State class does not need to be modified
FutureRx
is a special type of Rx
that observes a Future
.
FutureRx
will rebuild the widgets that depend on it when the Future
state changes.
Defining a FutureRx
is same as defining a Rx
.
Future<int> delayedCounter() async {
await Future.delayed(Duration(seconds: 3));
return 1;
}
final myDelayedRx = delayedCounter().rx();
You can use a set of getters and methods to observe the state of the Future
.
isCompleted
,isError
,isLoading
, andisCanceled
getters.when
andmap
methods
All of these getters and methods are reactive. They will rebuild the widgets that depend on them
when the Future
state changes.
// `myDelayedRx` from the above example
ReactiveBuilder(
builder: (BuildContext context) {
/// Provide all the possible states of the `Future`
return myDelayedRx.map(
loading: () => Text('Loading...'),
error: (error) => Text('Error: $error'),
completed: (value) => Text('Completed: $value'),
canceled: () => Text('Canceled'),
);
/// Or, at least `orElse` callback,
return myDelayedRx.map(
orElse: () => Text('Loading...'),
completed: (value) => Text('Completed: $value'),
);
}
)
when
method is similar to map
method, but it doesn't return a value.
You can cancel the ongoing Future
with cancel()
method.
// You can omit the `await` keyword
await myDelayedRx.cancel();
If your async method depends on other Rx
, you can create a reactive AsyncRx
.
FutureRx
is holding a Future
itself, but AsyncRx
is holding a function that returns a Future
.
Just use .rx
on your async method, not on a Future
result.
Let's see the example.
/// Somewhere in your code
final counter = 0.rx();
/// ...
Future<int> delayedCounter() async {
/// All the dependencies should not be in a async gap
final count = counter.value;
await Future.delayed(Duration(seconds: 1));
return count;
}
/// ** There is no method caller `()`, just make method itself a reactive **
final reactiveDelayedCounter = delayedCounter.rx();
/// Now, same as `FutureRx`
ReactiveBuilder(
builder: (BuildContext context) {
/// Provide all the possible states of the `Future`
return reactiveDelayedCounter.map(
loading: () => Text('Loading...'),
error: (error) => Text('Error: $error'),
completed: (value) => Text('Completed: $value'),
canceled: () => Text('Canceled'),
);
/// Or, at least `orElse` callback,
return reactiveDelayedCounter.map(
orElse: () => Text('Loading...'),
completed: (value) => Text('Completed: $value'),
);
}
)
In the above code, it the counter
changes, the previous reactiveDelayedCounter
will be canceled and
rebuild the ReactiveBuilder
with a new result of reactiveDelayedCounter
.
You can find examples from examples directory.