In January, I created a post where I sang Flutter’s praises over Android native. Three months later, does this still hold true?
First, let me give you a brief background. I launched my app back in 2013 on Android and the app has naturally grown from there. Late in 2018, I decided to do a full rewrite of the app in Flutter (with two things in mind: make the app more future-proof and ability to expand to iOS). I soft-launched on iOS in mid-December and went onto Android at the end of December. Despite a few hiccups (such as forgotten or missed features!), it generally went okay.
As of today, only about 6% of my Android user base is still on the old Android native app. I have around 2700 active users a day, so not massive, but it is a well-used app. Almost 4 months of constant hammering by users should give me a good flavour of what it is like to run a Flutter app in production.
What is still good?
Apple (almost for free): Being able to launch my app on Apple is definitely worthwhile. Revenue and use isn’t exactly where I want it to be, but I’m on the platform, so that is 100% further ahead than I was before Flutter. Apart from the nuances in terms of the iOS UX, I haven’t experienced any difference between the two platforms. It is great to do all of my development on Android and it just works on Apple.
Rapid developement: I have managed to add several new features to my app (including some fairly complex new features) which I would never have been able to achieve so quickly in Android native. And I also got this on Apple, almost for free. It may also be a sign of how well I architected my app, but this is also because Flutter and Dart allows this.
Community: The Flutter community is big and growing exponentially. Help is always at hand and I see new tutorials and walkthroughs coming out daily.
What’s not so good?
Dependency hell: I have found myself stuck in some dependency hell. I have one library which hasn’t updated and it causes a bad domino effect. I thought it was easier to manage in Dart, but appears not so. It took a fair amount of effort to unpick bit by bit and find the offending library.
Incomplete Google features: Further to the dependency hell I mention above, Google’s own features are still incomplete. Their maps are not usable yet, so I’ve had to use another package called “flutter_maps”, which is currently causing my dependency hell.
More incomplete Google features: AdMob is not really incomplete, rather just incompatible with the way that Flutter is meant to work. The library hasn’t been updated since February and I have to use awkward workarounds. I’ve had a really good explanation by Andrew Brogdon after I raised the issue on a Flutter Youtube video, but it is still frustrating that we are where we are with this. Nowhere.
Null handling: I’m not sure if it recently changed or if I just didn’t notice before, but I preferred the way that Dart handled nulls in that it won’t fatally blow up and just carry on. I had some really bad issues recently where it would just stop running parts of my code when I accidently tried doing something with a null. No errors, no nothing. It would literally just skip the rest of the function without throwing anything. It took hours to unpick and I would honestly have preferred the app to just crash instead.
Instability: I started using the stable channel of Flutter, so that I could keep up to the latest versions of Flutter, but without too much risk of things going wrong. Unfortunately, a fairly bad regression issue has appeared and it is causing some frustration (mostly just that Samsung decided to suspend my app).
Other weird bugs: This issue has appeared most than 4000 times in my app for more than 350 users. It has proven impossible for me to reproduce, but I can see it happening. I’ve had a number of other weird issues also occur. A more stable framework would not have these sorts of issues.
Do I still prefer Flutter?
Absolutely, I still prefer Flutter. I know that on balance, it appears from the above lists that I have more complaints about Flutter than praise, but this is not accurate. I simply recognise the current flaws with Flutter, but I still believe that the future is bright. Using Flutter, I believe that I can keep on expanding my app and giving my users all of the great experiences they are after.
I’ve seen all of the hype around dark mode lately and I knew that at some point, I’d probably have to jump on that band wagon. But, it isn’t on my priority list as I’ve promised my users something else.
As I was wasting time yesterday, I saw a Youtube video about the dark mode craze and I thought, let’s give it a go – I needed a distraction anyway.
I previously pulled out my theme into a separate variable as I was playing around to make sure that my entire app respected the theme as far as possible, but my entire MaterialApp uses the above theme (it’s a bit redacted). In it’s basic form, all I had to do to change to dark mode, was to add the following into my theme:
And that was it. I had a look through my app and quickly found three or so instances where my app wasn’t respecting my theme. Generally, I pull the colours from the theme, rather than it being hard-coded, for example:
Color backgroundcolor = Theme.of(context).dialogBackgroundColor;
The worst offender was my search delegate and after a bit of playing around, I ended up with what feels like a slight workaround, it now does what it did before if the brightness is light (which actually doesn’t respect my theme too much, but it works and doesn’t change anything) and then it fully respects my theme if the brightness is dark:
So that was it for being able to switch between dark mode and light mode in code, now to do it in run-time.
Switching during run-time
The implementation was quite straight-forward (I’m using the bloc pattern). Firstly, I hooked up a new setting in my application preferences (this doesn’t deserve a code example as it won’t be necessarily helpful for others), but what I end up with is a stream in my bloc which emits true or false depending on whether light or dark mode was selected. This is what the declaration in my bloc looks like to give you an idea (I start with false just to make sure that we have a value):
BehaviorSubject<bool> _darkmodeController = BehaviorSubject<bool>(seedValue: false);
Sink<bool> get inDarkMode => _darkmodeController.sink;
Stream<bool> get outDarkMode => _darkmodeController.stream;
And then, the easiest part of all was hooking up the app (which I thought would be the most difficult) – btw I replaced irrelevant code with …:
And that is it. As I change from light to dark mode, Flutter takes care of the rest and even the transition looks really good.
I think I was worried as I don’t do too much setState, since I’m using bloc, but this just worked. It doesn’t break anything and it looks great.
This is why I love Flutter.
Here are some screenshots (in the interest of full disclosure, while I was looking some screens which would show light vs dark nicely, I found one other screen which doesn’t respect my theme – but it will be a quick and easy fix – test your apps properly!!).
There are two inaccuracies in my title: firstly, Flutter is Android native, but we will be talking about developing Android native apps using Java versus Flutter app in Dart; and secondly, one isn’t really better than the other, but in my circumstances, one approach was definitely better than the other.
A brief introduction
The important information about my background which you should know:
I published an app in 2013 and I think it has been moderately successful. Not a Candy Crush, but it hasn’t died yet and people are using it daily.
I rebuilt this app and it has feature parity with the old one – it does exactly the same thing the old one did (I know, because I’ve had users point out to me where I have forgotten how the “old” app worked). From their point of view, it just seems like I updated the look and feel a bit.
Development is a hobby rather than a job (although I work with development teams, but not as a developer). Java and Dart were both self-taught and never used in a professional environment for me. (I learned C++ at uni and have a long history with languages like BASIC, Pascal, C and Visual Basic).
So my qualification to make these statements come from building the same production app in both Java and Dart with limited experience. Let’s dive right into some comparisons:
Java vs Dart
Both languages are very similar and I feel fairly comfortable using both. I was a bit hesitant with learning Dart, but it felt completely natural after being quite used to Java (I’ve dabbled a bit with Python and PHP and I found it much easier to quickly adapt into Dart comparatively).
Doing asynchronous work in Java is a bit more of a hassle compared to Dart. Dart’s syntax makes it very easy (except that in Flutter, you build methods cannot use the asynchronous syntax).
I also got into Dart and Flutter when Dart 2.0 was out, so the language was cleaned up for Flutter, so no annoying “new”‘s everywhere.
And then, Dart either seems to handle nulls a bit better or I have burned myself enough in Java to be handling it better myself. But it feels like Flutter protects you a little bit as you don’t have a mini-explosion every time you forget to handle a null.
The only negative point for Dart, but this more a Flutter thing, is that you end up with lots of brackets everywhere – more than usual. And maybe quite a lot of commas too.
As a language, I prefer Dart as it is a bit more to the point and feels like it is made for what I want to do.
I spent a good number of weekends implementing material design at the start of 2015. It had only been announced in June the previous year, so it was still relatively new and there wasn’t too much help available in terms of libraries. I remember having to style everything by hand, measuring margins and constantly referencing the material design specs.
Flutter basically gives you material design out of the box. I actually hadn’t realised how “not material design” my app was until I rebuilt it using Flutter. (Of course, I don’t know if you now get some better help in Android native, but it felt really good that the design system I wanted to use was immediately available to me rather than having to sit and figure out margins, padding, shadows, rounding and so on, on my own.)
Getting a material design look in Flutter was effortless compared to doing the same thing in Java.
XML vs code
When you work with Java, a lot of layouts, strings and random values will reside in XML instead of code. This should make scaling with large teams easier as there is a separation of concerns and a designer can pick up the XML files and edit them without having to know code. As nice as this is, I don’t like it for a smaller project. And then, you end up having to reference the XML in code any way, and you always wonder why you can’t just do it all in code (maybe you can?). It felt like things got messy having to work in XML and then use LayoutInflater to build the things I just build in XML to start with.
On the flip-side, I miss one thing: Strings.xml. This allows for easy translation and just having everything nicely in one place. I have seen a way to do it in Flutter, but it didn’t immediately make sense to me. I never actually translated my app, so it’s probably over-engineered, but equally don’t feel too happy with the strings just sitting all over the place. (I should probably at least just create a static class to put all my strings in…but that’s for another day).
Because I definitely prefer being able to have complete control over my layouts, I prefer the Flutter approach where it is all in code. The XML approach would have been more useful for WYSIWYG editors, but they never worked too well for me and I didn’t feel completely in control. And the fact that it would try and display even the smallest layout in the editor. It was just odd.
The lack of WYSIWYG layout editor in Flutter is made up for by having hot reload. It can do some strange things at times (or in my app if I change things which are done right at start-up, hot reload doesn’t work), but it is great for making layout changes and being able to see it immediately. This is not available when using Java and slows down the process a bit.
Fragments vs widgets
I’m calling out fragments here, but it really should be, “Widgets vs the Android kitchen sink”. Everything in Flutter is a widget – this makes it very predictable on how it will work and easy to move around and plug in somewhere else. It took me minutes to take my existing layout for phones and adapt it for tablets where I wanted side-by-side views of two different screens.
Adapting my app for tablets in Java was a complete nightmare. Again, things might have changed, but I had to use fragments. I had two fragments and it just felt awkward trying to deal with it differently between tablets and mobiles. Because everything is a widget in Flutter – this sort of thing is quite straight-forward to achieve.
It’s worth pointing out, that even after all of that work with fragments, the app stopped working on tablets at some point and I just never had the courage to want to try and fix it again. Until Flutter came along…
I have three different flavours in both my set ups. The Flutter one would not have been as straight-forward to set up since I had to go and muck around in the Android code – but since I have done it before, it was pretty much copy and paste. If I was new to this, I can imagine it would have been pretty difficult to figure out as you have to change files you don’t normally deal with.
One of my flavours has a different applicationId which I can use in Flutter code to distinguish, but two of them don’t and I cannot easily and without some workarounds detect in code which flavour is which.
I would prefer it if there was an easier way to deal with flavours in Flutter, but it barely handles it at the moment.
For both Java and Dart, dependency management is quite straight-forward. Dart feels a little bit more light-weight and straight-forward – I have many more dependencies in my Java app for Android support libraries which I didn’t need in Dart (due to Flutter).
Due to not having to worry about the support libraries I prefer Flutter for dependency management, but I’m sure this might be why the APK’s are so big.
This was almost a deal breaker for me, and even though the production APK’s are quite a bit smaller than the debug ones, this is Flutter’s worst point for me by far.
The Java APK is 4.07MB and the Flutter one is 10.03MB.
Due to the large initial size of the Flutter APK (I started somewhere around 18MB), I had to be aggressive in terms of making images as small as possible, reducing the size of my static data database and be very careful with dependencies. Both APK’s are Proguarded/Minified. My Flutter APK is only that small due to a dedicated effort to try and get it as small as possible. If I put the same effort into the Java one, I’m sure I would get it to be a bit smaller.
Android Studio vs VS Code
I moved from Eclipse to Android Studio and liked it in the beginning, but it was quite heavy on the processing side. I’ve killed a laptop battery due to it and I’m fairly sure the heat Android Studio has generated in the past might have fried a few laptop components too. I now use a desktop, but Android Studio is still quite heavy-weight and it just feels too clunky.
Even though I use Android Studio from time to time due to wanting to use the layout inspector as an example, I prefer spending my time in VS code.
We have to touch on maturity here. Because Java Android has been around for years, you have access to tons of libraries and many resources. Getting stuck shouldn’t happen for long and you’ll be helped quite quickly. All the libraries have had years to stabilise and shouldn’t give you problems (well, you’d think so at least).
On the other hand, Flutter is still fairly new. I’ve had specific issues with libraries crashing my app because despite claiming to be version 1.1+, version 1 just meant it was their first publication. I’ve also had issues with libraries being incomplete. It’s worth pointing out that in both cases, I found suitable solid alternatives, so I haven’t been left with nothing.
This is probably set to get worse for now as many libraries are still experimental or beta. As these libraries are upgraded, I’m probably going to have to rewrite large chunks of code. (Although, my Java code is probably in that situation now as I haven’t upgraded libraries for about 6 months).
Neither app had a wearable version, but it was an option for me when I was working in Java. Flutter doesn’t support this yet, so if I had any plans to also have a wearable version, it isn’t currently possible.
I’ll mention it here only because it is also in the category of I don’t have it, but had the option and lost it – widgets. I believe that Flutter doesn’t yet support home screen widgets on Android, but this I cannot confirm.
Flutter’s best feature for me
So I’ve saved the best for last – Flutter gives me something I’ve wanted since 2013, but due to many issues, like time constraints, willingness and ability to learn, having to work on a Mac, I just never could build an iOS app. Flutter gave me this for free. Of course I had to set up a Mac in order to test it, but it just worked – pretty much out of the box with no code changes whatsoever (working is of course different to being usable – I’ve done a post on Flutter on iOS for Android developers about my lessons I’ve learned).
I’ve broken into a whole new market due to Flutter, so this is for me, the main reason that Flutter is better than using Java Android.
This is all my opinion based on my own use case, but hopefully you can find some useful bits in here if you are deciding whether to use Java for Android for Dart for Flutter. The checkboxes simply point out which is better at supporting the feature than the other – in most cases features are supported in both.
Dart & Flutter
Language – both languages are very similar, but Dart makes asynchronous development a bit easier and handles nulls better
Material design – I had to hand-craft material design when I worked in Java, it came out of the box with Flutter
XML strings – having Strings all in one place and an easy way to translate them was useful in Java. It’s possible in Dart and Flutter, but not without some hand-crafting.
XML layouts – I disliked having to manage my layouts separately in Java and prefer the control I get in Flutter.
Hot reload – only available in Flutter and turns your emulator into an almost WYSIWYG layout editor
Widgets – everything being a widget in Flutter simplifies things a lot and makes it clear and predictable how they will interact with other widgets
Flavours – this isn’t completely baked in, but supported by Flutter. I would prefer the ability to know programatticaly which flavour is being used and not have to fiddle around the Android source to set it up.
Dependency management – pubspec.yaml feels more lightweight than a build.gradle and I don’t have to worry about all the support libraries just to build an app
APK size – based on what you get for it, I feel that the Flutter APK is probably still too bloated
Android Studio and VS code. I prefer VS code as it is more light-weight and doesn’t try and cook my processor, although I switch to Android Studio from time-to-time for things like the layout inspector.
Maturity – it would be unfair to say that Flutter is incomplete, because it is possible to build a production-ready app with it, but some things will be a bit harder to do due to it still being relatively new.
Wearables – not supported in Flutter yet, but I expect it will happen if wearables are still popular in a couple of year’s time.
Ability to build for iOS – this is the winning point for me about Flutter. I no longer have to fear that I’ll have to learn Swift or Objective-C. It makes me sleep better at night.
I’m previously an Android developer who got started with Flutter and rewrote my Android app in Flutter to release it on iOS. During this process, I learnt a few things about implementing material design in iOS and generally things you might want to think about as an Android developer when you also work on iOS. (I flip-flopped between using Flutter or material design in the title as this post is about a bit of both).
As an Android user and a bit of a purist, I get pretty annoyed when I use an app and it looks like an iOS app. I want developers to respect the Android platform. In comes material design and this changes the thinking, material design is supposedly cross-platform and apparently should be fine for iOS too. Ok, so Google said that is the case and I believed them.
I took my Android app and developed it for Android and at the last minute, picked up the MacBook and built an iOS app from it. It surprisingly just worked! I put in no special code for iOS (the only exception for platform specific code was a check I had to do in order to support upgrading from my native Android app to my Flutter app (which I know is still native!)).
Never look back
When I ran the app on the Apple simulator, I realised that there wasn’t really an easy way to navigate back in the app. Since I used the Bloc pattern and I had a burger menu, I rely on the Android or hardware back button in order to go back in my app. I wrongly assumed (and I’ve had an iPhone before, so I realise how naive this was) that since Apple users don’t have back buttons, they think differently about navigation and would probably just find a different way of navigating to the thing they saw two screens ago.
I showed my app to a colleague on his iPhone and his very first reaction was – “how do I go back” – he was looking for a way to go back in the top left hand corner of the screen.
Luckily, since I was already handling the back navigation using the Bloc pattern, I could just provide a button on the page that would do the same thing and help the user to navigate back.
When I investigated “back navigation” in iOS, a pattern I noticed is that you generally want to see context of what you are navigating back to, rather than just “back”. So I also had to start keeping track of that too.
In the screenshot I did, I realised that my app bar is looking a bit crammed, but the general idea is there. I had to add in back navigation, since there would be no other way to achieve this.
The other thing I had to do for Apple devices, is to shift the burger menu to the right, since the left is now taken up with the back navigation. I have seen examples where the back navigation and burger menu would be below each other, but I chose to just have it on the right hand side instead for Apple devices.
Back ≠ exit
On Android devices, once nothing is left on the back stack, you just exit the app. This is not a pattern followed on Apple and you should not just quit the app. At the point that nothing is left on the back stack, I just remove the navigation button.
No transparency with Apple
A bit of an issue I had was with my launcher icons and also with my screenshots for the app stores. You are not allowed any transparency in these images. In hindsight, it feels like good practice, but it’s annoying when you have made a bunch of icons and a bunch of screenshots and have to redo them.
Build is different
It is worth mentioning here. Building an app for iOS is a bit different and there is a bit of a process. I had to get my hands on a MacBook and had to do a few weird things with certificates (I really have no idea what exactly I did!). I follow a fairly mechanical process in getting a build done and Xcode also automatically submits your app to the store. In there the process is also a bit different (and slightly slow).
It was a whole lot easier getting an iOS app out than I ever thought it would be. Apart from a few minor styling changes and a change in the way my users navigate back in the app, the app works the same on both platforms. I was really surprised at how easy it was – especially since I told my users about 6 months ago that I’m unlikely to ever give them an iOS app.
Today was spent finally implementing something I have been meaning to do for probably years now. Up until now, there just wasn’t a way to reorder your favourites – probably the only control you could have would be to add them in exactly the right order.
I kept flip-flopping between doing it directly on the favourites screen, or doing it in the settings screen. My opinion is that it would feel more natural to just take a stop on the main screen and drag it to the position you want it to be. That is, until you realise that some users have 10 or 20 favourites. It would be very difficult to organise it – especially if you want to drag something all the way from the one end to the other.
One other thing I have to think about was the potential of breaking the favourites screen to add this. You wouldn’t want to re-order favourites all the time. So I settled for the idea to do it in the settings screen. It also gave me the chance to quickly reorganise the settings screen a bit.
I had the plan to use chips (if you don’t know what they are, there is a screenshot below) and allow users to reorder using them. I’ve never used them before and I had haven’t used drag and drop in Flutter before either. It took a couple of hours due to a silly little issue which had me stumped (thank you Stackoverflow – https://stackoverflow.com/questions/53585711/dragtarget-onwillaccept-and-onaccept-not-firing/53621273#comment94954839_53621273). At one point during the struggle, I was going to give up and use lists instead and maybe even “move up/down” buttons, rather than fancy drag and drop, but I wanted to have my chips! I persisted and got it to work.
It worked okay on the simulator, but it felt a little bit fiddly. I got it to work really well and then decided to test it on a real phone. I’m not happy. It is pretty awful. It’s difficult to get the chips to land where they should and the chips are much bigger on the real phone, which meant that each one was on its own line any way. This would not really help a user very much and probably frustrate them.
I’m going to do it over again – I’ll probably just use a list view with up and down buttons. It is unfortunate, because I liked the look of the chips in general, but it’s just not practical.
As I’ve just published a fourth major revision of my app, I thought I would relive the history of the app just to see how far it’s come:
Version 1 – the prototype
This version was first published on 6 September 2013, version 1.0.1 (incorrectly numbered 1.01) followed 2 hours later and version 1.0.2 followed 2 days later.
Opening version 1 with no nearby stops (as I no longer live in London) just shows a blank screen. I see no options and I clearly deployed a version with no ads enabled!
Version 1.1.7 was the final instalment of version 1 and went live on 28 December 2013. When I now open the app, I see an error (as I am not anywhere near London), but it is quite generic and could really be anything.
Improvements I can see is a loading indicator, the search bar looks much better and a new map icon which appeared. The map views have always annoyed me as I haven’t given it too much attention. Something else which is quite obvious is that the lines are not high enough for a touch device.
Version 2 – going native
The first release of version 2 was in October 2014. I rewrote the entire app to get rid of Google Web Toolkit and all the callbacks between basically a front-end and back-end.
Opening version 2.0.0, it looks very much like a minor change from version 1.1.7 (except I know it was a substantial change!). The one thing which is obvious is that the listview tiles have become even tighter.
Fast forwarding to version 2.1.3, the list view is still very “tight”. The changes I can see from version 2.0.0 are: I can now search for specific bus stops by name and we now have some settings. In the settings I can see that by this time I changed my implementation to allow more granular view of nearby buses (showing in 15 seconds intervals rather than seeing “due” for 90 seconds, which can feel like forever if you are waiting for a bus!).
Version 3 – material design
Version 3.0.0 was released only a week after 2.1.3, but I had been working on it for quite some time. This version was all about material design and it was fairly cutting edge, as material design was only announced in June of the previous year. The design hasn’t changed too much since this release – apart from a styling change for dialogs. The main design has remained fairly similar since. In the back, there were a fairly large amount of moving parts, but most of this would have been invisible to users.
The next notable update was in May 2018 with GDPR coming around. With 3 days to spare, I put out the GDPR release on 22 May. This caused so many headaches that I had to try again several times to the point where I actually started over and finally released the last version on 7 June 2018.
Version 4 – Flutter and iOS
At work, a colleague told me about Flutter and I decided to investigate. Having looked at it, it seemed to be a really easy implementation of material design and there was the added bonus of being able to also build apps for iOS. I’ve had some requests to also make my app for iOS, but I didn’t want to end up having to maintain two code bases, the only way I would do that is if I ended up with a single code bases. I had considered many times if I should go back to my version 1 design to facilitate this. Early in November, I installed Flutter. It was a slow start, but I gained speed and confidence really quickly that I was using the right technology.
On 16 December, I published version 4.0.0 of London Bus Pal. Not as an Android app, but as an Apple app. I wanted to try the Apple deployment process and see if I missed anything major. And I was still missing map views, so I decided to soft launch on Apple and Android would follow once I had map views in place. Finally on 26 December, I launched version 4.0.3 on Android. Being another rewrite, it is quite nerve wracking, because things which were previously fixed or just stable, might not work. I have seen a few small issues, but not being used to the new error reporting software, I don’t always know if my users can see all of the errors.
Well, for now I’m going to focus on stabilising version 4 of London Bus Pal. The way it has been designed now, means that it should be quite easy to add new features. But I might just leave it alone, because I have a steady following of users.