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!