This guide will walk you through the process of creating a Flutter plugin that uses Rust code via FFI (Foreign Function Interface). It consists of several steps:
- Flutter 2.5.0 or later
- Rust 1.55.0 or later
- cargo-make 0.35.6 or later
Note: When you see
<flutter_lib_name>
, replace it with the name of your Flutter library. When you see<rust_lib_name>
, replace it with the name of your Rust library.
Use the flutter create
command with the appropriate flags to create a new plugin project. This will generate the necessary files and directories for the plugin, including platform-specific code.
$ flutter create -t plugin_ffi --platforms macos <flutter_plugin_name>
$ cd <flutter_plugin_name>
With the cargo new
command, create a new Rust library project. This will generate a new directory with the Rust library's source code and configuration files.
$ cargo new --lib <rust_lib_name>
$ cd <rust_lib_name>
$ cargo check
Create a build.rs
file to configure the Rust library for FFI compatibility. You'll need to use the cbindgen
crate to generate the appropriate C-compatible header files. Also, modify the Cargo.toml
file to include the necessary build settings and dependencies.
Create build.rs:
// build.rs
use cbindgen;
use std::env;
fn main() {
let cargo_manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
let config = cbindgen::Config {
language: cbindgen::Language::C,
braces: cbindgen::Braces::SameLine,
style: cbindgen::Style::Both,
..Default::default()
};
cbindgen::Builder::new()
.with_crate(&cargo_manifest_dir)
.with_config(config)
.with_no_includes()
.generate()
.expect("Unable to generate bindings")
.write_to_file("<rust_lib_name>");
}
Edit Cargo.toml:
[package]
name = "<rust_lib_name>"
version = "0.1.0"
edition = "2021"
build = "build.rs"
[dependencies]
[lib]
crate-type = ["cdylib", "staticlib"]
[build-dependencies]
cbindgen = "0.24.3"
Write the Rust code for the functions you want to expose to the Flutter plugin. Make sure to use the #[no_mangle] attribute and the extern "C"
keyword for each function. This ensures the correct binary code is generated for FFI compatibility. Compile the Rust code with cargo build --release
.
Edit <rust_lib_name>/src/lib.rs
:
use std::ffi::c_int;
use std::thread;
use std::time::Duration;
#[no_mangle]
pub extern "C" fn sum(a: c_int, b: c_int) -> c_int {
a + b
}
#[no_mangle]
pub extern "C" fn sum_long_running(a: c_int, b: c_int) -> c_int {
// Simulate a long running task
thread::sleep(Duration::from_millis(5000));
a + b
}
$ cargo build --release
Generate FFI bindings: Update the ffigen.yaml
file to point to the Rust library's header file, and then use the ffigen
command to generate the Dart FFI bindings.
Edit ffigen.yaml
:
headers:
entry-points:
- "<rust_lib_name>/<rust_lib_name>.h"
$ cd ..
$ flutter pub run ffigen --config ffigen.yaml
For each platform that the plugin supports, configure the platform-specific settings to include the compiled Rust library. This may involve updating the platform-specific configuration files and copying the compiled Rust library to the appropriate directories.
Remove macos/Classes/<flutter_lib_name>.c
.
$ rm macos/Classes/<flutter_lib_name>.c
Copy <rust_lib_name>/target/release/lib<rust_lib_name>.dylib
to macos/
.
$ cp <rust_lib_name>/target/release/lib<rust_lib_name>.dylib macos/
Edit macos/<flutter_lib_name>.podspec
to include the Rust library in the podspec's vendored_libraries property.
Pod::Spec.new do |s|
# ... other config
s.vendored_libraries = 'lib<rust_lib_name>.dylib'
end
In the android/build.gradle file, comment or remove the following lines related to the shared CMake build with the Android Gradle Plugin:
// Invoke the shared CMake build with the Android Gradle Plugin.
externalNativeBuild {
cmake {
path "../src/CMakeLists.txt"
}
}
In Android Studio, ensure that the NDK and CMake are installed. See image below.
Install cargo-ndk
$ cargo install cargo-ndk
Add the necessary targets:
$ rustup target add aarch64-linux-android armv7-linux-androideabi
Use the following command to generate the binaries (there's a task in the Makefile.toml that handles this):
$ cargo ndk -t arm64-v8a -t armeabi-v7a -o ../android/src/main/jniLibs build --release
Modify the plugin's Dart code to use the generated FFI bindings and the Rust library. Make sure to specify the correct dynamic library name for each platform.
Edit lib/<flutter_ffi_plugin>.dart
const String _libName = 'lib<rust_lib_name>';
/// The dynamic library in which the symbols for [DashFfiPluginBindings] can be found.
final DynamicLibrary _dylib = () {
if (Platform.isMacOS) {
return DynamicLibrary.open('$_libName.dylib');
} else if (Platform.isAndroid) {
return DynamicLibrary.open('$_libName.so');
} else {
throw UnsupportedError('Unknown platform: ${Platform.operatingSystem}');
}
}();
Create a Makefile.toml
file in the Rust plugin's root directory. This file contains instructions for compiling the Rust code and copying the resulting library to the corresponding directories in the Flutter application. Use cargo-make
to automate the build process.
By following these steps, you'll be able to create a Flutter plugin that uses Rust code via FFI, allowing you to leverage the performance and safety features of Rust in your Flutter applications.