Restate is a reactive state management libray for Flutter applications with no dependencies and < 200 lines.
Flutter has a variety of great state management libraries you can explore to get a feel for what works best for your own applications. With Restate, the goal was to make a state management lib that was simple to use and as frictionless as possible.
In the past, we’ve used state management tools like InheritedWidgets
or Redux
, which have their own powerful features, but also come with more boilerplate and can be higher friction to use. Let’s dive in and see how Restate compares!
Each Restate StateBloc holds a single state value accessible synchronously, as well as a Future or as a Stream of values.
import 'package:restate/restate.dart';
final counterState = StateBloc<int>(0);
print(counterState.value); // 0
counterState.add(1);
print(counterState.value); // 1
import 'package:restate/restate.dart';
final counterState = StateBloc<int>(0);
counterState.stream.listen((value) {
print(value);
// 0
// 1
// 2
});
counterState.add(1);
counterState.add(2);
import 'package:restate/restate.dart';
final counterState = StateBloc<int>(0);
counterState.changes.listen((value) {
print('${value.previous}->${value.current}');
// null->0
// 0->1
// 1->2
});
counterState.add(1);
counterState.add(2);
import 'package:restate/restate.dart';
final counterState = StateBloc<int>();
counterState.current.then((value) => print(value)); // 1
counterState.add(1);
counterState.current.then((value) => print(value)); // 1
Accesing and listening for updates to your state is as simple as creating a StateBloc and then using a StreamBuilder to rebuild your widget when data changes:
final counterStateBloc = StateBloc<int>(0);
class MyWidget extends StatelessWidget {
@override
build(context) {
return StreamBuilder(
stream: counterStateBloc.stream,
builder: (context, counterSnap) {
if (!counterSnap.hasData) {
return Text('Waiting for value...');
}
final counter = counterSnap.data;
return Column(
children: [
Text('Counter: $counter'),
ElevatedButton(
onPressed: () {
counterStateBloc.add(counter + 1);
},
),
],
);
}
)
}
}
That’s it! You can run the demo to see a more in-depth working example.
Generally, the StateBloc.add API is sufficient for updating the current value held by a StateBloc
. Sometimes, however, you may be working with complex objects that need to be mutated.
To keep your state values immutable, you can see if it’s possible to use a copyWith
function to your objects:
class User {
String firstName;
String lastName;
User({
required this.firstName,
required this.lastName,
});
User copyWith({
String? firstName,
String? lastName,
}) {
return User(
firstName: firstName ?? this.firstName,
lastName: lastName ?? this.lastName,
);
}
}
final user = User(firstName: 'Anakin', lastName: 'Skywalker');
final userState = UserStateBloc<User>(user);
userState.add(
userState.value.copyWith(
firstName: 'Darth',
lastName: 'Vader',
),
);
Many Flutter data objects like TextStyle already support this pattern.
If you instead need to mutate the current value, you can use the StateBloc.setValue API:
final user = User(firstName: 'Anakin', lastName: 'Skywalker');
final userState = UserStateBloc<User>(user);
userState.setValue((currentUser) {
currentUser.firstName = 'Darth';
currentUser.lastName = 'Vader';
});
The setValue
API provides the current value held by the StateBloc
, allowing you to mutate it as necessary, and then re-emits that object on the StateBloc.stream.
Let us know if there’s a feature or changes you would like to see to Restate on GitHub and happy coding!