When chatting with some dev about the reactivity of Flutter he made the great statement Flutter is REACTive and not reactive in the sense we know from Reactive Extensions (Rx).
Coming from Xamarin and ReactiveUI this somewhat disappointed me because if you once started to think the Rx way you never want to go back.
From MVVM to RxVAMS
Coming from Xamarin Forms MVVM is the natural architectural pattern for Apps for me. MVVM solves a lot of problems while still is easy to understand and don't introduce unnecessary components.
Please stay with me for a moment we come back to Flutter I promise
I won't describe MVVM in detail this is excellently done here
It basically consists of three layers where the View which consists of all UI elements you see on the screen. It can also contain logic that only concerns the UI. The Model contains your business logic and backend connections. The ViewModel is the glue between both in that it processes and transforms data from the Model to a form the View can easily display. It offers functions (often called Commands) the View can call to trigger actions on the Model and provides events to signal the View about data changes.
Important: The ViewModel knows nothing about the View which makes it testable and more than one View can use the same ViewModel. A ViewModel just offers services to the View (events, commands). The _View decides which it uses.
Several MVVM frameworks encapsulate the subscription of events and calling functions to update data in the ViewModel from the View using DataBindings
To trigger any other action in the View besides data updates from the ViewModel gets tedious because you have to publish events and functions if the View should be able to retrieve data as a result of the event.
Another problem is that we always moving state between the different layers which have to be kept in sync.
What we really want is an App that just reacts on any event from the outside without having to deal with state management all the time.
Rx to the rescue
If you never heard of Rx before think of it as streams of events on that you can do transformations in a Linq like manner. Although Rx was first developed be Microsoft it is no available to almost any Language. You can find an introduction on reativex.io From there:
The ReactiveX Observable model allows you to treat streams of asynchronous events with the same sort of simple, combindable operations that you use for collections of data items like arrays. It frees you from tangled webs of call-backs, and thereby makes your code more readable and less prone to bugs.
When working with Rx you define rules how to react on events in a declarative way. This is done in a functional manor to reduce any side effects. Applied to MVVM this means all communication between layers happen over even streams (called Observable
in Rx) and reactive commands. Best of all Observable streams are asynchronous by nature.
The Dart implementation of Rx is RxDart.
Introducing RxVAMS
I have no idea if anybody else will use this name but I like it 🙂
There is another observation that leaves some doubt if MVVM is the right pattern for mobile Apps. MVVM was aimed at sharing the code of Model and even ViewModel between different apps which is really rarely the case.
Looking at real world apps you often realize that the model layer degenerated to a service layer that connects the app to the rest of the world and that the VieModels have more and more logic in it instead just beeing an adapter. The reason for this is that typical apps don't deal with complex business logic. So I propose the following pattern for Apps:
This also separates the App in three layers but replaces the ViewModel with an AppModel that contains the whole app logic and the interface for the View consisting of reactive commands (in the following RxCommands) and Observables that offer state changes to the view.
In order to react on events the View subscribes to this Observables
The Model is replaced by a designated Service layer which offers all services that connects the app to the outside (REST APIs, device APIs, databases). Data from the Service Layer is returned as Observable
or be asynchronous function calls to make the access asynchronous.
If you don't want to follow this pattern just replace AppModel with ViewModel in your mind for the rest of the post.
Calling to action
I made a little demo app to show the following in practise. It queries a REST API for Weather data and displays it in a ListView. I made branches for every step of the rest of this post so that you can easily try it yourself.
A big, big thank to Brian Egan for polishing the App for the last step, even including unit and UI Tests!
Inject the AppModel to the View
To access an object from anywhere in the widget tree Flutter offers the concept of the InheritedWidget. When added to a widget tree the InheritedWidget
can be accessed anywhere down that tree by calling its static of()
method.
In main.dart
:
class TheViewModel extends InheritedWidget
{
final HomePageAppModel theModel;
const TheViewModel({Key key,
@required
this.theModel,
@required
Widget child}) : assert(theModel != null),assert(child != null),
super(key: key, child: child);
static HomePageAppModel of(BuildContext context) => (context.inheritFromWidgetOfExactType(TheViewModel)as TheViewModel).theModel;
@override
bool updateShouldNotify(TheViewModel oldWidget) => theModel != oldWidget.theModel;
}
In this case the .of()
doesn't return the inherited widget but directly the contained HomePageAppModel
instance as it is the only data field.
As we want to have our AppModel available anywhere in our HomePage
we insert it in the very top of our widget tree.
In main.dart
:
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return new TheViewModel(
theModel: new HomePageAppModel(),
child:
new MaterialApp(
title: 'Flutter Demo',
home: new HomePage()
),
);
}
}
Step 1: Making the Views Reactive
The typical way to react with Flutter on events from a ViewModel is to create a StatefulWidget
and subscribe to an event in the initState
method of the State
and call setState
on each event.
Using StreamBuilder
and Streams
instead of event subscriptions is the first step to make this easier StreamBuilder takes a stream as an input and calls its builder function each time it receives a new input:
In listview.dart
:
Widget build(BuildContext context) {
return new StreamBuilder<List<WeatherEntry>>( // Streambuilder rebuilds its subtree on every item the stream issues
stream: TheViewModel.of(context).newWeatherEvents, //We access our AppwModel through the inherited Widget
builder: (BuildContext context, AsyncSnapshot<List<WeatherEntry>> snapshot) // in Dart Lambdas with body don't use =>
{
// only if we get data
if (snapshot.hasData && snapshot.data.length > 0)
{
return new ListView.builder(
itemCount: snapshot.data.length,
itemBuilder : (BuildContext context, int index) =>
buildRow(context,index,snapshot.data)
);
}
else
{
return new Text("No items");
}
}
);
Anytime someone queues a new Lists of WeatherEntry
in this stream this ListView will be recreated with the latest data.
This happens in homepage_appmodel.dart
:
// Subjects are like StreamSinks. You can queue new events that are then published on the .observable property of th subject.
final _newWeatherSubject = new BehaviorSubject<List<WeatherEntry>>() ;
// Only the observable of the Subject gets published
Observable<List<WeatherEntry>> get newWeatherEvents => _newWeatherSubject.observable;
update({String filtertext = ""})
{
_newWeatherSubject
.addStream( WeatherService.getWeatherEntriesForCity(filtertext)
.handleError((error)=> print) // if there is an error while accessing the REST API we just make a debug output
);
}
Querying data and updating are completely decoupled which makes testability easy.
update
is triggered by pressing the update button on the page
In homepage.dart
:
new MaterialButton(
child:
new Text("Update"), // Watch the Button is again a composition
color: new Color.fromARGB(255, 33, 150, 243),
textColor: new Color.fromARGB(255, 255, 255, 255),
see-> onPressed: TheViewModel.of(context).update
),
and if the user types into the filter TextField
.
In homepage_appmodel.dart
:
_inputSubject.observable
.debounce( new Duration(milliseconds: 500)) // make sure we start processing if the user make a short pause
.listen( (filterText)
{
update( filtertext: filterText);
});
Will call execute if a new string was queued into HomePageAppModel._inputSubject
which happens in this method:
// Callback function that will be registered to the TextFields OnChanged Event
onFilerEntryChanged(String s) => _inputSubject.add(s);
Again the event and the reaction are decoupled through a Subject and and Observable which allows further event processing like the debounce
operator which makes sure that only an event is emitted if there wasn't another change within a given period.
So to make your views reactive Streambuilder
is the key. So far almost everything besides the debounce
could be done too by using Dart Streams
instead of Observables
.
Step 2 adding RxCommands
To enable the Views to call functions in a reactive way I wrote the RxCommand package which is heavily inspired by the ReactiveCommand
class of the .net framework ReactiveUI. To be more correct this enables AppModels to react "reactive" on function calls from the View.
RxCommand
takes a function when created through one of its static factory methods. This method can be called by using its execute
method or by directly calling the RxCommand
object because it's a callable class. This makes it possible to assign a command directly to an event of a Widget.
Nothing special so far but any result of wrapped function is emitted from the RxCommand
over its results
property which is you might already expect it an Observable
. Especially when wrapping an async function with an RxCommand the .exceute
will return immediately but the result will be emitted when the function returns.
Besides moving to RxCommands
this iteration adds a busy spinner and a Switch
to disable the update functionality. The App now looks like this:
In homepage_model.dart
:
class HomePageAppModel {
RxCommand<String,List<WeatherEntry>> updateWeatherCommand;
RxCommand<bool,bool> switchChangedCommand;
RxCommand<String,String> textChangedCommand;
HomePageAppModel()
{
// Command expects a bool value when executed and issues the value on its result Observable (stream)
switchChangedCommand = RxCommand.createSync3<bool,bool>((b)=>b);
// We pass the result of switchChangedCommand as canExecute Observable to the upDateWeatherCommand
updateWeatherCommand = RxCommand.createAsync3<String,List<WeatherEntry>>(update,switchChangedCommand.results);
// Will be called on every change of the searchfield
textChangedCommand = RxCommand.createSync3((s) => s);
// handler for results
textChangedCommand.results
.debounce( new Duration(milliseconds: 500)) // make sure we start processing only if the user make a short pause typing
.listen( (filterText)
{
updateWeatherCommand.execute( filterText); // I could omit he execute because RxCommand is a callable class but here it
// makes the intention clearer
});
// Update data on startup
updateWeatherCommand.execute();
}
No more Subjects and handler functions just clean RxCommands. Let's look at it step by step beginning with the textChanged
command. The function that it wraps doesn't do anything else than pushing the passed string out of the RxCommand.result
Observable, to which the handler below listens.
In other Rx implementations
listen
is calledsubscribe
. AsRxDart
is based on Dart's `Streams' listen is used.
The switchChangedCommand
too just pushes the received bool
to its result
observable. It will be assigned to the onChanged
handler of the Switch
widget and issue a new bool
value every time the Switch
changes its state. This may seem a bit pointless because nobody seems to care about this results.
But looking at the updateWeatherCommand
we see that switchChangedCommand.results
is passed as a second (optional) parameter. That's the next feature of RxCommand
you can pass am Observable<bool>
which determines if the command can be executed or not. In our case this will make the updateWeatherCommand
react to any change of the Switch
automatically.
On the View's side the commands are directly used as handler functions which is possible because they are callable classes:
In 'homepage.dart'
new TextField(
autocorrect: false,
decoration: new InputDecoration(
hintText: "Filter cities",
hintStyle: new TextStyle(color: new Color.fromARGB(150, 0, 0, 0)),
),
style: new TextStyle(
fontSize: 20.0,
color: new Color.fromARGB(255, 0, 0, 0)),
--> onChanged: TheViewModel.of(context).textChangedCommand,),
Special Features:
Not only does RxCommand
offer a .results
Observable but also this Observables:
.isExecuting
emits abool
value every time the execution state of the command changes. So after callingexecute
it will emittrue
and when the wrapped function returns (even an async one) it will emit afalse
..canExecute
emits abool
value every time the executability of the command changes. This will emitfalse
while the command is executing but will also reflect the status of thecanExecute
observable if one was passed to the when creating the command. This allows to effectively set up rules on executability of multiple commands.- .
thrownExceptions
if there is a listener on this observableRxCommand
will catch any exception that are thrown by the wrapped function and emit them here. RxCommands
can not be called again while they are executing. If you want to disable command A while another command B is executing you only have to passB.isExecuting
when creating A ascanExecute
parameter. Cool isn't it? 🙂
Let's make use of this features in the App. For instance it's very easy to add a busy Spinner now by using another StreamBuilder
that listens on updateWeatherCommand.isExecuting
:
From homepage.dart
:
new StreamBuilder<bool>(
stream: TheViewModel.of(context).updateWeatherCommand.isExecuting,
builder: (BuildContext context, AsyncSnapshot<bool> isRunning)
{
// if true we show a buys Spinner otherwise the ListView
if (isRunning.hasData && isRunning.data == true)
{
return new Center(child: new Container(width: 50.0, height:50.0, child: new CircularProgressIndicator()));
}
else
{
return new WeatherListView();
}
})
),
While Brian did some refactoring on the example app he made the point that it would be helpful to have all three states (isExecuting, results, error
) in one stream because Flutter updates its UI by rebuilding it. Using separate StreamBuilder
might not always be ideal.
I totally agree with him. With frameworks that use bindings and update only single controls through them the separate streams were a good solution. For flutter I want to display alternative Widgets depending on the new state at one place during rebuild. This lead to the introduction of the CommandResult<T>
class:
/// Combined execution state of an `RxCommand`
/// Will be issued for any statechange of any of the fields
/// During normal command execution you will get this items if directly listening at the command.
/// 1. If the command was just newly created you will get `null, false, false` (data, error, isExecuting)
/// 2. When calling execute: `null, false, true`
/// 3. When exceution finishes: `the result, false, false`
class CommandResult<T>
{
final T data;
final Exception error;
final bool isExecuting;
const CommandResult(this.data, this.error, this.isExecuting);
bool get hasData => data != null;
bool get hasError => error != null;
@override
bool operator == (Object other) => other is CommandResult<T> && other.data == data &&
other.error == error &&
other.isExecuting ==isExecuting;
@override
int get hashCode => hash3(data.hashCode,error.hashCode,isExecuting.hashCode);
}
To get notified of any new CommandResult
you can directly .listen
to the command itself because RxCommand
now also implements the Observable
interface.
In the next step we will use that to our advantage.
Making life easier with rx_widgets
Using StreamBuilder is one way to react to AppModel events but it makes the Widget tree a bit messy and because I'm also lazy I wrote the rx_widgets package which contain convenience function to work with Streams, Observables and RxCommands.
You can pass an Observable at any place a Stream can be used because
Observables
extends theStream
interface
Currently it contains this classes:
RxSpinner
a platform aware busy spinner that takes a Streamtrue
and on false and alternative Widget.
With this the code from above would look like:
new RxSpinner(busyEvents: TheViewModel.of(context).updateWeatherCommand.isExecuting,
platform: TargetPlatform.android,
radius: 20.0,
normal: new WeatherListView(),)
As we want to take care of errors and empty data events too now there is also an extended version that takes directly an RxCommand
and offers three builders:
RxLoader<List<WeatherEntry>>(
spinnerKey: AppKeys.loadingSpinner,
radius: 25.0,
commandResults: ModelProvider.of(context).updateWeatherCommand,
dataBuilder: (context, data) => WeatherListView(data ,key: AppKeys.weatherList),
placeHolderBuilder: (context) => Center(key: AppKeys.loaderPlaceHolder, child: Text("No Data")),
errorBuilder: (context, ex) => Center(key: AppKeys.loaderError, child: Text("Error: ${ex.toString()}")),
),
WidgetSelector
builds one of two provided Widgets depending on the bool
value the provided Stream emits. In our app I use it to enable/disable the update Buttton
based on the updateWeatherCommand.canExecute
Observable.
In homepage.dart
:
new Expanded(
child:
WidgetSelector(
buildEvents: ModelProvider.of(context).updateWeatherCommand.canExecute, //We access our ViewModel through the inherited Widget
onTrue: RaisedButton(
key: AppKeys.updateButtonEnabled,
child: Text("Update"),
onPressed: ()
{
_controller.clear();
ModelProvider.of(context).updateWeatherCommand();
}
),
onFalse: RaisedButton(
key: AppKeys.updateButtonDisabled,
child: Text("Please Wait"),
onPressed: null,
),
),
),
More reactive widget goodies to come soon. If you have an idea for a great
RxWidget
just open an issue or even better make a PR.
Conclusion
Thanks to Brian Egan our App now looks like this:
By using Streams
or even better RxDart's Observables
in combination with StreamBuilder
and RxCommands
and rx_widgets
can make your App truly reactive.
If you got hooked on the idea of streams of events, vote for this Flutter issue to make Flutter use Dart Streams
for notifications.
Is it possible to create a Repository method with optional named parameter
e.g.: Future<List> getCities({int offset = 0}) async {
now it shows an error
The argument type ‘({offset: int}) → Future<List>’ can’t be assigned to the parameter type ‘(int) → Future<List>’.
I would need to see some more of your code to answer this. Best to open an issue on rx_command repository
These are my two classes, ModelCommand and Repository
class ModelCommand {
…
final RxCommand<int, List> getsCommand;
…
factory ModelCommand(Repository repository) {
…
final _getsCommand = RxCommand
.createAsync3<int, List>(repository.gets);
…
}
}
class Repository {
…
Future<List> gets({int offset = 0}) async {
…
}
…
}
how should i declare the optional int parameter in getsCommand in ModelCommand class
If I understand this correctly your command will receive the offset as parameter?
If you wouldn’t used a named optional you could just pass your method so you have to do:
.createAsync3<int, List>((offest) async => repository.gets(offset:offset);
Hello,
Thanks for this clear example ! I have one question about multiple view models application. How do you manage your “TheViewModel” class for this ?
Thanks !
Hi,
actually I myself are still exploring how to handle that the best way 🙂 Currently I only have one AppModel class in my current project that handles all state changes.I see too possibilities. I could move parts of the AppModel into “Manager” classes that are members of the AppModel or that get directly registered with my Service Locator GetIt.
Btw I recommend using getIt instead of an InheritedWidget to access the AppModel.
How pass more then 1 TParam ?
You can only pass one parameter. So if you need more you have to wrap them in an object and pass that
updateWeatherCommand.execute();
Any other way to initialize data?
Every time before my build method logic i call my command that is subscribed on rxLoader
updateWeatherCommand.execute();
Any other way to initialize data?
Every time before my build method logic i call my command that is subscribed on rxLoader
Hi.
In a more complex App could be better separate in many ViewModel, Like working with Xamarin, one per Page EJ: listaPageViewModel, detailPageViewModel.
In your example if the model change, you rebuilt the entire app not just the desired page widget tree.
How do you solve that issue?
You are right that’s why I evolved RxVAMS to RxVMS see https://www.burkharts.net/apps/blog/rxvms-a-practical-architecture-for-flutter-apps/
I think you mean “manner”, not “manor”
Yep, you are right, thanks
This is perfect example for my app been workin on past couple f weeks, I have worked around tutorial and been hittin my head, could n’t understand logic behind listview replacing each other?????? how in my screen they splittin in half even when they in expanded!!!!!!!!!! little help may be(logic could hav been so simple)
newWeatherSubject
.addStream( WeatherService.getWeatherEntriesForCity(filtertext)
.handleError((error)=> print) // if there is an error while accessing the REST API we just make a debug output
);
this code will throw Exception if we update the filter and the WeatherService.getWeatherEntriesForCity(filtertext) not finished !!
from DOCS:
Events must not be added directly to this controller using add, addError, close or addStream, until the returned future is complete.
Excellent. Detailed one to read and will share it in our Tutlane.com site.