Android / iOS app with shared Rust logic
This is an example that shows how to communicate with a shared Rust library from an Android and iOS app. The idea is to be able to share domain logic & most services (networking, database, bluetooth, etc.) using Rust and use the native SDKs for presentation and platform specific services.
This approach gives us the best of all worlds! We prevent code duplication by using a shared library. Rust, as a highly performant and safe language is a great fit for mobile. We keep a fully native UI experience and uncomplicated access to the latest APIs of the platforms.
Calls Rust functions from Android / iOS, shows returned vale in UI.
Passes callback to Rust, update UI from callback with result.
Subscribes in Android / iOS to events triggered by Rust and updates UI with them.
UI sends event to Rust. Rust emits to channel that is observed in Android / iOS. This allows to e.g. implement MVVM architecture writing the view models in Rust (with some glue to convert the observer-callbacks in observables/channels/SwiftUI/Jetpack compose/etc).
Pass serialized Objects between Android / iOS and Rust using JSON
Android / iOS instructions
List available targets:
rustup target list
Show currently installed targets:
rustup toolchain list
The Rust sources are here
These steps show how to build and run an Android app in debug mode for a 64 bits emulator.
Ensure the NDK is installed.
ANDROID_TOOLCHAINS=<Directory where targets should be installed> ANDROID_NDK=<NDK's root directory>
rustup target add x86_64-linux-android
Add path to linker
- Update linker path in Cargo's config.
[target.x86_64-linux-android] linker = "<Directory where targets were installed (provided in environment variable)>/android-29-x86_64-4.9/bin/clang"
Ensure adb is installed.
adb shell am start -n com.schuetz.rust_android_ios/com.schuetz.rust_android_ios.MainActivity
Start the app in the emulator / device!
Run the project in Android Studio. This will build, install and run.
Relevant configuration files
If you want to add targets or tweak the configuration, you have to edit one or more of these files:
App's Gradle config: This contains the apk settings (like application id, sdk version) and build steps. It builds for the provided architectures using cargo and puts the generated shared libraries (.so files) in the expected directories. If you want to build for a new target, add it here. The target's key is the folder name where the shared library will be put, and the value is the toolchain's name used by rustup.
Cargo config: Contains linker paths for targets.
build.rs: This is a script invoked by Cargo before everything else. For Android, it's used to tell rust_swig to generate the glue files for Java interop. If you change the app's package structure / names, you have to update this file accordingly. It also sets the import to use for the
use_null_annotation_from_package). If you're using a recent Android SDK version, you don't need to change it.
You edited something in Rust! Now you have to:
Build, install, run, as described above.
The code of the Android app can be found here. This is a regular Android app which you can work with normally. Just don't modify the generated JNI files and remember to update build.rs as described in Relevant configuration files, if you change the package structure.
The iOS project is here.
rustup target add x86_64-apple-ios
Build & run
From the project's root directory:
cargo build --target=x86_64-apple-ios
This will generate the required library files:
<project root directory>/target/mobileapp-ios.h with the C headers and the (static) library for the target,
<project root directory>/target/<target name>/libmobcore.a. At the moment you have to copy manually libmobcore.a into the iOS app's directory each time you update it.
With the header and library in place, you can run the iOS app.
You edited something in Rust! Now you have to:
Update the C glue Rust implementation file. Orient with the existing code. Differently to Android the glue has to be written manually, because there's no library like rust_swig.
Build as described in "Build & run". Cargo will invoke build.rs, which uses cbindgen to generate the iOS library files.
Copy manually the generated
<project root directory>/target/<target name>/libmobcore.ato the iOS app project's root folder.
The code of the iOS app can be found here. This is a regular iOS app which you can work with normally.
- Commit changes to a branch in your fork
- Push your code and make a pull request
- Pass / return struct pointers, casting / mapping to iOS structs and Kotlin classes (partly done already for iOS).
- Avoid using global variables in iOS app.
- Automate copying of libmobcore.a or reference properly & multiple targets.