Project Larvae: Migrating our native apps to Flutter

Visiba Care
15 min readMay 29, 2023

--

In this blog post we will go into depth on how Flutter is going to help us here at Visiba Care take the next big step to improve our mobile apps. The main goal of using Flutter is to increase productivity, much easier cooperation within the app team and keep even steps with the rapidly developing digital world.

We primarily develop apps for health care and have strict requirements when it comes to things like accessibility and security. With tens of thousands of people relying on our apps every day technical changes must be implemented with a lot of care and consideration.

We will share what we found during our early investigation of adopting a new multiplatform framework where different frameworks were compared, and we implemented proof of concepts.

We will talk about some of the technical aspects about Flutter and how we felt about working with it.

Finally, this blogpost lays up a strategy for how the adaptation of Flutter in our apps took place, what obstacles we had to overcome and how we intended to move forward in the future.

A collage I made of the official Flutter logo together with a picture of a butterfly. Butterfly photo taken by Joe Schelling: https://butterfly-conservation.org/news-and-blog/migratory-monarch-butterfly-now-endangered-iucn-red-list

Why Flutter?

As of the writing of this document the development of the mobile apps started approximately seven years ago. At that time, the choice of developing apps natively for each platform was perfectly logical since native is both future proof and allowed for a ton of flexibility.

However, during this time period, the quality and usability of multiplatform frameworks have skyrocketed, and the gap has on many fronts been eradicated. Therefore, the motivation and cost of maintaining two separate codebases for the mobile apps is becoming hard to justify. An increased number of reports become available every day from large companies that have managed to both increase quality and productivity by making the switch.

When beginning to evaluate several types of multiplatform framework Flutter quickly became an interesting prospect. Due to how easy it was for us to get proof of concepts up and running that did things like retrieving data from an API and displaying it the same way for both Android and iOS.

We started filling out a list with up and down sides of adopting Flutter to get an easy overview of what a switch could entail.

Disadvantages

  • Long setup time — Meaning that every time you switch to a new technology there must be an initial time investment to set up a fundamental structure which you then can work on. In other words, you must lose time to gain time.
  • Some native code will still be required — It is unavoidable that we will still have to write some code in native since Flutter out of the box does not cover all use cases. Examples being file uploading, video calls and device information.
  • Must learn a new programming language — Dart, which is the programming language you use for developing in Flutter. There are significant syntax differences between it and with the languages used in native development.
  • Smaller recruitment pool — There are more native app developers than there are Flutter developers. Therefore, it is going to be harder to find suitable developers if we want to expand our team in the future.
  • Larger app size — Flutter embeds its binaries into the artefact you upload to the App Stores increasing its size. This makes it both take up more space on the end users device and makes it slower to download.

Advantages

  • The App team will only have to develop new features once — Rather than having to invest a lot of time getting the intricate details exactly right for both platforms we do it once and it impacts both.
  • Shared codebase allows for consistent behaviour and implementations of features — It has historically led to confusion when the platforms have differences in how features are implemented or fundamentally behave differently.
  • Part of a large open source community where we can contribute ourselves if we need changes — Due to its open source nature we can modify and adapt any core functionality of the ecosystem so suit our specific needs.
  • You can partially run the app in a web browser — Sometimes setting up and downloading an entire app can be cumbersome if you are just trying to display something basic or just play around with distinct colour themes.
  • Really customizable UI (User Interface) — We work a lot with our design system and the apps we make needs to have full support for dynamic theming and colours.
  • Hot reload — When making changes in the UI and hitting save the change is instantly reflected on the device you develop for
  • Easy to write tests — With Flutter you can write small scale UI-tests called Widget tests. These tests can verify behaviour in the UI without the need of an external device to run the tests on.

Other multiplatform frameworks we considered

Kotlin multiplatform

Kotlin Multiplatform Mobile banner. Source: https://www.elitechsystems.com/wp-content/uploads/2020/12/kotlin-1.png

A framework we had our eyes on early and even created proof of concepts for, was Kotlin Multiplatform. This framework was remarkably interesting since the codebase for the Android app was already written in Kotlin and thus could in theory be reused with minimal friction.

Kotlin has an extensive standard library that covers an incredible amount of use cases, so it is easy to implement all sorts of solutions quickly. Kotlin has also received full backing from Google who internally starts to fully replace all Java code with it, so the future of the language and the ecosystem looks very promising.

One huge obstacle with Kotlin multiplatform is that at the time when this document was written it was still just in beta version. Therefore, things like documentation and third-party libraries have been meagre and it was hard to find good examples for implementations of anything that was not very basic stuff.

Most of the Kotlin libraries which we rely so heavily on today such as OkHttp, Koin and Moshi have no equivalences of similar quality in the Multiplatform framework. Therefore, large parts of the code must be rewritten anyway making Kotlin Multiplatform lose one of its edges over other frameworks.

As for the proof of concept we created it was a constant uphill battle of flakiness and trying to decipher nonsensical error messages. Stack Overflow answers and documentation were outdated so quickly that it did not really help at all. Kotlin Multiplatform also does not support sharing UI between iOS and Android; we can only share application logic. [1]

It is worth mentioning that it is being worked on to support shared UI between Android and iOS in Kotlin Multiplatform and it is still in an early phase.

Since the struggle and frustrating experience with the proof of concept it did not feel worth waiting for Kotlin Multiplatform to become good enough for us to use in our mobile apps. There are other frameworks that are already high quality we could start using today.

React Native

React Native logo. Source: https://en.wikipedia.org/wiki/React_Native

Flutters biggest competitor in terms of both adoption percentage and usability in large projects is React Native. It is not hard to understand why, because the prospect of not only being able to share code between the apps but the web as well is very alluring. Flutter can of course work on the web too but unlike Flutter, React is something many web developers are already remarkably familiar with and are already using. [2]

Since its creation around 2015 React Native has gained a lot of popularity and managed to grow a large community of developers. There is an extensive ecosystem of open source libraries, tooling, and plugins you can utilise to speed up the development of your apps while also feeling safe about long-term support.

Performance wise React Native can fully utilise all hardware capabilities of a mobile device while also being able to take advantage of native components for the platform it is being run on. During the last few years there have been substantial changes made to the structure of JavaScript bridge that React Native uses for running code on native platforms. These changes have resulted in performance gains that makes it a top contender in the multiplatform race.

React Native also matches Flutter in its support for hot reloading which allows you to make real-time code changes without having to restart the entire app. The only slight advantage React Native has over Flutter in this aspect is that it also supports hot reloading for the web while Flutter only supports hot restart as this blog post was written. Since the web is not part of the platforms we intended to primarily target this is a non-issue.

So far React Native is looking like a nice candidate for our multi-platform choice. So, let us consider some of the drawbacks.

Sharing code with the web is not as trivial as it sounds since the web is fundamentally different from mobile applications. In the bottom-line HTML and CSS shares almost no common denominator with the way the user interfaces for mobile applications in Android and iOS are built up. This would not mean creating common design components that React for web and React Native for apps is not impossible just that maintaining and ensuring that both looks as expected and works accessibility wise requires a lot of tinkering and workarounds.

The codebase is of course not just the components that build up the interface but also accessing things like the device local storage and talking to an API. It can be tricky to depend on third party libraries that work good on all the platforms you target, thus in the end you may gain little in the sense of productivity.

Working within the JavaScript ecosystem is something that is vastly different from programming languages and frameworks being used in our apps today. The app team has no experience working professionally with anything similar and thus it would take a significant amount of time to reach a satisfactory level of productivity and quality.

Others

There are other multiplatform frameworks from companies such as Microsoft with .NET Maui. [3] No deep dive has been made for any of these frameworks since their market share is negligible and our resources were much better spent elsewhere.

Maui could have been a prospect to investigate further since we have invested heavily into the .NET ecosystem on the backend side and thus have many coders who could potentially contribute to the apps. But as mentioned above it was simply not worth the time and effort to even investigate.

Time frame

Due to the large scope, fully migrating both native apps to share code with Flutter is a process which will span multiple years. We must remember that every feature and view in the current apps have taken multiple years to end up where they are today.

In part two of this blog post we will go into more detail about how we will take an incremental approach to the migration over to Flutter. This is important so that we can keep delivering value without grinding everything to a halt.

As we are starting fresh with a new codebase, we are going to take the opportunity to completely refactor a lot of the core that are building up the apps today. This will slow down the adoption over just more or less trying to one-to-one map the native code to Dart. It is however a critical long-term investment to achieve our goal with the migration.

Time will also have to be spent on training us App developers into understanding and learning both Dart and Flutter. You can optimise the learning process a bit by using a learn as you go mentality. Meaning that as we code and implement the design, we can look up things as the need arises instead of taking long courses before starting.

Migrating our first view to Flutter took approximately 3 months and we expect further migrations to be significantly faster now that all the core functionality and architecture is in place. We are also using a completely new modern design system for the Flutter views which takes some time to get used to.

The development cycle was further extended due to the extra workload put on our excellent QA-team. Testing Flutter required way more planning, awareness of permutations and what things could potentially still differ between Android and iOS.

Rough chart of our timeline. From left to right: Learning and Researching, Proof of concepts, Migration and Quality assurance.

Flutter usage at other companies

Before committing to a new framework, it is important to check for references and investigate how other companies in the software industry are utilising it.

One common shared metric among companies who have implemented Flutter is a 33% increase in productivity.

Google

As the primary maintainers and owners of the Flutter project Google uses Flutter for several of their apps such as Home, Pay and Ads. [4]

ByteDance

With over 800 dedicated Flutter engineers the company behind the massively successful app TikTok has gone all-in on Flutter. They are actively contributing to the open source codebase with performance improvements and various frameworks.

They claim a productivity increase of 33% when developing mobile apps compared to native. [4]

Alibaba

“Flutter significantly reduced the time we need to develop for new feature from 1 month down to 2 weeks.”

Bruce Chen, Senior Development Engineer, Alibaba [4]

Alibaba has successfully made the journey of migrating their native apps to flutter apps. [5] They have seen a significant increase in productivity and have 100s of millions of users. Some of their apps are AliExpress, Alibaba.com and AliSuppliers.

The Alibaba case is something we can get a lot of valuable information from since the overall goal of our migration would look remarkably like theirs.

Picture from Alibaba’s tech blog showing how their navigation routing works between Flutter and Native [5]

Ebay

Ebay was on a tight schedule for developing a new app where users could sell used cars on the US market. They were unimpressed with most existing hybrid frameworks until they evaluated Flutter. Flutter could handle everything they threw at it, and they were able to maintain a native feel in the app.

In an internal survey every single mobile engineer believes Flutter is superior to native development and they also believe it is up to 70% more productive. [4]

Toyota

The main reason Toyota chose Flutter was because of its support for embedded devices. [6] As this document is written Toyota feels they have successfully managed to build a system for their cars which feels premium and achieves high performance. They also really appreciate the hot reload feature of Flutter which increases the developer’s productivity.

Technical details about Flutter

Dart

The programming language used by Flutter is called Dart.

Example of Dart code:

class Spacecraft {
String name;
DateTime? launchDate;

// Read-only non-final property
int? get launchYear => launchDate?.year;

// Constructor, with syntactic sugar for assignment to members.
Spacecraft(this.name, this.launchDate) {
// Initialization code goes here.
}

// Named constructor that forwards to the default one.
Spacecraft.unlaunched(String name) : this(name, null);

// Method.
void describe() {
print('Spacecraft: $name');
// Type promotion doesn't work on getters.
var launchDate = this.launchDate;
if (launchDate != null) {
int years = DateTime
.now()
.difference(launchDate)
.inDays ~/ 365;
print('Launched: $launchYear ($years years ago)');
} else {
print('Unlaunched');
}
}
}

Created by Google, Dart is a client-optimised programming language that can run on any platform. It is open source and uses syntax that is familiar to languages such as Java and C# but also Swift and Kotlin.

Dart features an asynchronous programming model with support for async/await, isolates, and event loops, making it suitable for modern app development where concurrency is essential.

Important note regarding Dart is that it does not support reflection. This is motivated by increased security and support for so-called tree shaking which allows Dart to remove all unused code during compilation automatically. This in turn reduces the overall app size. [7]

Widgets

Flutter UI is built up by using something called widgets. A widget is something that will be drawn on the screen, it can be something simple such as a button or something more complex like a card view. These widgets are organised into a hierarchy and together they make up everything on the screen that the user can then see and interact with. The way that the widgets are structured reminds a lot of how for example how you build modern views in the native apps. It’s the equivalent of Compose in Android and Swift UI in iOS.

We use widget based on material design since they have a clean modern look to them. We then make a lot of customization, so they fit with our own design system that we call Cellula.

Below is a code example for a widget and how it looks like when rendered.

class TutorialHome extends StatelessWidget {
const TutorialHome({super.key});
@override
Widget build(BuildContext context) {
// Scaffold is a layout for
// the major Material Components.
return Scaffold(
appBar: AppBar(
leading: const IconButton(
icon: Icon(Icons.menu),
tooltip: 'Navigation menu',
onPressed: null,
),
title: const Text('Example title'),
actions: const [
IconButton(
icon: Icon(Icons.search),
tooltip: 'Search',
onPressed: null,
),
],
),
// body is the majority of the screen.
body: const Center(
child: Text('Hello, world!'),
),
floatingActionButton: const FloatingActionButton(
tooltip: 'Add', // used by assistive technologies
onPressed: null,
child: Icon(Icons.add),
),
);
}
}
An image displaying the output of the example code above. There is an app bar, a centered hello world text and a floating action button in the bottom right corner.

Skia

Skia is a 2D graphics library managed and sponsored by Google which provides a shared UI-API no matter which platform you are developing for. It is open source and capable of replicating a native feel experience when using mobile apps. [8]

Skia operates like a game engine where it draws all its elements fully by itself not using any of the native components the platform might offer.

Currently the Flutter team is working on replacing Skia with a new rendering engine called Impeller for Android and iOS. Impeller promises a more consistent performance by utilising modern graphic API: s and precompiled shaders. [9]

Pub.dev

The official package manager for the Dart programming language is called Pub.dev and allows anyone to share Flutter and Dart packages with each other. Pub.dev implements a quality control system which allows users to like packages ensuring that high quality packages get higher visibility.

There is also automatic code analysis which allows you to see if the package follows industry standards code conventions and the latest feature of the Dart programming language. [10]

Hot reload

The Dart virtual machine supports direct injection of source code into it. This means that changes to the source code can be instantly reflected on the device that you are testing on. This feature is called hot reload and is an immensely powerful tool when creating new features since you do not have to wait for recompilation every single time you make a minor change. [11]

Hot reload is not supported on the web as this blog post was written. But that is not a use case we really care about.

Gif displaying Flutters hot reload functionality in action. Taken from: https://github.com/flutter

Accessibility

Flutter is fully committed to support various accessibility features in accordance with the UN Convention on the Rights of Persons with Disabilities Article 9. This includes but is not limited to screen readers, large fonts and contrasts.

In the documentation for Flutter, it also highly recommended to take cognitive disabilities into consideration such as clear error messages and large intractable widgets. [12]

During our development of the first screen fully in Flutter: we had a heavy accessibility focus and used the Semantics widget a lot, which allows for us to explicitly define the accessibility properties of any UI element.

Automated testing

Flutter fully supports various methods of testing. The three main methods highlighted in the documentation are: unit test, widget test and integration test. These test types are industry standard and due to Flutters very modular nature it is easy to implement and maintain these tests.

The documentation also links to several useful code examples (referred to as cookbooks) that shows how to implement and best practices when writing tests. [13]

We found Widget testing particularly useful since previously in the native apps running UI-tests with continuous integration proved challenging because they required a device to run on. With Flutter it can run its UI-tests directly in its own SDK like it is any other Unit test.

Next Part

In the next part we will take a look at the technical implementation of Flutter in our existing native apps. The next part will come out soon so keep your eyes open!

--

--