Lessons learnt from first Flutter app

We have been writing mobile apps since 2002 starting with Symbian, then J2ME and eventually through to Android and iOS. As seasoned Android and Apple mobile developers, we've recently released our first dual platform app written using Flutter.

We've assumed you know a little about Flutter and Dart, but you haven't started coding in anger yet.
However, if you're new to all this, then here's an overview from the Flutter team:
https://flutter.dev/docs/resources/technical-overview

Here's a summary of what we found - the good, the bad and the ugly.

Game in action

TL;DR

Although there have been several solutions to the 'dual platform' problem (Titianium, Xamarin, etc) they all come with their own problems and challenges - like cost, having to learn yet another language, and so on.
Flutter is different - it's free, it's supported by Google, it seems to work and it's maturing.

Flutter is excellent for casual apps and games that are light on animation - you write once, and release twice. It's not suitable for 2D and 3D games, there are better existing solutions for those.

You will lose some development time learning the new Dart language and Flutter APIs, but we think you'll gain that all back in subsequent projects.

We expect to save 50% development time on future apps (and we'll let you know in the next article).

Oh our Flutter app can be found here:
Android:
https://play.google.com/store/apps/details?id=com.teazel.wordslide
Apple:
https://apps.apple.com/us/app/wordslide-puzzles/id1515040671

Promo page

Why we took the Flutter & Dart plunge

We've done a lot of dual development on Android and iOS - more than 10 years, and it wears a bit thin having to do everything twice.

Seriously, you wouldn't wish it on your worst enemy.

Our usual process is:

  • check both platforms have the technical capability, especially if we're using a custom feature (zooming of very large areas, heavy bitmap work, etc)
  • sketch out the screenflows, and payment / advert points
  • code the core 60% of the app on one platform (typically Android), checking performance and app size are both acceptable, and confirming the screen flows
  • having confirmed one platform is usable, we temporarily park the first build, start on the second build (iOS), and repeat the core 60% build to prove any custom funkiness is still possible and usable
  • final steps are then to finish both, and ideally release both simultaneously. We try to avoid releasing separately because apps always have a 'Share' feature, and that falls flat if your Android fan forwards the link to their iOS friend - and a sale is needlessly lost.
  • then create a microsite (https://www.learncrypticcrosswords.com/) or holding page, and do some advertising to stimulate downloads and interest

We use Java, Objective-C and Swift to develop. We haven't yet committed to Kotlin.

Our first steps in Flutter

User interface (UI)

The first thing we noticed is that creating any UI is, superficially, easy. And 'Hot start' lets you rebuild your UI almost instantly. Nice.

That's the tempter to come into the water, so to speak. Look how simple it is to code Flutter. Not strictly true, as we'll see.

Even app-wide Material theming is offered with one or two lines of code, as is landscape orientation. Again, both are very welcome.

The second thing we noticed was the APK size - 14MB minimum fully compressed for release. Our apps are usually 4-5MB. Ouch!

However, some modern web pages are 14MB with their massive images, so we decided we could live with that. (My kids have got into The Simpsons™: Tapped Out, a mere 60mb download and then subsequent download of 1.8gb!)

So, if you want simple standard UI elements, it's easy.
If you want simple animations of widgets that's also very easy, and there are some nice animation libraries out there.
However, full-on gaming, whether 2D or 3D, is not really Flutter's bag. You're better off using one of the gaming-specific systems like Unity or Unreal.

Now - let's see how to get data and state into the UI.

State

On the first look, the StatefulWidget looks slightly odd, but usable. Make your Widget stateful, and then be sure to make any data changes happen within a setState() call, and that guarantees to redraw the widget in the next frame.

Many example projects and snippets have a button press changing some data, within a setState() causing the screen contents to update. Again easy.

But how about changing content without a button press. Not quite so straightforward. Now we're into the world of driving widgets to be updated by events. But how do you deliver such events?

Using Streams. Now it starts to look rather more complex. Everything is a widget, and yet we have events flying around - which aren't really widgets.

Unfortunately we didn't notice Streams until quite late in the development so went for an even more simplistic event emitter approach which worked well, after an initial few days of hiccups, but we will use Streams next time. For us this exposed the downside of just diving in without having read fully around the documentation / ecosystem - saying that getting started is always the hardest part.

Limited libraries currently, but good

Flutter and Dart libraries are available at pub.dev. There's a reasonable selection, but not nearly as many as Java and Swift on Github, but enough for casual apps and games.

We used libraries for I18N, animation, encryption, Firebase configuration, Admob, Crashlytics, a very sweet configurable calendar, an app rater, and so on.

And the beauty there is, we test on Android, flip over to an Apple device, and test again and it works. No hassle.

For example adding encryption and Firebase remote configuration was less than an evening's work (we have day time jobs), and both worked flawlessly on each platform.

The libraries on pub.dev are indispensable, and they all came with good, simple straightforward examples.

Areas that did cause us some grief was:

  • getting a decent splash to show on both platforms (see below)
  • having too many redraws of the UI from over-use of setState()
  • inconsistent behaviour of iOS devices not sleeping
  • the final step of releasing (also see below)

We also had times where you did have to dig into the Xcode (and Android) and resolve some specific issues. This wasn't too bad as we had the existing knowledge but I wonder what this would be like to someone who was fresh to either platform.

Dart - the language of Flutter

We have over 40 years of coding experience between us covering: C/Java/Swift/Objective-C/Node-JS and some limited Python. After a while the syntax of a given language isn't the significant thing more it's approach to handling things.

Dart relishes a few of the features of a functional language async/futures/passing functions. This is slightly different to our typical Android/iOS dev but not a million miles away. For Javascript developers out there it'll all be very familiar.

In general we like the expressive nature of the language and got into the rhythm of it all.

Release problems

Having worked through the development, we got to release time.
This part of the process was not quite as seamless, and that's simply because you have to connect into the 'real' world of the Android and Apple environments and tools.

For existing mobile developers you'll know the two sets of tools and the various gotchas. Newcomers, however, may well be scratching their heads at this point. Here's a flavour of the sort of things we found.

Splash screens can be generated using a library plus the YAML file to run a script.
However, if you do that it's quite tricky to find and undo the changes the script made, if you decide maybe you didn't want it after all!

There's also a script on pub.dev for creating icons, but the result was so-so. It mostly worked for Apple (bar some missing image sizes), but the Android icons were old school.

The latest Android app icon format (with separate foreground and background) wasn't supported, and it's not even offered as a tool in Flutter. Therefore you have to nip off to a standard Java/Kotlin project, use the Studio icon generator there, and then copy the resulting res folders back into the Flutter project. Not buttery smooth.

Having said that, the other Android changes were very small since Flutter is a Google tool. We had to manually add some config to use a new key file to do final signing.

On the Apple side, it's understandably a little clunkier. Once you're ready to release, you need to move over and run Xcode to do that. It's initially easy since Android Studio creates you an .xcworkspace file, which you can just open and run. However, you also need to dip into the Xcode configuration if you use external tools like Firebase. Flutter cannot seem to place those into the Xcode config.

You'll also need to heed the various warnings to update your pod files before building. You may also need to override the minimum iOS level supported manually.

Other points:

You can hook Swift and Kotlin into Dart, but not Java. But you can go the other way - integrate a Flutter module into Java. But that's only good for Android devices, of course.

You can't name project the same as a dependency ( a new project called 'confetti', for example, can't use the confetti plug-in from pub.spec). There's probably a way to resolve it, but just avoiding project v. lib name clashes is simplest fix.

You can build both platforms on a Mac, but you can't build iOS on Windows/Linux. But this limitation exists for native dev anyway.

Nice shortcuts for instant code snippet creation:

  • stless - creates stateless widget
  • stful - creates stateful widget
  • stanim

Errors can be quite verbose. Example: a clash of Container 'decoration' and 'color' (you can't have both) chucks out about 300 lines of dross on the console.

However, on the device screen it gives you a short failure description. But that may not happen on all errors.

Flutter doctor is your friend!

Limitations

Less examples than Swift & Java as noted above

A custom font should be specified if absolute font consistency is required for your application. (https://api.flutter.dev/flutter/painting/TextStyle-class.html)

Flutter does not currently implement all of the tablet-specific adaptations recommended by Material Design, though we are planning further investment in this area. (https://flutter.dev/docs/resources/faq)

Today Flutter doesn't support for 3D via OpenGL ES or similar. We have long-term plans to expose an optimised 3D API, but right now we're focused on 2D. (https://flutter.dev/docs/resources/faq)

Long-term Stability

Apple could make it difficult for Flutter to work/deploy to iOS, if they felt control of the platform was being lost to Google (i.e. sales would have to be affected). Seems unlikely though.
Or possibly stymie the Cupertino look & feel that Flutter uses (copyright issues, etc).

What happens on iOS updates? How long before Flutter is updated too? Not a problem with Android, of course.

Summary

For utility apps and casual games Flutter works well.

On your first project you won't halve your development time, because of the new language and features, but you'll probably still complete it quicker than you could do two lots of native development.

The real win is on the subsequent projects. So we're now off to create our second Flutter app!

Please do try out our Flutter app it can be found here:
Android:
https://play.google.com/store/apps/details?id=com.teazel.wordslide
Apple:
https://apps.apple.com/us/app/wordslide-puzzles/id1515040671