Flutter: Using Redux to manage complex forms with multiple tabs and relationships

The full code for this post can be found here.

In my last post I described how we’re using keys to manage the state of complex forms. Although the implementation works having any state outside of our main Redux store meant that persistence was incomplete. For example, if a user started to create an invoice and then closed/reopened the app before saving their work would be lost.

Screenshot_1528817346

I had initially decided to keep some of the state separate due to the large size of our store. A user can have up to five companies linked under a single account. Each company can have thousands of clients, invoices, etc. Having to constantly write the state to disk would be CPU intensive. To solve this we’ve modified our persistence middleware to save parts of the state separately and then piece it back together when initializing the state on load.

Many of the actions in our app require persisting either data state (ie, a list of clients) or UI state (ie, the current sort field). In order to prevent having an extremely long list of actions in the reducers we’ve create two base classes PersistData and PersistUI. Actions that require either can add them as mixins. We use the same approach to track the if the app is currently loading data or not.

Here’s an example with some of the client actions.

class LoadClientRequest implements StartLoading {}

class LoadClientSuccess implements StopLoading, PersistData {
 final BuiltList<ClientEntity> clients;
 LoadClientSuccess(this.clients);
}

class LoadClientsFailure implements StopLoading {
 final dynamic error;
 LoadClientsFailure(this.error);
}

class SortClients implements PersistUI {
 final String field;
 SortClients(this.field);
}

We’re using a shell script to help quickly generate some of the boilerplate Redux code. Using the mixins has the added benefit that it requires far fewer manual adjustments to the code once it’s generated.

You can see a full example here. We’re using Redux to manage a client’s details across multiple tabs. There are a few implementation details worth pointing out.

  • The code for the reducer is a bit unwieldy. In an actual app you’d most likely use built_value which provide a clean way to handle immutability.
  • In order to update the TextControllers with the state from the model overriding didChangeDependencies seems to work best.

Hope you found this post useful. If you can see any ways to improve the code feedback is greatly appreciated!

Flutter: Complex forms with multiple tabs and relationships

Note: we’re no longer using this approach, you can see our updated solution here.

In our app we needed to support editing complex entities with nested relationships, in this post I’ll try provide a high level overview of our solution.

The full code for this post can be found here

Screenshot_1528817346

For this example we’ll use a Client entity which has a name and a list of contacts, each contact has an email address.

class ClientEntity {
 ClientEntity({this.name, this.contacts});
 String name;
 List<ContactEntity> contacts;
}

class ContactEntity {
 ContactEntity({this.email});
 String email;
}

Dividing the UI into multiple tabs is relatively straightforward however as soon as we started testing we realized if a user made a change in one tab and then switched to a different tab without first clicking save their changes would be lost. Not good…

The solution is to have the state class use the AutomaticKeepAliveClientMixin. Adding the mixin is pretty simple, you just need to override the wantKeepAlive method to return true.

class ContactsPageState extends State<ContactsPage>
 with AutomaticKeepAliveClientMixin {

 @override
 bool get wantKeepAlive => true;

For most simple forms you can use keys or text controllers to access the text input values however with arrays/relationships it gets a bit more complicated. The approach we’re using is to have the master page create global keys for the sub-pages to use.

On the contacts page we store two lists for the contacts: the contact models as well as the keys for the contact form state.

List<ContactEntity> _contacts;
List<GlobalKey<ContactFormState>> _contactKeys;

@override
 void initState() {
  super.initState();
  var client = widget.client;
  _contacts = client.contacts.toList();
  _contactKeys = client.contacts
    .map((contact) => GlobalKey<ContactFormState>())
    .toList();
 }

To build the view we loop through each of the contacts/keys. When initially working on this whenever we focused the text input the focus would be lost, this was caused by using anonymous keys which were being recreated every time the view was rebuilt. The solution was to just make sure we reused the same keys when rebuilding the layout.

for (var i = 0; i < _contacts.length; i++) {
   var contact = _contacts[i];
   var contactKey = _contactKeys[i];
   items.add(ContactForm(
     contact: contact,
     key: contactKey,
     onRemovePressed: (key) => _onRemovePressed(key),
   ));
 }

When the user clicks save the master page can use the keys to generate a new client from a combination of the existing client and the changes in the form. We need to check if the state is null in case the user clicks save without first viewing the second tab.

 ClientEntity client = ClientEntity(
   name: clientState?.name ?? _client.name,
   contacts: contactsState?.getContacts() ?? _client.contacts,
 );

There’s one caveat, as of right now I haven’t been able to make this work with more than two tabs. I believe it’s a bug, I’ve opened a GitHub issue for it and hope it will be resolved before our app launches 🙂

Hope you found this post useful, if you have any suggestions to improve the code please let me know.

 

Building a (large) Flutter app with Redux

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.

I should preface this post by clarifying that I am in no way an expert in Flutter or Redux, this is my first app with either. I’m just sharing what’s working for us, if you have any corrections/suggestions they are greatly appreciated.

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.

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

Flutter is Dart’s Killer App

This past week we started using Google Flutter to build a new mobile app for Invoice Ninja, I thought it may be helpful to share some of the things we’ve learned early on.

What is Flutter?

At a high level it enables building high quality mobile apps for iOS and Android using a single codebase. While this has been possible for a while now with frameworks like React Native the catch is they use a JavaScript bridge which can impact performance. Flutter compiles to native ARM code, in theory it could be considered ‘more native‘ than a native Android app which compiles to JVM bytecode.

For the record, this isn’t the first time I’ve fallen hard for a new Google technology (anyone remember GWT). That said Flutter is receiving glowing praise from the developer community, myself included.

Here are some key points to be aware of:

  • It’s completely open source, you can drill down into the libraries to understand how they work and debug problems.
  • Everything is a widget, the architectural patterns are very similar to Adobe Flex.
  • It’s becoming very popular very fast.
  • It supports hot reloading which applies code changes in real time while retaining the application state.
  • It provides a comprehensive widget library.

Some of the drawbacks:

  • It’s very new, although Google claims it’s ready for production.
  • It requires learning Dart.
  • The app size will typically be larger than a native app.

Finally some of the reasons I believe Flutter will succeed:

  • ARM is the future of mobile/mobile is the future of computing.
  • JavaScript isn’t very good, or more accurately it wasn’t initially designed for how it’s being used.
  • Probably most importantly… it’s fun to use.

Setting up a Development Environment

The setup guides are good but I ran into a few problems. I initially started on Ubuntu Linux but in the end wasn’t able to get my test app to build, the details are here in case you have any ideas.

I then switched to my MacBook. After dealing with a Homebrew issue I was able to get it running. The last OS I tried was Windows which worked out of the box.

You can choose between two code editors: Android Studio (which is built on IntelliJ) or Microsoft’s VS Code. I’ve found VS Code runs faster however Android Studio provides a more seamless experience with better debugging tools.

A few points worth mentioning:

  • On first launch I had trouble with the Android virtual machine. By default it uses x86, I needed to download a 64bit version for it to run.
  • I initially started on the beta Flutter channel but after a short while I ran into an issue which forced me to change to the dev channel.

Learning Flutter/Dart

When I learn a new technology I’m a fan of reading a book cover to cover, sadly I wasn’t able to find any Flutter books on Amazon (yet). That said their docs are excellent and there are a fair number of tutorials online.

Here are some standout references.

I’d also strongly recommend installing the gallery app on your phone, it’s a great way to discover all of the available widgets.

Finally, it’s worth mentioning that Flutter recently switched from Dart to Dart 2. A new feature in Dart 2 is the ability to remove the ‘new’ keyword when creating objects, you’ll notice a lot of older examples still include it while newer examples don’t.

Deciding on an Architecture

Probably the biggest decisions you make when developing an app (once the technology stack has been chosen) is deciding on the architecture. It seems the main choice right now comes down to whether or not to use Redux.

While many developers love it there is definitely pushback from others who feel it requires too much boilerplate code. This article by the co-author of Redux does a great job detailing the pros/cons.

The latest advice from Google seems to be to use Streams and BLoCs (business logic components) to create reactive apps, that said it doesn’t preclude also using Redux. I think the co-author of the flutter_redux package explains it well on this Reddit post.

Redux is basically a StreamController sitting in an InheritedWidget at the root of your app. StoreConnector is just a wrapper for StreamBuilder with some helper functionality.

Note: the comment will probably make more sense once you’ve learned what any of those words mean.

Starting to Code

At this point I’ve only been writing Dart/Flutter for a few days but I think I have a reasonable sense for the possibilities it enables.

Live reload is amazing but I should point out it doesn’t always work, it’s useful when fine tuning the style of a screen but can fail when making large changes to the codebase.

And finally, back to Dart. I remember first hearing about it and thinking it looked cool but at the time I had no reason to use it, with Flutter that’s no longer the case.

Using a foot pedal for ctrl and alt modifiers on Ubuntu

I’m a big fan of my DataHand (or one of these) but overtime I’ve found the Ctrl and Alt modifiers a bit uncomfortable.

One of my favorite hotkeys is to use the ctrl key with left or right to move between words but that has me regularly holding down the key.

The solution I’ve found is to use a USB foot pedal, here are the steps to set it up on Ubuntu.

First use evtest to determine the /dev/input/event number and the button codes

evtest
/dev/input/event16: OLYMPUS CORPORATION HID FootSwitch RS Series
Select the device event number [0-16]:
Event: time 1456855230.136267, -------------- SYN_REPORT ------------
Event: time 1456855230.528267, type 4 (EV_MSC), code 4 (MSC_SCAN), value 90011
Event: time 1456855230.528267, type 1 (EV_KEY), code 272 (BTN_LEFT), value 0
Event: time 1456855230.528267, -------------- SYN_REPORT ------------
Event: time 1456855230.872269, type 4 (EV_MSC), code 4 (MSC_SCAN), value 90012
Event: time 1456855230.872269, type 1 (EV_KEY), code 273 (BTN_RIGHT), value 1

Then use evrouter to map the buttons by creating a file called ~/.evrouterrc with the following contents (you’d likely need to update the values).

"" "" any key/272 "XKey/Control_L"
"" "" any key/273 "XKey/Alt_L"

And then run evrouter

evrouter /dev/input/event16

Sites to help predict future trends

One of the challenges working as a programmer is keeping up with new technologies. Although many appear promising if you decide to spend the time to learn something new it needs to pay off in the long run.

There are two sites I primarily use to help decide, I consider both to be good leading indicators of the future success of a language/framework/library/etc.

All technologies will eventually go out of date, the challenge is picking ones that will hang around for a while.

Food For Thought

This post may seem a bit out of place on the blog and I guess that’s because it is. In a nutshell: I got sick, I learned how to eat and I got better. These are my current thoughts on food and nutrition. We’re all different, as always do what’s best for your snowflake of a body.

There’s always talk of low carb or high protein or zero fat or … but they’re missing the point. It’s not one versus the other it’s which of each.

We’ll start with fats. Coconut oil for high heat cooking and olive oil for low heat cooking. We’re starting to accept the myth that saturated fats are bad was just wrong. They’re stable and hold up well to high heat, if your oil is smoking throw it out.

As a questionable runner up I’ll mention Canola oil. It’s low in Omega-6 fats but it’s heavily processed. You get olive oil by squeezing the olives, the process for canola oil is a bit more complicated.

For protein, meat’s important but most of us eat too much of it. Organic makes a lot more sense here as you’re higher up the food chain but it can get expensive.

We’ve been ingrained with the belief that processed foods are bad, but the positive approach is to aim for high nutritional density. Kale gets it’s great reputation because it’s at the top when compared this way.

Eggs got a bad wrap, they truly are a super food. Most of the cholesterol in our blood is made by our body. A far more important indicator is the ratio between the types and densities of cholesterol.

I can always judge how well I’m eating when I’m standing at the checkout lane at the supermarket. The basic idea with shopping is to mostly buy from the perimeter of the store.

What we don’t see when we buy our food is all of the bugs in it. We’ve become a germ phobic society but only 10 percent of the cells in the body are human. I like the quote that antibiotics were the science of the 20th century while probiotics will be the science of the 21st century.

It’s good to eat your vegetables with meals raw, cooked and fermented. Raw sauerkraut and pickles are a great source of natural probiotics.

With fiber it’s important to note the difference between soluble and insoluble fiber, the former slowing you down while the latter speeds you up. Psyllium seed powder is a great natural form of soluble fiber.

Two common food triggers to watch out for are gluten and dairy. The best way to check for a sensitivity is to eliminate it for 30 days and then add it back for a few days in a row.

The trouble with gluten is more accurately an issue with Zonulin (sounds like a bad guy from a Superman comic) which can loosen the gut lining.

Most people can digest dairy when younger but many lose the ability as they get older. It’s akin to losing our hearing as we get older but digestive health and genetics plays a role.

Bone broth soup is one of the single best foods you can eat. Some frozen veg (brocoli, colifower, carrots, etc) boiled in broth and then pureed with a hand mixer makes a great soup. Maybe add a bit of coconut milk and some salt & pepper.