Building an Android App with Rust Using UniFFI
Developing Android applications is usually associated with languages like Java or Kotlin. However, the rising popularity of Rust, a systems programming language celebrated for its safety and performance, combined with UniFFI, an interface generator for Rust, opens new avenues for Android app development. In this blog post, we delve into the process of building a simple Android app using Rust and UniFFI.
Prerequisites
Before starting, make sure you have the following tools installed:
- Rust: Obtain it from the official Rust website.
- Android Studio: Download from the official site.
Step 1: Setting Up Your Rust Library
Begin by creating a new Rust library:
cargo new app --lib
In your Cargo.toml
file, add the uniffi
dependency:
[lib]
crate_type = ["cdylib"]
name = "mobile"
[dependencies]
uniffi = { version = "0.25.3", features = [ "cli" ] }
Modify your lib.rs
file. Here's an example:
uniffi::setup_scaffolding!();
#[uniffi::export]
fn say_hi() -> String {
"Hello from Rust on Android!".to_string()
}
Include the uniffi-bindgen
CLI tool:
fn main() {
uniffi::uniffi_bindgen_main()
}
Build the library:
cargo build
Step 2: Preparing for Android Build
Set up cargo-ndk
for cross-compiling:
cargo install cargo-ndk
Add targets for Android:
rustup target add \
aarch64-linux-android \
armv7-linux-androideabi \
i686-linux-android \
x86_64-linux-android
Step 3: Building Libraries for Android
Create your Android project in Android Studio.
Compile the dynamic libraries in ./app/src/main/jniLibs
(next to java
and res
directories):
cargo ndk -o ./app/src/main/jniLibs \
--manifest-path ./Cargo.toml \
-t armeabi-v7a \
-t arm64-v8a \
-t x86 \
-t x86_64 \
build --release
Step 4: Generating Kotlin Bindings
Generate the bindings for Kotlin:
cargo run --bin uniffi-bindgen generate --library ./target/debug/libmobile.dylib --language kotlin --out-dir ./app/src/main/java/tech/forgen/todolist/rust
Step 5: Setting up Your Android Project
Create a new Android project in Android Studio.
In the project, import the Kotlin binding files and the compiled libraries from the jniLibs
directory.
Step 6: Integrating Rust Functions in Android
Add these imports to your build.gradle
file:
dependencies {
// ...
// Uniffi
implementation("net.java.dev.jna:jna:5.7.0@aar")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4")
}
Edit your main activity or any Kotlin file to use the Rust functions. For example:
import tech.forgen.todolist.rust.Mobile
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val message = Mobile.sayHi() // Calling the Rust function
Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
}
}
Putting Everything Together
Create a script named build-android.sh
to automate the Rust library building process for various Android targets.
#!/bin/bash
# Set up cargo-ndk and add the Android targets
cargo install cargo-ndk
rustup target add aarch64-linux-android armv7-linux-androideabi i686-linux-android x86_64-linux-android
# Build the dylib
cargo build
# Build the Android libraries in jniLibs
cargo ndk -o ./app/src/main/jniLibs \
--manifest-path ./Cargo.toml \
-t armeabi-v7a \
-t arm64-v8a \
-t x86 \
-t x86_64 \
build --release
# Create Kotlin bindings
cargo run --bin uniffi-bindgen generate --library ./target/debug/libmobile.dylib --language kotlin --out-dir ./app/src/main/java/tech/forgen/todolist/rust
This approach simplifies the process of integrating Rust into your Android development, leveraging the advantages of Rust's safety and performance in your Android apps.