Take two of the production-quality Flutter starter app after nearly seven months of tremendous progress in the Flutter community. The starter app has been updated to the latest Flutter releases, best practices, and improved in many aspects.
Update Mar 6, 2020: The first_app is now available in Google Play store and as an iOS beta!
When I open-sourced my work in July 2019 as a result of an investigation into the state of the Flutter tools and eco-system for production-quality mobile apps, I also decided to write an article about it and share it on Medium. I had not expected the overwhelming response and, as of today, the nearly 7,000 reads, 63 stars, and 22 forks. It must have filled a gap in people’s desire to learn more about Flutter.
I typically tinker more over holidays, so in mid-December I decided to do an overhaul of the starter app as I had a number of things I wanted to improve and update. First of all, I updated the app to use the latest Flutter version, 1.12.13+hotfix.5 (Dec 10, 2019), as well as update the app to the latest Android SDKs, Xcode, and tooling. Many of the packages I had used also had newer versions. I learned that keeping track of newer versions is important as the amount of work to update everything after a while can be daunting. Indeed is Flutter a fairly new and rapidly changing project, but Android and iOS are also changing rapidly, so to upgrade everything and make sure that everything worked properly was a few hours of work. I also realised that some of the packages I had relied on, like flutter_crashlytics, were no longer maintained and new packages had showed up that were maintained better (firebase_crashlytics).
One of the painful things about Flutter is the fact that Flutter is an overlay and framework on top of Android and iOS (and other platforms now), which means that a Flutter project is really three different projects where the Flutter project includes tooling that updates the underlying Android and iOS apps. When you build an app, you really build the Android and the iOS projects separately, and when you want to upgrade these underlying projects, you can use flutter create .
to update the project files to the latest templates. My experience was that this didn’t work smoothly, probably because I had to tinker with the platform projects to introduce auth0, crashlytics, as well as Google Maps. Upgrading to AndroidX, the new 29 API, and the latest Xcode updates were not painless as I don’t have any experience with the platform-specific technologies.
Internationalisation is important to get right in larger projects from day 1. The IntelliJ/Android Studio plugin and package flutter_i18n still seemed to be maintained, and a newer version resolved the issue with lack of default language for countries with multiple languages (beyond English). Another thing that is important to have established early in a project with multiple developers and UI/UX people, is the templating/themes for the UI. I thus added a more complete theme that I generated from https://rxlabz.github.io/panache_web/.
In the first version of the app, I was primarily interested in the “plumbing” of making a production-quality app, so I sort of ignored the UI. I now put some effort into making a half-decent UI, and below you can see the updated looks. The new theme is cleaner, and although the UI is fairly simple, it is now more pleasant and can more easily be extended and changed.
I also wanted to test out the use of Google Maps, and in the process of showing a map, I added a new widget type as a library widget in the ui/widget/ folder. I found the AnchoredOverlay widget that I used for showing the Google map shared by one of the Flutter maintainers. As the new locations flow in, the map overlay is updated to show the map of the latest coordinates.
I also did several cleanups throughout the app. It turned out that I didn’t have the Podfile under version control (guess nobody built the iOS version…) and my .gitignore missed a few files. Also, I had hard-coded includes of files several places, something I learned was not best practice (you should include from your app package, not from the file system). My “keep-it-dense” C origin showed up and made me get rid of trailing commas, something I learned the Dart formatting relies on to get the widget code formatted nicely, so I added back the commas 🙂
The flutter-auth0 package was not released as an official package back in July, 2019, and I sort of maintained the repo while the original developer was busy elsewhere. The effort showed an interest from folks to get the package released, and Denny Segura luckily re-engaged and got it released. I thus replaced the use of a local package with the released version and updated the code to use the new flows and methods.
One of the things I was not happy with in the original starter app was testing. I had some unit tests, including some mocking using mockito, but I had no widget tests, and I really wanted to have a look at the integration test capabilities of Flutter. I thus put some effort into improving the mocking capabilities, and introduced a map of mock objects into the main appstate, thus making it super-simple to leverage the state updates through the widget tree to turn on and off mocking. I haven’t seen this pattern before, so I would be really happy to get feedback on how wise that approach is!
The added benefit of these mocking capabilities was that I was able to use the flutter_driver and add proper integration tests by leveraging the inter-process communication between the observatory (the instrumented app) and the test process. This fully integrated testing on a simulator as part of the Flutter framework is super-nice! The two main drawbacks are that the tests need to be written in Dart and that the Flutter driver cannot interact with system dialogs (yet). The first problem is a process and people problem: Either you need the developers to develop the integration/UI tests as part of their development work (should be feasible once it’s set up like I have) or you need your QA people to use Dart. They probably already know Python, so they should not have a problem with Dart. The lack of interaction with the system dialogs caused some problems until I created the mock pattern mentioned above for libraries that interact with external entities. Hopefully, this is something that will be possible to add for the Flutter team.
A side note on the flutter_driver: The documented way to run flutter drive is really cumbersome as it always starts the observatory (instrumented) app and then run the tests. See the app’s README for how you can run the instrumented app in hot reload mode, while re-running your tests separately. Thanks to Maurice McCabe for explaining how to do this!
One of the hot topics of the Flutter community is state management. In July, there was no clear winner, and after evaluating several state management approaches, I used my experience (and desire for clean and simple architectural patterns in larger teams) and chose scoped_model. Later, state management has become a bit more nuanced. When I did my research starting about a year ago, all the state management solutions solved two problems: 1. how to put state data into buckets and cleanly separate different type of data into different buckets, so that state change notifications did not lead to repainting of all widgets, but only the widgets that used the data that changed, and 2. how to actually propagate changes throughout the widget hierarchy and make these buckets of data state available to the right widgets.
With the refresh of my app, I was curious to understand provider, and I wanted to replace scoped_model with provider for state management as a talk at Google I/O quickly made provider the hottest (and recommended) approach to state management. As scoped_model and provider were built by the same people (guy), I was able to quickly wire up provider and replace scoped_model within 30 minutes. However, that was when I understood that while scoped_model solved both of the two problems above, provider is really made to solve problem #2 above, i.e. how to manage state data distribution throughout the widget tree (and life cycle). First, I found it really cumbersome that I had to make explicit choices whether I wanted to listen to changes or only access the methods of the state objects. But, when the dime suddenly fell down, and I realised that provider is a much more powerful tool and that it forces you to do the right thing, I started looking at problem #1 again. With scoped_model, I did a very simple thing and made a single bucket of state data, called appstate. With provider, I was forced to look again at this design because I needed to refresh a widget when there was a new GPS location available, but I only needed to get access to the global appstate to run a method in that same widget (and not refresh the widget).
As a result, I split out a new locstate state model, thus more cleanly separate the location data from the global appstate. If you look at the models (lib/models/) in the app, you will see that they are very simple. They are basically basic classes with a ChangeNotifier mixin: class AppStateModel with ChangeNotifier{}
. This gives me the notifyListeners() method, the only thing needed to notify about a state change. In the updated starter app, I have on purpose NOT introduced a state management system to solve problem #1, but used provider to propagate the state properly.
In my research, MobX came up as a state management solution that plays really well with provider and where you can structure your state data (“stores”) in much more advanced ways than what I have done in the starter app.
In line with the original app and my desire to make it ready out of the box, I have committed the Google Maps API keys to the repo (though throttled the quotas), and I have also set up Codemagic to build the latest version of the app. If you want to install on Android, you can use the app-release.apk file. Unfortunately, it is non-trivial to build something you can install on iPhone, but you can easily build it yourself and run it in a simulator.
I hope this update to the starter app is useful and…
[…] original article can be found here, and here is an article describing the updates (included in this […]