Why Flutter on the Desktop Can’t Survive Without Rust

Why Flutter on the Desktop Can’t Survive Without Rust

Writers note: In this article, I talk about the?Win32 package?which is the hard work of Tim Sneath and many other hardworking contributors. The Win32 package is high quality and Tim is a great contributor to the Flutter ecosystem. Nothing I have said is supposed to disparage this package in any way. I am merely discussing alternatives that work well for me.

Flutter on Desktop is in a bit of an in-between at the moment. It’s stable on all major desktop platforms (like Windows, macOS, and Linux), and it’s seeing?some?adoption. It’s certainly quite a popular framework for mobile development, but that popularity doesn’t seem to carry across to desktop apps (just yet, anyway).

It’s true that stability on desktop platforms is a lot newer than its mobile counterparts, but there’s another important area where Flutter on the desktop suffers in comparison to its mobile equivalent. This relates to extensibility, and to be more precise about that, how hard it is to get Flutter to call native code on the underlying operating system. On Android or iOS, it’s not so hard to put together some Kotlin or Swift that will achieve what you want it to do. On the desktop, it’s a whole different story.

“Flutter on Desktop” is quite a broad term, encompassing Windows, Linux, and macOS. For the purposes of this article, we’ll only be looking at Windows. I think that’s fair though — considering how much Windows dominates the desktop space.

In my case, I was developing a desktop app called StagePlay.?It’s an app that connects to a smart bike and tells the user what gear they’re currently in. In order for it to work, it requires the use of Bluetooth Low Energy (BLE) to communicate with the smart bike.

Support for BLE on the desktop is not completely absent, but in my case, the existing plugins did not work for me (they caused my app to crash to the desktop). So, I needed to write my own implementation for this.

Why I wrote a Desktop app in Flutter in the first place

If Desktop support is average in Flutter, why did I write my app in Flutter then? That’s kind of an obvious self-own. Well, I had been using Flutter for mobile apps for some time, and to put it simply, I think the layout system that Flutter uses is the best I’ve ever seen. Previous to using Flutter, I would attempt to use XAML to lay out my Windows WPF apps or Xamarin Forms apps. Flutters’ layout system blows XAML out of the water. It’s not even close.


As it would turn out, I’m such a Flutter fanboy that even the idea of turning back to XAML to write an app in WPF/Xamarin Forms/MAUI was genuinely depressing. So, if my choice of framework was going to be Flutter, I would have to take a harder path to get my Bluetooth requirements to work.

Before I used Flutter, I would attempt to use XAML to lay out my Windows WPF apps or Xamarin Forms apps. Flutters’ layout system blows XAML out of the water. It’s not even close.


This meant I would have to find a way to leverage native platform functionality to connect to my chosen BLE device. And, there were three options for that, the?win32 package, a native C++ plugin, or using Rust (with?flutter_rust_bridge). I didn’t know about the third option (or, much less, the Rust language itself) when I started this journey.

Using the Win32 package

The predicament of your garden-variety cross-platform developer is that they are good at the language they are using in their cross-platform framework, but perhaps not as good in platform-specific languages. In my case, I’m good at Dart, C#, and probably TypeScript, but not good at lower-level languages like C++. Naturally, this meant that I would stick with using Dart if I could. And for this, using the Win32 package seemed like a great fit.


This seemed like a great place to start, and there were even samples on how to use Bluetooth in your project, as per?here. However, using the win32 package presented two major problems.

  1. I couldn’t subscribe to notifications from my BLE device, which was literally the only thing I wanted to do over Bluetooth in the first place
  2. I’d have to write some pretty difficult Dart code to be able to leverage the win32 API’s

What do I mean by “pretty difficult Dart code”? Well, even though the Win32 package lets you use Dart, you still have to call functions like?calloc?(or C Allocate) to allocate memory for the return value of functions. You’re also responsible for your own memory management and cleaning up after yourself by manually deleting objects when they are no longer in use.

The predicament of your garden-variety cross-platform developer is that they are good at the language they are using in their cross-platform framework, but perhaps not as good in platform-specific languages.


On top of the above, the Win32 package is good at mapping the Win32 API surface but less good at mapping the WinRT API surface. The WinRT API surface is where all the newer technology (like BLE functionality) tends to reside, so this was a non-starter.

So, the Win32 package was out. The only other thing I could think to do was to write a native C++ plugin.

Writing my own native plugin in C++

Up until I attempted to write my own plugin in C++, I felt like I was a pretty good developer. I knew enough about software development to design and produce an app from start to finish — everything in the API, database, app, and so on and so forth.


Sitting down to try to produce anything of value in C++ was enough to completely smash this elevated opinion I had of me. Seemingly, I was a child amongst the burly, grizzled men of the software development world. I was but a babe, with no idea what a pointer was, or what those ampersands did.

To aggravate this situation, I even found a good cross-platform BLE plugin, written in C++. All I had to do was carry water from my Flutter app to this library, and map the commands and responses from this plugin back to my app. And I failed?spectacularly.

Sitting down to try to produce anything of value in C++ was enough to completely smash this elevated opinion I had of me.


If you know C++ and you’re great at it, and you’re reading this thinking “Wow, this guy sounds like he is terrible at software development”, then that’s okay. But, I submit to the jury, I have to maintain a lot of knowledge about Web development, database development, and finally app development. It’s a lot. And, I think learning how to safely use C++ for my app is a huge ask.

Somewhere between trying to use?Pigeon?to generate my binding code for me, and writing my own platform binding code, I started to give up. There seemed like there was just too much to learn for what I was trying to accomplish. Plus, I knew that even if I hacked together something that seemed to work in C++, it was irresponsible of me to then go on to deploy something that I wasn’t completely sure would?not?cause memory corruption or other issues on clients’ machines.

Deciding to mothball my project

Without a reliable way to leverage BLE from my project, there wasn’t a way for my project to continue. I can’t ship an app that connects to a BLE device when the BLE connection is unreliable, and the app occasionally crashes to the desktop because of it. I persevered to see if there was another way. And in doing so, I found the Flutter Rust Bridge project.


Using the Flutter Rust Bridge

At this stage, this was my current awareness of Rust:


  1. It was a programming language.
  2. Their mascot was a crab.

It’s obviously pretty scary to do anything non-trivial in a brand-new language that you’ve not used before, and there are no guarantees that you would even be successful. I was expecting to flame out in a few days.

Instead, only a few days later, I had completed my Bluetooth implementation and everything worked great.

Because my experience went so well, I now believe that Rust is the critical missing piece to making Flutter on the desktop a success. Here’s why:

Easier than C++, and a better choice than the win32 package

The other alternatives for native functionality on Windows come up short when compared to using the Flutter Rust Bridge. Here’s why:


  • If you’re a cross-platform developer, you probably don’t know C++. Trying to use a hard language like C++ to implement native functionality is almost definitely going to end in tears.
  • Also in C++, uncaught exceptions will just crash your app. Because you’re new to C++ (see point 1) there are going to be a lot of uncaught exceptions.
  • Implementing your stuff via the Win32 package will still require you to read the API documentation for most function calls. This in itself is not bad, but reading examples in C++ and trying to rewrite them in Dart, while still remembering to call the underlying constructs like?calloc?and?destroy, does not lead to many happy outcomes.

Using Rust and the?flutter_rust_bridge?avoids these problems (points 1 and 2), or makes them more manageable (point 3). For example, it’s easier to rewrite C++ code samples that you may find on the Microsoft API documentation in Rust than it is to do the same in Dart via the win32 package.

The Rust community is huge and kind

The programming field has a problem with elitism. Some developers will treat you like an idiot or talk down to you simply because they are better developers than you, or know more than you. I’ve learned that not a lot can be done about this problem specifically, and it’s just easier to work out the general feel of a community and then decide if you are going to spend the majority of your time asking questions on Discord, or if you’re just going to try to piece together the documentation and digest crumbs from GitHub issues from strangers who have a vaguely similar problem to you (where, at least, people won’t yell at you).


In my experience, joining the?Rust Community Discord?and peppering them with my stupid questions in the #beginners channel was a joy. Strangers went above and beyond, every single time, to help with what are admittedly pretty simple questions. As long as I displayed a modicum of understanding, and applied some logical sense to what I was being told, I received all the information I needed.

And it didn’t stop there — I also received helpful suggestions on how I could stop writing a monstrosity with global state and deadlocks and start writing a cleaner, more elegant solution. What I have now is absolutely higher in quality because of the tireless help of the volunteers on these Discord channels.

Strangers went above and beyond, every single time, to help with what are admittedly pretty simple questions.


Flutter Rust Bridge is a dream to use

If you are writing a native C++ plugin for Flutter on Windows, you have to write your own bindings, or rely on experimental platform support within the Pigeon package. Even then, Pigeon won’t generate?Stream?‘s for you, so you have to implement these yourself. It’s a lot of boilerplate you have to write yourself and can be easy to get wrong (exacerbated by a complete lack of documentation).


Using?flutter_rust_bridge?is dead simple in comparison. The features are multiple, but just to highlight some of my favorite elements:

  1. Your functions within Rust are automatically mapped into callable functions from Flutter. Just define a new?pub async fn?within Rust, run?flutter_rust_bridge?‘s codegen tool, and immediately start calling your native code.
  2. StreamSink<T>?is supported out of the box, so you can emit values over time. Values written into these streams are emitted directly into your app in Dart objects, so you can manipulate them as you please.
  3. Causing Rust to “panic” doesn’t result in your app crashing to the desktop. Instead, internal error-handling within?flutter_rust_bridge?catches the panic occurrence, and describes what happened.
  4. “Fancy enums” within Rust are supported. For example:

This rust code…

pub enum TransportType{
    Car{
        capacity: u8,
    },
    Bike{
        wheelCount: u8,
    }
}        

…would not work within Dart. That’s just not how enums work within Dart, and that’s probably not going to change in the near future. Still, for streams, it’s really cool being able to stash certain details into an otherwise boring?enum?.

flutter_rust_bridge?maps these values into a supremely usable format that is intuitive and makes a great deal of sense, meaning you can handle them like this:

api.transportTypeStream().listen((event) {
  event.when(car: (capacity) {
    print('the car has {capacity} capacity');
  }, bike: (wheelCount) {
    print('the bike has {wheelCount} wheels');
  });
});        

And all of the interconnecting code is generated for you by?flutter_rust_bridge?.

The Takeaway

From me to you, if you are a developer and you are writing a Flutter app for Windows, seriously consider learning a bit of Rust and using?flutter_rust_bridge?it to achieve what it is you are looking to do. In reality, it’s only going to benefit you and make you able to deliver your apps faster.


But, on a grander scale, it’s hard to imagine my app being successful on the desktop, or ever actually working, without Rust and?flutter_rust_bridge?. I think that people who decide to give Flutter a go on the Desktop for their app will find using the win32 package possibly restrictive and hard to understand, and those same people will lack sufficient C++ experience to carve out their own custom native implementation for the functionality that they are after. At this point, it’s possible (or likely) they will just use a different framework to achieve their goals.

That’s why (save for something else massive happening in this space — like being able to load and execute compiled .NET code from within Flutter), the success of Flutter on the desktop seems strongly linked to Rust and the?flutter_rust_bridge?. I’m personally excited to see where this project goes in the long term. If you’ve not heard of it, be sure to check it out. It might just save your project as it did for me.

Jukka Nikki

?No one in the brief history of computing has ever written a piece of perfect software. It’s unlikely that you’ll be the first.“ – Andy Hunt

7 个月

Thank you for nice article. I found it humble and well balanced and had a joy of reading it.

要查看或添加评论,请登录

Buildyounique的更多文章

社区洞察

其他会员也浏览了