And the one thing that could make it come undone
If you’re anything like me, you’ve likely been tracking the progress of Flutter over the past couple of years. If you’re even more Like me, you’ve sorted out bet the farm on it by switching to it for new projects and starting up a YouTube channel discussing the ins and outs of Flutter.
I’ve almost entirely switched my development loadout to Flutter, and I’m not regretting it one bit. I’m already referring to my development as “before Flutter” and “after Flutter”, that the time “before” was quite and difficult to target multiple platforms in a performant way.
But cross-platform isn’t new, so why does Flutter represent such a pivotal moment in software development?
The desire to “write once, run anywhere” has been around for a very long time. It’s one of the reasons that Java was originally produced, so you could have an app that you could ideally write in a single language, and then run that app on any device of your choosing.
Considering that the concept of DRY (Don’t Repeat Yourself) is baked into the hearts and minds of every software developer, creating an app for iPhone and for Android in two separate languages and design frameworks has always been difficult. It seems like the ultimate duplication of code, that your Android or iOS app would invoke the same API, produce the same text on the screen, but both had to be made in their own way.
And over the years, we’ve had many attempts to right this perceived inefficiency, and I’ve tried a lot of them. Anything from Cordova, Ionic, NativeScript, React Native, and Xamarin Forms, I’ve written at least something in them, and left them. Why? Here’s a short breakdown:
- Cordova/Ionic — Quick to prototype apps in, but slow for general use. Didn’t make for the best user experience.
- NativeScript — Showed some promise initially, but licensing on critical packages meant it was a non-starter (at the time I was using it, I couldn’t use SQLite with it for a commercial product unless I wanted to pay money)
- Xamarin Forms/MAUI — A very frustrating, joyless experience. Only stayed with it because I knew C# (the wrong reason to stay with something)
All of the above frameworks also had something else in common. They depended on the utilization of “native controls” on the target device. So, for iOS or Android, the framework would bind the appropriate system control for what I was using at the time, be it a label, checkbox, or some other control.
So, if the way a checkbox was implemented changed, then the people responsible for maintaining the framework had to update the way they bound to those controls. The same thing for new controls — the framework maintainers (or open-source packages) would implement these new controls within the framework. From my perspective this equated to a near-constant game of whack-a-mole, chasing new features in the API as they became available, and fixing problems with existing bindings as the respective operating systems were updated themselves.
It also meant that porting these frameworks to new platforms was a huge burden, as you would have to map all the controls that the framework provided to native controls on the device itself. And then, once that was completed, someone would be responsible for keeping these bindings up to date.
I have less-than-fond memories of a certain app of mine not displaying any background for buttons on Android 7.0 in Xamarin Forms. In the end, this was simply because of an implementation quirk within the framework itself, but that’s not something you can explain easily to a customer. Instead, you have to spend hours chasing it (and write a custom renderer!) just to resolve an issue like that.
Flutter solves this problem by not using any native controls and instead draws its own controls to the screen. Now, that just about sounds like the most terrible outcome for everyone in relation to user experience and battery life. But before you draw that conclusion, consider that’s what pretty much every game in the last two decades has been doing. We don’t have a “platform-specific” gaming experience, we have the same game that’s been ported to multiple platforms. Only the touchpoints are updated per platform (to show you how to use an Xbox controller on Xbox, and a PlayStation controller on PlayStation). Each game runs natively on the respective platform and can perform quite well.
Getting stuff to render quickly on independent platforms is something of a core business for Google, which also produces a browser you may have heard of. Chrome uses Skia for almost all graphical operations, including text layouts, and does so in a very performant manner. And when you think about it, that makes sense. The Chrome developers don’t need a way to layout text on Android, iOS, Windows, etc. They just need to get Skia to perform that operation extremely quickly, and then implement that functionality across platforms.
Flutter also uses Skia for its rendering operations, which means it’s extremely performant in laying out apps and rendering animations, even on lower-specced hardware.
As operating systems have been released as time has gone on, there has been a fairly strong connection between the OS version and features available within apps written for that platform. As time goes on, more functionality is added to the core OS, more controls are introduced and existing controls are updated. If your app uses a newer set of APIs, it possibly wouldn’t run on older versions of the same OS, as your app would depend on those specific platform APIs.
And that’s just within the context of the computers that we use every day. If we think about software in a broader sense, even the cars that we drive have a real dependence on software as well. The infotainment systems all have a certain style applied to them and are consistent in how they appear.
The point of the story is that a lot of time is spent just getting things to render to a screen in a performant way. Whatever you’re reading this article on now has had an incredible amount of time invested into it on how the buttons, text, labels, and other visual elements should appear. And once they are rendered to the UI, and other visual effects come into it like shadows and gradients, more effort is spent to try to get it to render quickly, to reduce jank or slowness in operation.
Flutter is revolutionary in this sense because it entirely decouples the dependency on the UI from the parent operating system. If you can port Flutters’ rendering engine to your phone, car, popcorn maker, or coffee machine, then you have access to a great-looking library of controls that will make whatever app you’re making look great. So, the requirement on the platform developers is less about creating a set of controls for buttons, texts, and the rest, but just to ensure that their thing can run Flutter. That’s pretty cool.
This possibly means that platform developers could create an OS and choose to fully accept the visual design language that ships with Flutter, and not actually design any of their own native controls. As long as their thing can support rendering Flutter apps to their screen, everything should work. That’s a huge amount of development work simply removed.
We’re not ever going to get a framework that is cross-platform that is perfect. Somewhere along the line, we’re going to have to compromise because we’re creating a cross-platform app. Admittedly, I find these compromises with Flutter itself to be fairly small.
One thing I have noticed within Flutter is an amount of something I’m calling stable feature slip. When Flutter came to Android and iOS, and it became stable, it landed with stateful hot reload. You’ve likely already heard about this, but you could make changes to your app layout and service layer, and the state of your app would be maintained, but your app would just magically update. This reduced the development loop significantly and was one of the headline features for Flutter.
Flutter for Web entered stable last year, and did so without stateful hot reload. This issue tracks this particular problem. Make no mistake, hot reloading for web is a very A difficult problem that is not easy to resolve, as per one of the comments on that issue.
But, Flutter for Windows/Android and iOS went stable with support for hot reload. People using Flutter could perhaps have a concept of what makes a piece of software “stable”, and that once Flutter reaches stable, they could reasonably expect a hot reload because that’s what every platform before shipped with.
As we can see here though, that’s obviously not the case with Flutter Web. To be clear, this isn’t me shouting “come on you noobs! how hard could it be!” Because I recognize and accept that enabling hot reload is an immensely complicated proposition even on non-web platforms. But it doesn’t change the fact that Flutter Stable has a variance in features depending on what platform you are developing for, which feels weird.
Another example of this is with Flutter on the Windows platform. iOS, Android, and macOS shipped with the ability to make your platform code in a language that you could reasonably expect the developers of that platform to know. iOS lets you write the platform code in Swift or Objective C, and Android lets you write the platform code in Kotlin or Java. I would expect developers of those platforms, to know those languages.
Naturally, you would expect Flutter on Windows to let you use .NET as your platform-specific code, by being able to invoke the DLLs you produce from .NET. Despite C# and .NET being used by probably millions of developers around the world, and being the obvious choice, Windows functionality must be implemented in C++.
Prior to Flutter, why would a Windows developer need to know C++? If they were writing an extremely high-performance native app that was time critical, it’s possible they could know it. But this is not the norm, this is absolutely the exception. As a result, there are comparatively few (and even less “good”) C++ developers compared to .NET developers on Windows.
Because Flutter launched for iOS and Android with support for the most popular languages, I count the lack of .NET support for platform-specific functionality on Windows as another example of a stable feature slip. As a result of this, it’s much harder to develop and release plugins for Flutter on Windows and affects the value proposition of Flutter on Windows as a whole.
Again, I’m not saying it’s easy to do this. It’s extremely hard to do any of this, on any scale. But just because something is hard, does that mean you should announce platform stability for something like Flutter and randomly forego certain features when you do so? I don’t think that makes sense, for the simple fact that it sets a precedent that a platform can go stable without commonly accepted functionality being available in it. After all, stable is just a word, and it’s probably better to keep platforms in beta until they have the features of the other platforms.
If this trend progresses, it means that Flutter could go stable for any platform, with possibly any features present or not present. In the long term, that’s a problem, as Flutters’ feature set varies between platforms. That’s the one thing that could affect Flutters’ viability as a cross-platform framework of choice, as you wouldn’t really know what features you would get in a stable release of Flutter for your platform of choice.
Still, Flutter does represent a thoroughly great framework, and if you haven’t started using it then you should definitely give it a go. Just be aware that things might be a bit different between platforms, though.