Ongoing adventures with Flutter and Redux…

We’re now about 3 months into the project (with a short detour to work on itsallwidgets.com), I thought it may be helpful to share some more thoughts on using Redux to manage state in Flutter.

What about BLoC?

I don’t believe there are ‘better’ architectures, selecting the right one for your app comes down to your specific needs and background. Redux has been around long enough that it’s become a well understood architecture, most topics are covered by detailed tutorials.  They’re typically for React/JavaScript but it’s straightforward to port the ideas to Flutter/Dart.

While I haven’t used it myself developers are clearly very happy with the BLoC pattern. My sense is the differences between Redux and BLoC are similar to Redux and MobX. I think Dart’s first class support for streams combined with the reactive nature of Flutter make the BLoC pattern a great fit. It’s worth noting that under the hood the flutter_redux library is just providing a wrapper for StreamBuilder, you don’t have to feel like you’re missing out using streams with Redux.

I find it interesting that in the JavaScript/React world you have to convince developers not to use Redux whereas with Flutter it seems to be the opposite, that said I really like this tweet from Dan Abramov the co-creator of Redux.

I’m endorsing MobX because I hear its happy users and I hate to see FUD against new approaches. But I still prefer immutability 😉

Another valid criticism of Redux is that debugging the code can require a lot of searching for matching actions. I don’t have any experience developing plugins for an IDE but I’m curious if it’s possible to build a plugin which understands the app state and reducers, enabling you to trace the Redux flow the same way you can drop down into the source code. UPDATE: turns out it’s possible… here’s the plugin.

Don’t rebuild too much

The main challenge we faced initially was preventing too much of the app from getting rebuilt when the state changed. For example, we weren’t able to keep a bottom sheet open if a setting was selected. The problem was that we were using a StoreBuilder in main.dart, this meant any change rebuilt most of the app. The solution was to move the rebuilding further down the widget tree limiting it to just the parts of the app which needed to be updated.

If you’re basing your app of off Brian Egan’s excellent Redux architecture sample take note that this approach is currently used. It can work for some apps but it’s definitely something to be aware of.

Related to this it’s important to note that all widgets, including non-visible widgets further down the navigation stack, are rebuilt when the state changes. In our app we simply keep the navigation stack short. but you can set distinct to true to only update the view when the state is updated. Note: this also requires overriding == and hashCode on the view model.

Boilerplate Code

I think the main criticism of Redux is that small changes to the functionality can require large changes to the code. In my experience this is accurate. These metrics are from Brian’s comment on the Reddit post linked above.

At this point, scoped_model requires the least amount of code at ~800 LOC. Next best is simple_bloc at around ~900. Finally Redux was ~1200.

It was clear from the outset that app would require a lot of boilerplate code, rather than write it by hand we created a library to help generate it for us.

While I agree less code is generally better than more code (every one less line of code we write is one less line for a bug to hide in), our main objective is to build the best app with the fewest bugs possible. Redux provides a form of state machine for your UI/UX making it far more predictable, that benefit though does require paying a Redux tax in the form of additional lines of code.

Built Value

I mentioned this in the last post but it’s too important not to mention again. If you’re building a Redux app strongly consider using built_value to handle immutability. It’s possible to build a Redux app without it however it would require writing a ton of extra code by hand.

A great feature is that it automatically adds support for object equality, this is extremely useful when managing forms. One challenge however is if the app supports creating multiple blank records they’ll end being equivalent. Our solution is to use a static counter to set a negative id for new records, once they’re saved to the server the ids are replaced.

Although built value supports the @nullable annotation I’d advise avoiding it if possible. To support creating new records in the app we use a factory constructor which sets default blank values. One of the many problems with using the @nullable annotation is a new record will appear changed by the form because the field will be set to an empty string rather than null.

It’s worth noting if you’re making a lot of changes consider using the watch command rather than the build command, it’s much quicker to update the files.

Forms

We created this sample app which demonstrates our approach to forms. To persist state while typing we listen for changes to the TextEditingController, when they’re detected we use the view model to update the store. When the user clicks ‘Save’ we run the form validation, if it passes we persist to the server.

To support localization (ie, number or date formatting) we initially passed all of the supporting data to the forms in the view model however found it required a fair amount of extra code mapping each of the fields and then passing it down. Instead we now just pass the context to a formatting utils class and use StoreProvider.of<AppState>(context).state; to access the state.

If you use this approach it’s important to move any initialization code from initState to didChangeDependencies. This is spelled out clearly in the docs for the State class.

Subclasses of State should override didChangeDependencies to perform initialization involving InheritedWidgets.

Gotchas

Like most design patterns done right Redux can be amazing, however if done wrong it can become painful to say the least. One critical aspect is separating application state from transient UI state. We’ve found we use a combination of dispatching actions alongside some calls to setState() for cases where we’re managing transient state.

To support changing top level settings such as the application theme or locale we use a custom AppBuilder widget, this enables to on-demand rebuild the entire app. You can read more about it here. Our top level widgets are ReduxStore > AppBuilder > MaterialApp, this enables us to rebuild the MaterialApp without affecting the store.

If you’re using a form in a dialog and the state changes the form will rebuild with the latest values from the state, this may or may not be the desired behavior. We prevent it by checking if the form has already been initialized.

In middleware functions take note of whether next(action); is at the beginning or end of the function, it will determine whether or not the state is updated before/after the code is executed.

Debugging

If you’re using the flutter_redux package I’d highly recommend installing redux_logging, it automatically prints the Redux actions to the console. This is really helpful when trying to debug the app.

By default the output will include the entire state (which can get pretty big), you can override the toString method on the state class to just print the relevant info.

Wrap Up

Flutter is amazing… whether you use Redux, BLoC, ScopedModel or something else you’ll be able to quickly build a great app. In my time with Redux I’ve found it works incredibly well for our use case (a CRUD style app). I think a key metric for these choices is whether development speeds up or slows down over time, we’ve found we’re constantly accelerating.

If you’d like to learn more I’ve also written about our overall architecture, managing complex forms and other Redux topics. You can follow @hillelcoren for my latest thoughts on Flutter. If anything’s unclear please let me know.

flutter-logo-sharing

Flutter: How to rebuild the entire app to change the theme or locale

Update: although this widget does work as described it’s an anti-pattern. The better approach would be to define the state and let Flutter handle it. Thanks to Pascal Welsch for pointing this out and Iiro Krankka for providing a working example.

Settings which are defined in the MaterialApp widget, such as the theme or locale, require the entire widget tree to be rebuilt when changed.

This article (and package) by Norbert Kozsir make it easy to set the app’s theme however in our case we need to manage additional top level properties including the locale. All of the settings are stored in our central data store, we just need to tell the app to rebuild after the settings are changed.

This AppBuilder widget (which is a stripped down version of Norbert’s solution) makes it possible for any part of the widget tree to request the app to be rebuilt. I believe the key aspect which makes it work is that the build method calls a builder function rather than directly returning a child widget.

import 'package:flutter/material.dart';

class AppBuilder extends StatefulWidget {
  final Function(BuildContext) builder;

  const AppBuilder(
      {Key key, this.builder})
      : super(key: key);

  @override
  AppBuilderState createState() => new AppBuilderState();

  static AppBuilderState of(BuildContext context) {
    return context.ancestorStateOfType(const TypeMatcher<AppBuilderState>());
  }
}

class AppBuilderState extends State<AppBuilder> {

  @override
  Widget build(BuildContext context) {
    return widget.builder(context);
  }

  void rebuild() {
    setState(() {});
  }
}

You can use this class by wrapping your MaterialApp with it, for example:

Widget build(BuildContext context) {
  return AppBuilder(builder: (context) {
    return MaterialApp(
       ...
    );
  });
}

Finally, you can tell the app to rebuild using:

AppBuilder.of(context).rebuild();

You can see a full working example here.

download

Write the code that matters…

Code on GitHub: hillelcoren/flutter-redux-starter

I’m still relatively new to this Redux thing but it seems one point can be agreed on by everyone, there’s a lot of boilerplate code.

Building apps in many ways is like running a small company, there are always various departments at work vying for time and resources. Considering both are limited it’s imperative that developers spend their time writing the code that’s special to their app.

To help make writing Redux apps with Flutter faster we’ve created a library which automatically generates the CRUD (create, read, update and delete) code for you. It takes a standard REST API and converts it into a performant Flutter app.

starter

We’re developing this project in parallel with the mobile app for our invoicing platform. We’re working to improve the code templates through experiences solving problems for our app. The thought is if more people are using the same base templates we can collectively improve them to benefit everyone.

I created two videos to help explain the project. The first video is extremely short (a bit over a minute) and demonstrates using the project to create an app.

The second video is longer (closer to 10 minutes) and goes into more detail explaining why we created the project and how it works under the hood.

While many awesome Flutter apps look nothing like a CRUD app many others do. Hopefully the library can benefit some developers out there. If you try it out and have any questions let me know.

Building a (large) Flutter app with Redux

Update: since writing this post we’ve created a Redux code generator to reduce boilerplate code.

To continue from my last post we’re now two weeks into the development of our new open source Flutter mobile app for Invoice Ninja. I thought it may be helpful to share some of our early experiences with Flutter and Redux.

It seems the main entry point for Flutter/Redux are these excellent sample architectures. In this post I’ll try to detail how we’ve adapted it to support our particular use cases.

The main change we made initially was to restructure our codebase in line with the layout suggested by this project. The top level folders are data, ui and redux which can be thought of as Model, View and Controller/domain. Within the redux and ui folders are separate folders for each of the app’s features (clients, invoices, etc). Using the approach has made it easier to work with related files when digging into a particular feature.

mobile_app (1)

A nice aspect of the sample architecture is that the views are split into ‘container’ and ‘presentation’ widgets (or stateful/stateless), this enables dividing the UI code into view logic and view layout. These terms come from the world of Redux however the term ‘Container’ conflicts with a ‘Container‘ in Flutter. We’re labeling Containers as ViewModels to help clarify their use in the app, we’ve also made the view model class public which enables passing it to the view rather than map each property individually.

There are two main Redux examples provided: Redux and Built Redux. My approach when working with a new technology is to start with the simplest implementation and only add in extras once I’ve felt the pain they’re designed to eliminate. Master the 4 string bass before buying the 5 string…

In the plain sample the models are manually generated. From quick research it seemed json_serializable was the common choice to auto generate JSON serialization code. While the library worked as advertised it left us needing to manually write the hash/equality code required to support detecting state changes in Redux. Changing over to the built_value library (which is also used by the Built Redux sample) solved this as it generates everything needed.

As an example, it converts a Login class from this:

before
To this:
after
One issue we ran into when refactoring the reducers was this error:
error: A value of type ‘…State’ can’t be assigned to a variable of type ‘…StateBuilder’.

We were initially able to solve it using the toBuilder() method but it had a serious code smell. Thankfully the author of the library suggested a better approach using the replace method instead.

A question that came up early on was how to handle navigation with respect to the app state. As became clear from the comments here navigation already has a state which is being tracked by Flutter. Adding it to the app state would end up duplicating the state in two places. We’re currently managing the navigator in our view models but I recently saw this suggestion to use middleware which we’re going to look into. (UPDATE: to achieve full persistence we’re now storing the current route in the state).

It’s worth mentioning that although most navigation tutorials focus on pop and push, the replace methods can be extremely useful when navigating a user through your app. For example to remove the login screen from the stack once a user has successfully logged in.

In our app each user can have up to five companies under one account. To support this our main AppState class has five companyState properties along with a selectedCompanyIndex field. We then use the index in the app_reducer to only pass the action to the selected company.

Another issue we faced was how to show a snack bar message after submitting an API request from the middleware. Our initial solution was to pass the context as a parameter in the action. This worked as the middleware was able to use the snack bar however it introduced UI concerns where they didn’t belong.

A cleaner approach we’ve switched to is to send a completer with the action. This enables the view model to handle any steps required once the action completes as well as remove the view dependancies from the middleware.

The last feature I’ll review is sorting and filtering. The app needs to handle thousands of records so this can become computationally expensive. To solve this we’re using the memoize library to cache our filtered listed.

The first time the function is called the list is calculated normally. When the function is called again with the same parameters (ie, sort/filter settings) it returns the original result without needing to recalculate which greatly improves the performance.

So far we thrilled with the development experience Flutter with Redux is providing. We’re making rapid progress and should have a basic app ready in just a few more weeks.

Continue to part 3 >>