Writing Multi-Platform GUI App in Rust

Altimetrik Poland Tech Blog
4 min readAug 1, 2023

A skippable introduction

There are many ways to make software work on a platform that the developer did not intend. There’s emulation, of course, but that’s bulky. Wine has been a valiant effort to bring games and applications from Windows to Linux, but applications running this way may look worse when transplanted to a new environment. WSL is also an interesting development, but it didn’t receive official GUI support until recently, and there are similar problems with non-native looks.

What if, instead, our application could, using a single code base, run on many different platforms. I know what you’re thinking — I don’t want to write in Java either.

Setting things up

We target the following platforms: Desktop (Win/Linux), Web as well as Mobile (Android). Dioxus, the framework we will be using, also supports macOS and iOS, if you are interested in that.

First, we need to install xbuild, which takes care of some of the annoying aspects of targeting multiple platforms.

cargo install xbuild

Next, we will generate a project template.

x new $project_name

In general, I found the whole rust-mobile organization helpful when working on Android compatibility.

Take a look at these files

Let’s go through the files from the fresh template to find out how it works.

Why do you see a Kotlin file in our beautiful Rust application? We still need some glue to marry the dominant Java platform with our compiled code, and this serves as just that. Actually, TauriActivity from WRY, a cross-platform WebView rendering library, does the heavy lifting for us.

This is the template configuration for xbuild itself, you can review the code for details, but the defaults work for us.

A word on dependencies

Default dependencies are strongly platform-based, and unfortunately you will often have to work around some crates not supporting all the platforms you need.

Desktop is of course the easiest, most crates support most popular configurations, even if by accident. The problems start with web — wasm runs in a very closed environment (which is good), but it means that your favorite crate died before setting up the login because it tried to access the system timer, and you spent half a day wondering why.

Android can be another problem, again because of the NDK barrier that makes many seemingly obvious things non-trivial. As always, there are ways around this if the community wants them (e.g., Google Play Billing crate, which is part of crossbow, another multiplatform toolkit, this time designed to write Android/iOS games purely in Rust), but expect to have to write glue code in Kotlin once you’re off the beaten path.

Finally, the code

First, let’s take a look at the android glue code.

Above is another Android-specific element. start_app with no_mangle makes sure that the resulting library has the start_app symbol. This tandem of functions also ensures that it doesn’t expand outside the Rust code in case of panic when main is called.

Speaking of which, this is the main, or where our mobile and desktop code paths meet. dioxus_desktop::launch(app) is the entry point to the dioxus application and handles all the platform details behind the scenes.

The WebAssembly version also sets up logging, and instead calls the dioxus_web crate.

Finally, all platforms meet in the app function.

RSX

Dioxus uses its own meta-language to describe the UI. It is inspired by React and provides a way to get data into and out of the GUI. The Dioxus guide is a great place to learn about the syntax and capabilities of the language in a vacuum, while the example projects should give you an idea of how things look in less trivial cases.

Adding WebAssembly Support

The xbuild has left us in a state where we are almost ready to run our application as a web application, so let’s finish it off by adding an index.html file.

The important part here is the div with id=main. This is where trunk will place our application. Let’s install it first.

cargo install trunk

…and then use it to support our application.

trunk serve

You can add --address 0.0.0.0 if you’re in WSL, by default it listens on localhost. Either way, you should be able to see the app when connecting to port 8080.

Running the app

Other platforms should be supported out of the box. First, let’s see what “devices” x recognizes.

florek@desolace:/mnt/c/dev/try$ x devices
host Linux linux x64 Debian GNU/Linux 5.15.79.1-microsoft-standard-WSL2
adb:emulator-5554 emu64x android x64 Android 13 (API 33)

By default, x run starts on the first device, which is usually the host. To run it on an emulator, as in the above case, run.

x run --device adb::emulator-5554

Troubleshooting

As you can imagine, between android, web and everything else, there are a lot of moving parts. xbuild has a built-in diagnostic tool under the x doctor subcommand, where you can see all the tools that xbuild has detected, which should help with obvious deficiencies. For platform-specific problems, don’t shy away from resources, even if they don’t seem directly related to Rust. I, for example, found out that my debian installation had too old a version of gradle to build the app package, and all I had to do was download the latest version from their site.

Words by Mikołaj Florkiewicz, Senior Engineer

Editing by Kinga Kuśnierz, Content Writer

--

--

Altimetrik Poland Tech Blog

This is a Technical Blog of Altimetrik Poland team. We focus on subjects like: Java, Data, Mobile, Blockchain and Recruitment. Waiting for your feedback!