Making Flutter more Reactive

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

MVVM

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.

ghuntley

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:

RxVAMS

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

Code for this step

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

Code for this step

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 Switchto disable the update functionality. The App now looks like this:

App_Step_2

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 called subscribe. As RxDart 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 a bool value every time the execution state of the command changes. So after calling execute it will emit true and when the wrapped function returns (even an async one) it will emit a false.
  • .canExecute emits a bool value every time the executability of the command changes. This will emit false while the command is executing but will also reflect the status of the canExecute 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 observable RxCommand 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 pass B.isExecuting when creating A as canExecute 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();                                  
        }
    })                                              
    ),

Code for this step

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 the Stream interface

Currently it contains this classes:

RxSpinner a platform aware busy spinner that takes a Stream and builds a running Spinner on trueand 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:

App_Step_3

By using Streamsor 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.

Contact me:

17 thoughts on “Making Flutter more Reactive

  1. Robin says:

    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>’.

  2. Robin says:

    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

    • admin says:

      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);

  3. Antonin says:

    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 !

    • admin says:

      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.

  4. Faruk says:

    How pass more then 1 TParam ?

    • admin says:

      You can only pass one parameter. So if you need more you have to wrap them in an object and pass that

      • Faruk says:

        updateWeatherCommand.execute();

        Any other way to initialize data?

        Every time before my build method logic i call my command that is subscribed on rxLoader

  5. Faruk says:

    updateWeatherCommand.execute();

    Any other way to initialize data?

    Every time before my build method logic i call my command that is subscribed on rxLoader

  6. Manuel Franco Giraldez says:

    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?

  7. Roel Cagantas says:

    I think you mean “manner”, not “manor”

  8. Pradeep Manchu says:

    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)

  9. Bachar says:

    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.

  10. Excellent. Detailed one to read and will share it in our Tutlane.com site.

Leave a Reply to Suresh Dasari Cancel reply

Your email address will not be published. Required fields are marked *