The last time I gave any update on development for London Bus Pal was before the global pandemic became a thing in the UK. Things looked up at the time and I didn’t realise what an impact the pandemic would have!
New record!
Daily usage of the app was on an ongoing upward trend. As probably every other Friday before, on the 6th of March, I hit a new record of the number of users who were using my app. It hasn’t been surpassed since, and two weeks later, when lockdown was announced, usage had dropped by 50%. And a week later, usage dropped by another 50% from the week before (or 75% of our record day!).
Usage has slowly crept back up since April, with each week hitting a new record over the previous week (since lockdown started), but we’re still a while away from our previous record. Friday, 14 August, another record is still 32% below our pre-lockdown record. But it’s increasing rapidly and that’s good!
Please stay safe
Don’t take from my comments in the previous paragraph that I think it’s necessarily good for buses to become more crowded, this is not my wish. Please stay safe and do what it takes to stay safe.
What am I working on?
I think the core of the app is good and working. Every now and again I get a bee in my bonnet to rewrite some of it, but it works, so I should leave it alone!
I’m working on a range of add-on functionality which is mainly suited for bus enthusiasts. I already have the bus information screen where you can get all kinds of details about the bus you are travelling on. There is some basic details about which routes the bus has been operated on during the last 7 days.
I’m currently working on making it easier to track which buses you have seen before. I would say think about it as a bus journal, but I’m actually doing both – you can tag a bus as “seen” and you can add some details into a journal which you can then review later on.
With tracking all of the information about buses and routes, it is also quite easy to get out data about buses running on routes they don’t usually run on. The most difficult challenge for me is to find a way to fit it into the app without bloating it.
Sticking to my roots
When I created London Bus Pal all those years ago, the idea was to keep things simple. I didn’t want to over-complicate things and keep it easy to use for everyone. This hasn’t changed, so it’s quite challenges to keep things the way they were, but also add useful functionality without crowding out screens.
Equally, another thing I’ve always believed in, was to keep ads as unobtrusive as possible. It’s always been a small banner at the bottom of the screen, unless you donate some money and then it gets removed.
Why do I mention these things? Because things will inevitably change, but I won’t forget what’s important. On the main screens (anything with prediction data), I will still keep to only showing banner ads; but for any of the extra functionality (bus information, user-generated content and so on), I might become a little bit more aggressive with advertising or ask for some form of subscription to help fund this. There’s a good reason for it:
It costs money to keep the app going
The basic functionality (predictions) is there, it is stable and the costs are fairly small (generally covered by the banner ads). The more sophisticated functionality costs a bit more in terms of money and time investment, so I will try to make sure that I at least cover costs of what it takes to keep the lights on. So please, if you start seeing subscriptions and some full-screen ads, I promise you that it is not because I am trying to milk the app for all the money I can, but just a way that I can keep funding all of the infrastructure needed for whatever you are using.
That’s all for now, I’ve just taken a bit of a break from working on the “seen” functionality – back to some coding for me!
I haven’t written any posts for a while, but you will notice that there have been some updates to the app. Life started getting really busy from October, but things seem to have stabilised a bit now so that I can try and find a balance between life and my app.
My posts are mainly to try and give people a bit of an insight into what goes on behind the scenes of London Bus Pal. Here’s what you need to know:
I have a day job which is not developing a mobile app
I work on London Bus Pal in the evenings, mornings before work and weekends (but I have taken long breaks at times when I just get too busy at work)
I do not make enough money from the app to not have to work a day job
If I could, I would work on the app full-time, because I have so many ideas but just not enough time to implement them
That’s all for now. Always happy to correspond over email!
I have some grand ideas which need users to be less anonymous than they are now. People need the ability to sign into the app in order to do certain things (I don’t want to give away too much, but it will be personalised to them) – but some things needs them to be identified, especially if they use multiple devices.
Not being able to log in so far has always been fine and it makes things like GDPR compliance a whole lot easier. There are some difficulties however and I have had those suggestions made: being able to transfer settings from one phone to another for example.
So currently the idea is fairly simple – give users the ability to sign into the app and then they will be able to backup their favourites to the cloud and restore them when they wish. Of course, this can be more seamless than described here, but that’s the first pass at connecting the app into the cloud.
As it stands, I have built functionality to allow signing in with Google and Facebook details, as well as signing up for an account with username and password combinations. This all works and I just needs to make sure that everything is also fully GDPR compliant. (This is not yet in version 4.5.0 which is published at the time of writing, but it will probably be in the next version, unless I decide to do some quick bug fixes).
After doing the log in screen, the first bit of functionality I will enable is a simple “backup to cloud” and “restore from cloud” facility. This should make it loads easier for people to continue to use the app on different devices.
I am hoping for the functionality to connect to the app to the cloud to be released in September…
My back-end API’s are all running on a GoDaddy hosting account. Until yesterday, I was using one of their shared servers and the response times started to bother me.
The median response time for a call was about 760ms, which is bad in API terms. What is worse for me, is the disparity – the quickest results tended to be around 500 to 600ms, but then other calls would take up to 4 or 5 seconds to complete. I have some heartbeat checks which does nothing other than return a response, so these should be super-quick, but they were not.
I had a look around the internet and determined that part of my problem was that I had chosen one of the slowest frameworks out there (Slim v3) to build my API. To prove whether it was the framework or my server, I did two things: created a blank heartbeat which doesn’t use the framework (basically just “echo ‘ok’;”) and then also setup node.js to check the speeds. The results pointed to the server, not the choice of framework. Apart from requests within a few seconds of each other, “cold” requests would always take around 500ms.
Upgrading to business
The decision was made to upgrade to business hosting. Surprisingly little information is available on GoDaddy’s upgrade process other than a bunch of complaints about it not working. I was really unsure about how seamless it would be as I didn’t want to be spending the whole day troubleshooting. I half-did a bunch of backups and paid over the money.
About 20 or 30 minutes later, things were done. A quick check of my API’s showed that everything was running and everything appeared to have just worked…or so I thought.
The main two issues which happened to me were that my MySQL database was really slow after the transfer and my WordPress blog stopped working. The blog was fairly trivial to fix as some permissions had changed during the move (not sure why only on for my WordPress installation as everything else remained intact).
One other side issue which happened is that I didn’t disconnect one of my terminal connections. It became clear after a few small tweaks that I was on the old server, not the new.
Slow database
To be fair, most of yesterday is now just a bit of blur, but I noticed the slow database and tried to fix it. In my own inexperience, I ended up locking a bunch of tables (by running optimize on it repeatedly, renaming a 2GB table etc) and things just completely died on me.
A thorough search of the internet helped to kill of most of the processes which had locked up my tables. Stubbornly, two tables remained and I couldn’t see what was locking it, but generally everything else was resolved and my database was running ok apart from the two tables I couldn’t access.
The last suggestion is to just restart the MySQL service, but I do not have the access to do this, so I decided to phone GoDaddy support. I know people complain about them a lot, but I’ve previously had to deal with them (after they killed my SSH access) and it was a painless affair.
Just shoot me instead
The call wait time was short enough and I was soon answered by someone American sounding who seemed eager to help. I explained that I have two tables which are locked which means I cannot access them. This first got interpreted as me forgetting my password; no, I can access the database, I have two tables which are locked.
The things which frustrated me on the call were:
The technician clearly managed to access my database as he called out my table names (this becomes important in a minute).
The technician did not understand what a table lock meant or how to tell that a table was locked. Due to his own lack of knowledge, he kept trying to check with me if what I was saying was really the case or if I’m just another idiot user. Due to this, he had to escalate to his supervisor.
My total database size is around 2GB and I have 2GB of RAM available to me on the server. They repeatedly tried to insist that I simply had to buy more RAM as this was not sufficient due to my database size. I wasn’t having any of it and just kept on refusing.
I asked the technician if they were able to just restart the MySQL on that server and he then said that his supervisor just did this. I checked this and he did not restart the service. He kept explaining to me that it was the MySQL that was restarted, not the server. Yes, I was looking at it and I could see that MySQL was running for 18 hours already.
He then told me that his supervisor suggested that I upgrade to a package with more RAM as my database was inaccessible due to a lack of RAM. I can see the memory usage on my server and only around 300MB was used. He now claimed that he was unable to access my database at all, despite reading me back my own table names earlier. Only two tables were locked, everything else was fine (I checked this in several ways: directly on MySQL, through some API calls and then also checking my logs of external calls with perfectly good responses).
I had to just end the call after 44 minutes because this guy and his supervisor were completely useless. He was clearly out of his depth and instead of owning up to it, he started making up stuff like the fact that your entire database has to fit into the memory available on the server.
While I was on the call, I decided to just rework some of my database any way. The only annoying thing is that I’ve lost around 12 hours worth of data as the two tables in question, are used to store the collected data.
New day
I got up this morning and checked things out and the locks are gone and everything is going swimmingly again. Except for the fact that I redesigned half my database to work around the locked tables. It was a useful exercise any way since I was saving way too much data. (Yes, the one table in question makes up 1.7GB of the 2GB database).
I managed to do queries on it and it is all just fine. In fact…
Rechecking all of my heartbeats and health check, I noticed a few things:
The fastest responses times after I upgraded my package is now around 300ms – this compares to about 600ms for the fastest responses previously. This is a significant improvement.
The median response time is now down to around 350ms to 400ms down from 760ms. Given what I was doing on the server yesterday, I expect this to go down, as these measurements are all from the time while I was doing large numbers of database queries.
Checking this morning, some of my heartbeat responses are coming back below 100ms.
Most surprising is how fast the response is from Node.js – 22ms for a heartbeat – I might just migrate everything to use Node.js instead as this is the speed I am after.
Executive summary
Here is a summary on my feelings about GoDaddy:
Shared hosting is ok for absolute starters, but in hindsight, I should have just got started on business hosting.
GoDaddy is okay in terms of their offering and I feel much better about them after upgrading to the business package.
The company is being let down badly by their support staff. My call yesterday feels like a major run-in – it is also frustrating that both times I have called their support, they try to upsell to me. Support should be good at support, not sales.
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.
It’s been a while since I posted here, mostly because I’m busy behind the scenes with huge new features. It will take a while, so I can’t promise or really say anything yet.
Today I had to do a bit of housekeeping and had a look at how sales were doing. It’s has been about 2.5 months since I started my in-app purchases, so I can finally start to see how these are performing in comparison with ad revenue and sales revenue. The breakdown in percentage from 1 February until today is as follows:
Advertising revenue: 95.7%
In-app purchases: 3.2%
Mobile app sales: 1.1%
The thing which is not obvious here, is that there hasn’t been any obvious drop-off in advertising revenue, although, it would be very difficult to tell, since advertising revenue is a little bit volatile any way. Revenue is up by 3% vs the previous 28 days, but it can be down 5% next month for reasons beyond my control.
I see in-app purchases as an additional revenue source, rather than cannibalising my advertising revenue.
Another interesting metric I could look at, is how Android compares to iOS, however, my iOS take up is still fairly low, so it would be an unfair comparison. I simply don’t have enough data points to really say.
Feel free to get in contact if you want to know more – I’m always happy to talk to other developers and share any experiences I may have had. Back to work for me then…
Following my previous post where I did some rudimentary experiments on other bus apps, I decided to leave my phone running for another couple of hours and see the effects on battery usage. This experiment involved opening and viewing specific stop and then simply leaving the app by pressing on the home button. I left the apps, but didn’t kill them. I did this for all the apps in the experiment and then reset all the counters.
And then I just left the phone for a couple of hours.
During the experiment – I left the phone off – in fact, I left the house and just left the phone on a desk. I used the phone for a couple of minutes, but made sure not to go anywhere near any of the bus apps.
The results were interest in that one app had woken up the phone 28 times and enabled the sensors for over 7 minutes during the 2 hours and 24 minutes the experiment was running for. It also transmitted 32.2KB of data during this time – not a significant amount, but that’s almost 50% of the amount that was transmitted by London Bus Pal when it was doing some real work.
Here is a breakdown of the results for this offending app (it is listed as a top app on the Google Play Store):
Duration:
2h 24m 51s
CPU Usage:
47s
Number of times waking device
28
GPS:
7m 27s
MPU6500 Acceleration Sensor
7m 26s
MPL Rotation Vector
7m 27s
Gravity Sensor
7m 27s
YAS537 Magnetic Sensor (compass)
7m 29s
I went back into the app to check why it would need to be on in the background. I noticed that Push Notifications were enabled and that I was automatically subscribed to get notifications for “special promos etc”. I turned off everything and disabled auto-refresh and also turned off analytics data sharing. After having to dismiss an ad (wow these are annoying!), I killed the app and restarted it (to make sure that all of the settings would load properly). I loaded a bus stop and pressed the home button and reset my battery usage monitor.
Time for some tea…
After tea, the results were so bad, that I thought I did something wrong and restarted the whole experiment. This time making sure that I take a couple of minutes before resetting the battery usage monitor to ensure that the app should be asleep.
Duration:
18m 26s
CPU Usage:
1m 25s
Number of times waking device:
3
GPS:
18m 26s
MPU6500 Acceleration Sensor:
31s
Gravity Sensor:
1m 1s
MPL Rotation Sensor:
1m 1s
YAS537 Magnetic Sensor (compass)
58s
The GPS timing was the reason I didn’t take the results from my first test, because I thought it was broken. It turns out the app is broken and holds onto the GPS when it really doesn’t have to.
Now I’m really frustrated
Do users even realise how this app is killing their battery? I looked through the reviews and this app gets a lot of praise (yes I’m slightly jealous) – but it seems that users don’t generally notice. I wouldn’t know without extensive testing which app is it that caused my battery to die so quickly – I usually blame the phone, not the apps. There are users who noticed, but they are far and few between:
Out of interest, this app was “App 2” on my previous list. It was exceptionally bad in terms of data usage and really bad on the battery in real usage testing too.
I’m going to stop writing now, because I don’t like focusing on other people’s work – I want my own to be the best it can possibly be. This was a useful exercise to make me believe in what I am doing, but it equally gets me down that unsuspecting users don’t realise why their phone batteries are draining so fast!
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:
brightness: Brightness.dark,
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!!).
About a month ago, I wrote a post about my ideas for improving the monetisation of my app (give it a read if you haven’t). Now, a month later, I’ve done the implementation and things have progressed and changed a bit since then.
Time limited benefits
After I wrote the post and before starting the implementation, I sat down and worked out all of the economics of what I was trying to achieve. Long story short, there is very little benefit in time-limiting some functionality. Even my smallest tip covers more than 2 years worth of advertising revenue per person. Therefore, I decided after doing these calculations, that I would simple turn off ads permanently for anyone who ever tipped me.
Subscription model
During the implementation, I found that there is a bit of work to be done to upgrade or downgrade someone’s subscription on Android if they’ve previously subscribed. This complication made me deprioritise the subscription in my initial implementation, but most of the code is there and it would probably not take more than 30 minutes to enable. For now though, the decision was made to have no subscription model.
The result
This screen should give a good idea of what I ended up with.
The financial result
It might be too early to tell, but the results so far are encouraging. The movement is slow, but it’s isn’t as slow as it was with my paid app. This is meant to replace my paid app, so I compare the result to that. So far, since I launched my in-app purchases, my revenue is double that of my best month of my paid app. It’s not a difficult record to beat, since it is less that my most expensive tip, but the word to use is encouraging!
Something else which is different from my planning is that I had assumed that most people would choose the small tip, a few would choose the medium one and I would probably never see the bigger ones being used. Almost the opposite has happened – in my small sample, I’ve had use of the two bigger tips, so again, the word to use is encouraging.
Feel free to reach out to me if you want to know more about what I’ve done. I’m always happy to share experiences.
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.
Material design
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.
Hot reload
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…
Flavours
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.
Dependencies
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.
APK sizes
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.
Maturity
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).
Wearables
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.
Summary
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.
Feature
Android
Dart & Flutter
Language – both languages are very similar, but Dart makes asynchronous development a bit easier and handles nulls better
Java
Dart
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
4.07MB
10.03MB
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.