Production-ready Flutter FFI plugin template with Rust backend
A complete, ready-to-use template for building high-performance Flutter applications with Rust. Clone, customize, and ship.
Flutter for UI. Rust for everything else.
- ⚡ Performance: FFI calls in ~50ns, native Rust speed, zero GC pauses
- 🔒 Security: Build from source, no prebuilt binaries, verify everything
- 🌍 Cross-platform: Android, iOS, macOS, Linux, Windows
- 🚀 Production-ready: Based on real production apps handling millions of users
- 🛠️ Zero config: Cargokit handles all platforms automatically
- 📦 Complete: Memory management, async operations, comprehensive examples
- 🎯 Pub-ready: Standard Flutter plugin structure, ready to publish
- ✅ Cross-platform support (5 platforms out-of-the-box)
- ✅ Automatic C header generation (cbindgen)
- ✅ Automatic Dart binding generation (ffigen)
- ✅ Memory management utilities (auto-free patterns)
- ✅ Type-safe FFI with convenient extensions
- ✅ Async operations with isolated ports (no global state, truly parallel)
- ✅ Working example app demonstrating all features
- ✅ Helper scripts for development workflow
- ✅ Comprehensive tests
- ✅ Easy package renaming with automated script
Required:
# Install Rust (must use rustup, not package managers)
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# Verify Flutter
flutter doctorPlatform-specific:
- Android: Android Studio + SDK (NDK auto-installed)
- iOS/macOS: Xcode + Command Line Tools
- Linux: CMake, Ninja, Clang
- Windows: Visual Studio 2019+ with C++ tools
1. Clone this template
cd your_flutter_app
git clone https://github.com/yourusername/app_core.git
cd app_core2. Add to your pubspec.yaml
dependencies:
app_core:
path: ./app_core3. Get dependencies
flutter pub get4. Initialize in your app
import 'package:flutter/material.dart';
import 'package:app_core/app_core.dart';
void main() {
initAppCore(); // Initialize FFI runtime
runApp(MyApp());
}5. Use FFI functions
// Math operations
final sum = AppCore.sum(10, 20); // 30
final product = AppCore.multiply(5, 6); // 30
// String operations
final reversed = AppCore.reverseString('hello'); // 'olleh'
final upper = AppCore.toUppercase('flutter'); // 'FLUTTER'
final greeting = AppCore.helloWorld(); // 'Hello from Rust!'
// Get version
final version = AppCore.getVersion(); // '0.1.0'6. First build
flutter run
# First build: 5-10 minutes (compiling Rust dependencies)
# Subsequent builds: <1 minute (cached)app_core/
├── lib/ # Dart code
│ ├── app_core.dart # Main API
│ └── src/ # Extensions, platform loader
│
├── rust/ # Rust library
│ ├── src/
│ │ ├── ffi/ # FFI utilities (memory, types, runtime)
│ │ ├── examples/ # Example FFI functions
│ │ └── lib.rs # Library entry
│ ├── Cargo.toml
│ ├── cbindgen.toml # C header generation config
│ └── build.rs # Auto-generates C header
│
├── cargokit/ # Cross-platform Rust builds
├── android/ # Gradle + Cargokit
├── ios/ # CocoaPods + Cargokit
├── macos/ # CocoaPods + Cargokit
├── linux/ # CMake + Cargokit
├── windows/ # CMake + Cargokit
├── test/ # Dart tests
│
├── example/ # Demo app
│ └── lib/main.dart
│
├── scripts/
│ ├── regen_bindings.sh # Rebuild + regenerate bindings
│ ├── test_all.sh # Run all tests
│ └── rename_package.sh # Rename package to your own name
│
├── pubspec.yaml # Flutter package config
├── ffigen.yaml # Dart binding generation config
│
└── docs/
└── CREATING_YOUR_OWN.md # Tutorial: Build your own FFI plugin
This is a standard Flutter plugin structure, ready for pub.dev publishing.
// rust/src/examples/my_feature.rs
use std::ffi::c_char;
use crate::ffi::{CstrToRust, RustToCstr};
#[no_mangle]
pub extern "C" fn greet_user(name: *const c_char) -> *mut c_char {
let name = name.to_native(); // Auto-frees Dart memory
format!("Hello, {}!", name).to_cstr() // Rust allocates
}Add to rust/src/examples/mod.rs:
pub mod my_feature;./scripts/regen_bindings.shThis automatically:
- Builds Rust (
cargo build) - Generates C header (
build.rs→cbindgen) - Generates Dart bindings (
ffigen)
// Direct FFI binding
final greeting = bindings.greet_user('Alice'.toPtr()).toStr();
// Or wrap in AppCore class
class AppCore {
static String greetUser(String name) {
return bindings.greet_user(name.toPtr()).toStr();
}
}Rust side:
#[no_mangle]
pub extern "C" fn process_data(input: *const c_char) -> *mut c_char {
let data = input.to_native(); // Auto-frees Dart memory
let result = do_processing(&data);
result.to_cstr() // Rust allocates, Dart will free
}Dart side:
final result = bindings.process_data(input.toPtr()).toStr();
// Both input and result automatically freedlet data = input.to_native_no_free(); // Dart still owns memoryfinal inputPtr = input.toPtr();
final resultPtr = bindings.process_data(inputPtr);
final result = resultPtr.toStrNoFree();
// Manual cleanup
bindings.free_c_string(resultPtr);
malloc.free(inputPtr);Rust side:
use irondash_dart_ffi::DartPort;
#[no_mangle]
pub extern "C" fn fetch_data_async(url: *const c_char, port: i64) {
let url = url.to_native();
let dart_port = DartPort::new(port);
crate::ffi::spawn(async move {
let result = simulate_network_fetch(&url).await;
dart_port.send(result); // Send directly to isolated port
});
}Dart side:
// Clean usage - just await!
final result = await AppCore.fetchDataAsync('https://example.com/api');
print('Got result: $result');
// With error handling
try {
final data = await AppCore.processAsync('some data');
print('Processed: $data');
} catch (e) {
print('Error: $e');
}How it works:
- Dart creates isolated
ReceivePortfor each call - Passes port to Rust function
- Rust spawns async task on multi-threaded runtime (uses all CPU cores)
- Rust sends result directly to that port
- Port auto-closes after receiving
- Truly parallel - multiple calls run simultaneously across cores
No global state, no request IDs, no complexity!
For CPU-intensive work that blocks:
// Dart side - runs in isolate to prevent UI freezing
final result = await AppCore.fibonacci(40); // Runs on separate isolate
// Implementation uses ffiCompute
static Future<int> fibonacci(int n) {
return ffiCompute(_fibonacciWorker, n);
}
static int _fibonacciWorker(int n) {
return bindings.fibonacci(n); // Blocking Rust call
}Rust side:
#[no_mangle]
pub extern "C" fn fibonacci(n: u64) -> u64 {
// Blocking, CPU-intensive work
match n {
0 => 0,
1 => 1,
_ => fibonacci(n - 1) + fibonacci(n - 2),
}
}When to use:
- ✅ CPU-intensive computations
- ✅ Blocking operations (no tokio)
- ✅ Long-running synchronous work
- ❌ Don't use for async I/O (use
ffiAsyncinstead)
| Platform | Arch | Status |
|---|---|---|
| Android | arm64-v8a, armeabi-v7a, x86_64, x86 | ✅ |
| iOS | arm64, x86_64 (sim) | ✅ |
| macOS | arm64, x86_64 | ✅ |
| Linux | x86_64, arm64 | ✅ |
| Windows | x86_64 | ✅ |
cd example
flutter runThe example app demonstrates:
- Math operations (sum, multiply)
- String manipulation (reverse, uppercase, concatenate)
- Memory management (automatic free)
- Async operations with isolated ports (truly parallel)
- Cross-platform FFI integration
# Add Rust function
vim rust/src/examples/my_feature.rs
# Regenerate bindings
./scripts/regen_bindings.sh
# Test
./scripts/test_all.sh
# Run example
cd example && flutter runThe easiest way to rename this package:
./scripts/rename_package.sh my_awesome_package MyAwesomePackage my_awesome_coreThis automatically updates:
- Rust package name in
Cargo.toml - All platform configurations (Android, iOS, macOS, Linux, Windows)
- Dart package references
- Library loader
- Example app
If you prefer to rename manually:
1. Update Rust:
# rust/Cargo.toml
[package]
name = "my_core"2. Update platform configs:
// android/build.gradle
cargokit {
libname = "my_core"
}# ios/app_core.podspec (and macos/)
:script => 'sh "$PODS_TARGET_SRCROOT/../cargokit/build_pod.sh" ../rust my_core',
:output_files => ["${BUILT_PRODUCTS_DIR}/libmy_core.a"],# linux/CMakeLists.txt (and windows/)
apply_cargokit(${PLUGIN_NAME} ${CMAKE_CURRENT_SOURCE_DIR}/../rust my_core)// lib/src/platform_loader.dart
const String _libName = 'my_core';3. Rebuild:
cd rust && cargo build
dart run ffigenRust:
# rust/Cargo.toml
[dependencies]
reqwest = { version = "0.11", features = ["json"] }Dart:
# pubspec.yaml
dependencies:
http: ^1.0.0# Rust tests
cd rust && cargo test
# Dart tests
flutter test
# Run all tests
./scripts/test_all.sh- FFI call overhead: ~50-100ns
- String conversion: ~1-2μs
- Memory allocation: ~100ns
| Type | First Build | Incremental |
|---|---|---|
| Debug | 3-5 min | 10-30 sec |
| Release | 5-10 min | 30-60 sec |
| Android (all ABIs) | 10-15 min | 1-2 min |
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source $HOME/.cargo/envflutter clean
flutter build apk # Cargokit auto-installs NDKrustup target add x86_64-apple-ios aarch64-apple-ios-simThis is normal! First build compiles all Rust dependencies (5-10 min). Subsequent builds use cache and are much faster (<1 min).
- Ensure
initAppCore()is called before using FFI functions - Check that first build completed successfully
- 📚 Creating Your Own FFI Plugin - Complete tutorial
- 🦀 Rust FFI Guide - Official Rust docs
- 🎯 Dart FFI - Official Dart docs
- 🛠️ Cargokit - Cross-platform Rust builds
Contributions welcome! Please:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing) - Commit changes (
git commit -m 'Add amazing feature') - Push to branch (
git push origin feature/amazing) - Open a Pull Request
MIT License - see LICENSE for details
- Cargokit by irondash
- Inspired by real-world production Flutter + Rust apps
- Built with ❤️ for the Flutter and Rust communities
Build from source. Ship with confidence. 🦀💙
Need help? Open an issue or check the tutorial!