Flutter, Flutter Little Bat: Is Cross-Platform Development Finally for Everyone?
We’ve recently seen lot of discussion concerning Flutter, specifically, whether it’s the new silver bullet (spoiler alert: it’s not) of mobile development, or if it’s just another awful cross-platform framework that allows developers to build sub-par applications, while putting in little to no effort (second spoiler alert: you will have a bad app with minimal effort, but the framework isn’t to blame). I’ve decided to write a few words on Flutter, as well as cross-platform approaches overall and how they fit in with Fintech and trading apps in particular. Let’s see how they stack up.
For starters, Flutter is a mobile cross-platform development framework that in theory, allows you to write the app once and run it on multiple platforms, mainly, iOS and Android. From there, you could write a web app using the same codebase, but I’ll try to stick to mobile to keep it simple for now.
Given that the idea is pretty promising, Flutter is not the first attempt to make it work. Today, Microsoft has Xamarin, Facebook has ReactNative, even Devexperts used to have a cross-platform framework with a less fancy name, called MOBUI at one point in time. So, Google decided they needed their own technology, targeting the same goal, but that addressed a few of the weakest points of cross-platform development.
So, let’s have a brief look under the hood.
Flutter compiles into the native binaries on both platforms. These binaries are then called from the respective application wrapper, which then provides your app with the canvas to draw on, as well as all the inputs you’d typically receive from the user or the external world. This is sort of a gamedev-style approach to the interaction with the underlying OS, aside from the framework built on top of that.
The thing to mention is that Flutter uses the Dart programming language. It’s not widely used in the industry, but that’s no problem. Syntax-wise, Dart is absolutely familiar to anyone who knows Java, Kotlin or Swift. Of course it has some minor, convenient features; but that said, it does not prevent developers from figuring out how the code works right away.
On top of that, Flutter provides developers with a rich and beautiful UI kit, including a set of ready-to-go widgets. Even though none of them have anything to do with the native controls, they are designed to look exactly like the native ones. So we have both Material(Android-like) and Cupertino(iOS-like) controls. More on that later.
What does this all mean in practice? Several things:
- The UI can be super fast and responsive, given the framework’s low operational overhead.
- There is no “magic” happening, unlike ReactNative or Xamarin – no virtual machine, no intermediate code generation – the code written is the code run. Which is good news both in terms of debugging and performance profiling.
- It is pretty easy to kick-start application development using Flutter. Moreover, it’s simple to sketch a great (and even native)-looking UI for both platforms.
But let’s get back to Dart. Aside from the syntax, I didn’t mention one thing yet: Dart is a single-threaded language. It is designed to operate on top of the event loop, allowing you to perform asynchronous operations. For the most part, that’s what developers need. This approach not only simplifies memory management, but also guards you against all the concurrency problems developers can face.
While that all sounds good as it addresses a lot of the development pain, it’s still not a one-size-fits-all solution. The thing is, the simpler your application, the higher the chances technology like Flutter will fit your use case. Here I would totally agree with Juhani Lehtimäki from Snapp, who gives us this awesome diagram:
And this is not a bad thing at all. Today we are seeing a widespread demand for super simple apps. If the industry becomes capable of creating these apps cheaper and faster, more businesses will be able to afford a modern and sleek app – and more importantly, to support it over a period of time without paying for two separate apps individually.
On the topic of more sophisticated mobile solutions, let’s have a look at what does not work so well to the right of the orange dashed line.
The first topic would be the single-threaded asynchronous approach versus concurrency. The first one will definitely work if your app doesn’t load or compute the data dynamically. It might be an app consisting of pre-composed static content, or a basic fetch-and-display application. For instance we have a list of <some things>, each one has a separate page with the details and actions. We load this list once, and work with it. We can change the filter and reload the list once in a while, but that does not change the picture dramatically.
Now, imagine the other extreme: real-time data streaming to the app, changing the values multiple times a second. Also imagine having a mathematical model that combines both user input and the streaming data and updates the values of a number of the fields on the screen, as well as their existence. Now you live in my world!
In order to achieve this while making the process fast and fluid, you need a number of things. First, an asynchronous data transfer protocol. You need a way to explicitly specify which logic can run on the main thread, and which might be unpredictably slow and must be moved out. Second, you need a way to orchestrate all this. In other words, you need more control. Flutter wasn’t designed for this.
Could you still do it? Oh you could. You could work with multiple Isolates. Or you could write this natively and leverage the interop, which is workable in this case. The main hesitation is whether this approach would pay off. To put it simply, this is a tool that doesn’t fit the use case. All these “overly complex” multithreading concepts, such as shared memory, atomic transactions, synchronization, concurrent algorithms and data structures, are here for a reason. In the real world, we have tons of cases where the developer needs them to create a working code. As the complexity of the app grows, the probability you’ll need more control becomes more and more significant.
But suppose you don’t have loads of real-time data. If you have an app that doesn’t do any crazy real-time stuff, probably be fine living in a single-threaded asynchronous world. Are things any different? Yes and no. Yes, because we have one less issue No, because in any application development fields, you’ll see the same picture: At some point on a complexity scale, a generic cross-platform solution doesn’t provide you enough control to allow your program to work flawlessly.
For example, back to the Flutter widget packs: Material and Cupertino. Why two? In order to create a beautiful app, you have to respect the platform’s native UI and UX. So while you could pick and choose only one of them, this is not the best possible solution. That said, you need both – one that will look great on iOS and another for Android. This means you’ll basically have to write the code at the UI level twice. But then doesn’t that sound like native mobile development?
Why doesn’t Flutter do this automatically? In short, it needs to respect the nuances of the platforms to do so – just like the developer – and that is not a trivial task. Or ignore them and compromise the user experience. The developer could do the same thing to optimize the cost or length of the project. It’s an option – one to choose sometimes, but this is a trade-off, not a silver bullet.
By this point, I risk sounding like my dad trying to prove to the younger generation that music from my days was better. But let’s take a closer look at the industry to see if I’m alone here.
As we know, Airbnb has sunsetted ReactNative, and Udacity removed everything written in the same technology shortly after. More interestingly, Facebook, the company behind ReactNative, was rewriting the Messenger app from the ground up. Did they go the cross-platform route, given their expertise? No, they decided to go native for some very valid reasons.
Speaking of Flutter and Google, they have made one relatively straightforward AdMob application using this technology, while keeping everything else native. The thing is, AdMob is specifically a fetch-and-display application, for which Flutter makes perfect sense. But it doubtedly makes sense for most others.
As a final word on Flutter, I’d say it’s a great tool for making simple and stylish applications very fast. A market for these kinds of apps does exist; and on that market, Flutter might be unbeatable. But would it change the mobile development landscape forever? I highly doubt it.
What if you still like the idea of writing once instead of multiple times? The point is, you can do all that for the code that doesn’t immediately interact with the user or the operating system itself. So for example, if you have a mathematical model calculating something to show the resulting numbers to the user, you can do it. We have several ways to achieve this.
First, as much is possible, move the common logic to the server-side. But in doing so comes clear limitations. There is a network between the user and the logic. Which could lead to latency and slow loading times for large data sets.
Second, there are ways to reuse the logic on the client-side while keeping apps native. Each approach is not universal and has its limitations, but deserves evaluation.
Since September, Kotlin Multiplatform for mobile goes Alpha, which, given it is Alpha, has some limitations that don’t suit us, but could work well for other cases.
We are currently using j2Objc to reuse Java-code compiled as an Obj-C library for iOS. Of course, Java and Obj-C don’t sound fancy in the world of Swift and Kotlin, but this is a well-established solution proven to work even for very complex use cases.
And finally, it’s possible to share C++ libraries, but in our experience dealing with JNI complexities, platform specifics and lower-level programming language doesn’t seem to pay off.