Skip to content

Instantly share code, notes, and snippets.

@bus710
Last active September 28, 2024 10:37
Show Gist options
  • Save bus710/94a3c0e0d93aa9b522a2e2d21da7f99e to your computer and use it in GitHub Desktop.
Save bus710/94a3c0e0d93aa9b522a2e2d21da7f99e to your computer and use it in GitHub Desktop.
Building rust native library to be used by flutter/android

Building rust native library to be used by flutter/android

This short doc shows how to build rust to be used by flutter/android app.
Inspired by hyousef's work but added some details for the workflow.



References

Docs:

Conversations:



Prerequisites

  • Ubuntu 18.04 or later
  • Rust SDK
  • Android SDK, NDK, LLVM, and CMake
  • Flutter SDK as beta channel



1. Config NDK achiver and linker location

We don't need to generate standalone NDK tools anymore but need to specify where the prebuilt tools are.

To find the archiver's location:

$ find -name "*aarch64-linux-android-ar*"
./Android/Sdk/ndk/21.0.6113669/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android-ar

To find the linker's location:

$ find -name "*aarch64-linux-android29-clang*"
./Android/Sdk/ndk/21.0.6113669/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android29-clang

Create a config file under ~/.cargo:

$ vi ~/.cargo/config

Then fill the file with:

[target.aarch64-linux-android]
ar = "Android/Sdk/ndk/21.0.6113669/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android-ar"
linker = "Android/Sdk/ndk/21.0.6113669/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android29-clang"



2. Target architecture

This needs to be done once per host machine.

$ rustup target add aarch64-linux-android



3. Create a new rust library project

$ mkdir rustylib && cd rustylib
$ cargo init --name rustylib --lib



4. Edit Cargo.toml

[package]
name = "rustylib"
version = "0.1.0"
authors = ["bus710 <[email protected]>"]
edition = "2018"

[lib]
name = "rustylib"
crate-type = ["dylib"] # could be `staticlib` as well

[dependencies]



5. Edit lib.rs

This function accepts an int and returns the input's power value.

#[no_mangle]
pub extern fn rust_fn(x: i32) -> i32 {   
    println!("Hello from rust\nI'll return: {}", x.pow(2));
    x.pow(2)
}



6. Build it

Build the code but target the architecture:

$ cargo build --target aarch64-linux-android --release

Then there are files as output:

$ tree
.
├── cargo-config.toml
├── Cargo.lock
├── Cargo.toml
├── src
│   └── lib.rs
└── target
    └── aarch64-linux-android
        └── release
            ├── build
            ├── deps
            │   ├── librustylib.so
            │   └── rustylib.d
            ├── examples
            ├── incremental
            ├── librustylib.d
            └── librustylib.so

The librustylib.so is shared library as the ultimate output to be copied to and used by Flutter app.

To check the format:

$ file target/aarch64-linux-android/release/librustylib.so
target/aarch64-linux-android/release/librustylib.so: ELF 64-bit LSB shared object, ARM aarch64, version 1 (SYSV), dynamically linked, with debug_info, not stripped



7. Config flutter/android

A new flutter project should be created:

$ cd ..
$ flutter create caller
$ tree -L 2
.
├── caller
│   ├── android
│   ├── build
│   ├── caller.iml
│   ├── ios
│   ├── lib
│   ├── pubspec.lock
│   ├── pubspec.yaml
│   ├── README.md
│   ├── test
│   └── web
└── rustylib
    ├── Cargo.lock
    ├── Cargo.toml
    ├── src
    └── target



7.1 Copy the shared library

Create a directory and copy the shared library file to the new directory:

$ mkdir -p caller/android/app/src/main/jniLibs/arm64-v8a
$ cp rustylib/target/aarch64-linux-android/release/librustylib.so \
    caller/android/app/src/main/jinLibs/arm64-v8a



7.2 Edit build.gradle

Open the build.gradle under android/app:

$ vi caller/android/app/build.gradle

Add a line in the android/sourceSets block:

android {
    compileSdkVersion 28

    sourceSets {
        main.java.srcDirs += 'src/main/kotlin'
        main.jniLibs.srcDirs = ['src/main/jniLibs'] // this!
    }
    ...
}



8. Call it from flutter

Below snippet shows how to use the rust library from flutter without kotlin wrapper:

import 'package:flutter/material.dart';
import 'dart:ffi';

// FFI signature of the hello_world C function
typedef ffi_func = Int32 Function(
    Int32 x); //pub extern fn rust_fn(x: i32) -> i32
// Dart type definition for calling the C foreign function
typedef dart_func = int Function(int x);

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);
  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  String path;
  DynamicLibrary dylib;
  Function myFunc;

  @override
  void initState() {
    // Open the dynamic library
    path = 'librustylib.so';
    dylib = DynamicLibrary.open(path);

    // Look up the Rust/C function
    myFunc = dylib
        .lookup<NativeFunction<ffi_func>>('rust_fn')
        .asFunction<dart_func>();

    super.initState();
  }

  void _incrementCounter() {
    setState(() {
      _counter++;
      debugPrint('Double of 3 is ${myFunc(3)}');
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }
}

So there will be message "Double of 3 is 9" in the debug console whenever the floating button is pressed.



8. What's next?

To automate this little tedious steps, please refer this repo by brickpop:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment