observable_state 0.0.1+1 observable_state: ^0.0.1+1 copied to clipboard
Yet Another Flutter State Manager for a Reactive Application Architecture
Observable state #
🔭 Yet Another Flutter State Manager for a Reactive Application Architecture.
But this time #
- State mutations are handled by the OO Encapsulation Principle on Plain-old objects.
- You can freely Unit Test your State and its Mutations, like pure Dart (because IT IS pure Dart).
- Keep track of every State change using an Enum, simple like that.
- Notify only the specific States that are Observing the triggered change, NOT the entire Widget tree.
- Actually, observable_state does not mess with Widgets, it's all about the State. Stateless are kept Stateless.
- No Streams or new Widgets, it is purely
State
andsetState()
, like Vanilla, but ⚡ Reactive!
How it works #
It's a sweet, sweet sugar on top of the battle-tested Observer pattern. Your State is stored in a List of Observables in a given Subject (Change) and it's setState()
is called only when your Model explicit tells to notify about that Change.
- It is not BLoC, but you still can maintain, test and visualize your Business Logic away from the UI.
- It is not Flux (Redux/Rx/Stream), but you still can control data in an unidirectional flow.
Usage guide (Get started) #
The fact is: state is hard! Probably the hardest thing (after naming, of course). This is why things like BLoC, Flux/Redux and ScopedModel appears; to help you solve that.
And that is why observable_state is here too.
Talk is cheap. Show me the code — Torvalds, Linus
Installing #
dependencies:
observable_state: ^0.0.1
State modeling #
class MyState {
int counter;
}
From a simple model like that, to add observable_state super-powers you just need to:
class MyState extends Observable<MyState, Changes> {
int counter;
}
Then make sure you declare what changes to your Model are:
enum Changes {
increment,
}
Even better, remember that Mutations are handled by the OO Encapsulation Principle, so:
enum Changes {
increment,
}
class MyState extends Observable<MyState, Changes> {
int _counter;
int get counter => _counter;
void increment() {
_counter++;
}
}
Ok, cool, but how to notify about the increment
Change? #
Well, how Flutter does it with local state? With the setState()
method, right? observable_state borrows the same API, but with a little difference: the notify
argument to notify States listening to this change!
enum Changes {
increment,
}
class MyState extends Observable<MyState, Changes> {
int _counter;
int get counter => _counter;
void increment() {
setState(() => _counter++, notify: Changes.increment);
}
}
That is it. Your Observable
is notifying about MyState
Changes.
How to listen to then? #
Here comes the StateObserver
.
Remember: StatelessWidget
s are state-less as the name suggests. We are not going to mess with them adding Streams or whatever. Actually, even the StatefulWidget
remains the same. We are going to super-power the State
!
class MyStatefulWidget extends StatefulWidget {
@override
_MyStatefulWidgetState createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends StateObserver<MyStatefulWidget, MyState, Changes> {
@override
List<Changes> get subjects => [Changes.increment];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Observable state #${state.counter}')),
body: Center(child: Text('Counter: ${state.counter}')),
floatingActionButton: Builder(
builder: (context) {
return FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
state.increment();
},
);
},
),
);
}
}
Notice that we are extending StateObserver
instead of State
and we are given a list of Changes we are interest in, like Changes.increment
.
StateObserver
already got the state
so we can get counter
and we can call increment()
on it as well.
Wherever increment()
is called, since it notifies about Changes.increment
, whoever StateObserver
is observing this change, it will automatically calls its inner setState()
method, then rebuilding it.
Where does the state
comes from? #
The last piece: ObservableProvider
.
class App extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ObservableProvider(
MyState(),
child: MaterialApp(home: MyStatefulWidget()),
);
}
}
It's an InheritedWidget responsible to instantiate your initial State and handle it on its Context.
That is it! You're ready to Rock! 🎸
ObservableModel
has no dependency at all with any Flutter APIs, so you can Unit test your Model using Plain-old test
package. And you can test your Widgets using a Mock or a real state without any hassle.
Check out the example directory for a complete example with Asyncs, Services and Dependency Injection.
⚠️ For now, use with caution, API may change.
© 2019