An architectural review of the Invoice Ninja Flutter app

We’ve been working on our Flutter mobile app for a few months now, I thought it may helpful to share some of the techniques we’re using to help keep our code maintainable.

OpenSourcedResized

Create widgets!

Keep your code DRY (don’t repeat yourself). Refactoring widgets is just like refactoring standard code, look for patterns of duplicate code and refactor it out to a widget. Widgets can be incredibly small but if used throughout the app they both reduce code and make it easier to apply changes later on.

For example we created an ElevatedButton widget which wraps a standard RaisedButton class so it always has a consistent elevation and enables setting the color or applying an icon. Another example is this IconText widget which just makes it easier to show an icon and text together.

Wrapping/extending widgets is a great way to customize the framework to suit your needs. We try to follow the convention where the name of the widget is the concatenation of the child widgets it combines.

View models

One of the core principles of software architecture is SRP (Single Responsibility Principle). As the views get more complicated a great way to implement separation of concerns is to create a view model which backs the view.

This enables the view to focus on the layout of the UI while the view models manage the view logic. For example, the view model would handle preparing data (ie, caching using memoize) from the central store to the format needed by the view and provide methods to dispatch actions to update the store.

Completers

In the app it’s common for the user to trigger an action in the UI which depends on completing a successful request on the server. For example, saving a new record.

Initially we passed the context in the action and had the middleware use the context, however we’ve found using completers provides much cleaner code in the UI layer.

We use a utility class to create common completer types. For example. a snackBarCompleter will show a SnackBar message if the request completes successfully or a modal ErrorDialog if the request fails.

final completer = snackBarCompleter(context, localization.archivedProduct)
store.dispatch(ArchiveProductRequest(product.id, completer));

There’s also a popCompleter which is used by actions dispatched from dialogs which will automatically close the dialog and return the response message in the call to pop().

Enums

Our app has many modules, for example: clients, products, invoices, tasks, expenses, … They all provide similar functionality (list, view, edit, archive, delete, restore) and then some provide other actions such as invoice or email.

To support this we use built_value enums. In this case we have an EntityType enum and an EntityAction enum. A nice aspect of this solution is it’s automatically serialized/deserialized when the state is persisted.

Our custom widgets then can accept an EntityType parameter which can be used to configure itself from the store. We’ve also added a lookup function in the AppLocalization class to help with translations.

Persistence

Our solution to persistence is to split up the core parts of the store (data, UI and auth) to persist each part separately. As a user makes changes to a new record we can constantly persist the UI store without needing to persist the data store which could potentially have tens of thousands or records.

If a user starts to create a new record and then quits the app we’re able to present the partially completed record when the app is relaunched. This is handled in the code by having any action which requires persistence (such as when data is loaded or modified) implement the PersistData or PersistUI abstract classes.

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

Abstract classes

We have two types of data in the app: editable entities which the user can create and edit (such as clients and invoices) and static data which can be referenced but not changed (such as the list of languages or currencies).

To support typical interactions in the app (ie, selecting a choice from a list) we’ve created a SelectableEntity abstract class. The built_value classes then implement the class. This provide a way for each entity type to define how it should be presented and searched in a list, we use the subtitle of the ListTile to show the matching field. This class is implemented by both types of data.

The editable entities implement the BaseEntity class which provides shared functionality such as archiving and deleting and handles filtering the list by their state (active, archived or deleted) and status (ie, draft, sent, paid, …).

Clean code

If you’re just getting started I’d highly recommend using a more comprehensive analysis_options.yaml file. Our approach was to start with the Flutter project’s file and comment out if needed. This can be harder to change in an existing app as it can generate thousands of new warnings.

Hope you found this useful, if anything’s unclear or can be improved please let me know. You can follow my thoughts on Flutter on my Twitter feed or subscribe to the blog for more posts. Thanks for reading!

Flutter API Docs Sorted by Length

It goes without saying that one of the best sources for information about Flutter are the API docs. Many of the widgets provide detailed explanations with useful samples.

If you have a specific widget you’re trying to use the docs are extremely helpful but if you’re new to Flutter and just want to do a bit of reading up it can be hard to find some of this great content. So of course as a developer I wrote a script…

The following list shows the widgets included in the material library sorted in reverse order by length of the explanation text.

 

Follow me on Twitter for more post related to Flutter.

flutter-logo-sharing

Dart for JavaScript Programmers

My perspective on Dart has quickly changed from being a language I needed to learn to build something with Flutter to a language I enjoy using on a daily basis.

dartlang-card

When starting with Flutter you often hear that Dart is just like JavaScript. Well, it is… except for when it isn’t. I thought it may be helpful to highlight some of the subtle ways in which Dart differs from JavaScript to make it easier to get up to speed when switching languages.

Conditions must have a static type of ‘bool’

In addition to types, Dart is in general a stricter language. In JavaScript you can use any ‘truthy’ value in a conditional. For example:

var name = 'Joe';
if (name) {
  // do something...

If you try the equivalent code in Dart you’d see “Conditions must have a static type of ‘bool'”. The reason is that Dart requires that a condition is a bool true not just a ‘truthy’ value. You could correct the code by changing it to:

if (name.length > 0)

But the preferred approach would be:

if (name.isNotEmpty)

Related to this, there is no triple equals (===) in Dart.

As a side note I saw this great tip on Twitter to use the same analysis_options.yaml as the Flutter team. It’s been a big help getting up to speed with Dart best practices in general.

Where is console.log

Although I’m constantly trying to train myself to rely on the debugger old habits die hard. In place of console.log you can use print. Dart supports string interpolation so whereas with JavaScript you may write:

console.log('Name is %s', name);

With Dart you’d use:

print('Name is $name');

If you need to access properties on the variable or call functions you can wrap it in curly braces.

print('Length is ${name.length}');

Function Parameters

This is one area where I think JavaScript and Dart are the most different. Dart provides a far more powerful implementation but it can take a bit of time to adjust to.

This answer on StackOverflow by Seth Ladd does a great job of explaining the differences in detail. At a high level with Dart you can either pass parameters in set positions:

getFullName('John', 'Doe');

Or you can pass them by name:

getFullName(firstName: 'John', lastName: 'Doe');

For constructors you can use this.fieldName to tell Dart that the value passed should be assigned to the property.

Contact(this.firstName);

Handling Arrays

Arrays are mainly the same but there are a few differences worth pointing out. A key difference is that you add an item to array by calling add rather than push.

Dart provides helper methods first and firstWhere which you may not be surprised to learn returns the first item in the array. What’s less obvious is that by default if a match isn’t found the methods will throw an error. You can handle this case by specifying a value for orElse which will be returned if no item is found.

Final and Const

This one took a while for it to sink in. I think this post does the best job explaining the differences that I’ve found. The key difference between final and const is that ‘final’ describes the variable whereas ‘const’ describe the value itself. A final variable can only be set once but the value it points to can be changed, a const’s value is frozen and can not be changed.

Another good tip you’ll get from using the Flutter analysis_options file is to use const constructors where possible when creating your widgets. This can have a great impact on your app’s performance by enabling the framework to cache the widgets.

Fat Arrow

The ‘Fat Arrow’ or => can be used for single line functions. For example, instead of:

someField: () {
  return true;
},

You could write:

someField: () => true,

It seems like a small difference but it can definitely have a positive impact on the readability of the code. That said in practice I find I sometimes replace a one line function with a fat arrow only to discover the function required more than one line forcing me to remove the fat arrow, but I guess it was nice while it lasted.

Odds & Ends

To wrap up here are a few other points worth keeping in mind.

  • If you’re using a double the value needs to have a decimal in it (ie, double value = 2.5) otherwise you’ll see “A value of type ‘int’ can’t be assigned to a variable of type ‘double'”
  • To convert a value (for example) to a double you can either use double.parse or double.tryParse. The former will throw an error if it fails whereas the latter will not.
  • You can use null-aware operators (ie contact?.firstName) to make it easier to handle null values. This post (also by Seth) does a great job explaining it in detail.

Hope this helps, if anything’s unclear please let me know and I’ll update it.

 

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.

 

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