nitro 0.1.0
nitro: ^0.1.0 copied to clipboard
High-performance Native Modules for Flutter (Nitro Modules equivalent). Runtime support for .native.dart spec generated bridges.
nitro ๐ #
Zero-overhead native bindings for Flutter. nitro is the runtime layer of the Nitrogen SDK โ it provides the base classes, annotations, and Dart-side runtime primitives that make type-safe, zero-copy FFI plugins possible on iOS and Android with zero method-channel overhead.
This package is the runtime dependency. Plugin authors add it to their
pubspec.yaml. App developers pull it in transitively through any Nitrogen-powered plugin. The code generator lives innitro_generatorand the CLI innitrogen_cli.
Why Nitro? #
| Method Channel | FFI (manual) | Nitro | |
|---|---|---|---|
| Overhead per call | ~0.3 ms | ~0 ms | ~0 ms |
| Type safety | stringly-typed | manual | generated, strict |
| Async support | โ | manual isolates | โ generated |
| Streams | โ slow | manual SendPort | โ zero-copy |
| Zero-copy buffers | โ | manual | โ
via @HybridStruct |
| Code to write | lots | enormous | 3 files |
Requirements #
| Tool | Minimum version |
|---|---|
| Flutter SDK | 3.22.0+ |
| Dart SDK | 3.3.0+ |
| Android NDK | 26.1+ (r26b) |
| Kotlin | 1.9.0+ |
| iOS Deployment Target | 13.0+ |
| Swift | 5.9+ (Xcode 15+) |
| Xcode | 15.0+ |
Installation #
In your plugin's pubspec.yaml:
dependencies:
nitro: ^0.1.0
Then run:
flutter pub get
Core concepts #
1. HybridObject โ the base class #
Every Nitrogen plugin's public API extends HybridObject. You never instantiate it directly; the code generator produces a _XxxImpl hidden class that does the real FFI work.
// lib/src/math.native.dart โ the ONLY file you write
import 'package:nitro/nitro.dart';
part 'math.g.dart'; // โ generated
@NitroModule(ios: NativeImpl.swift, android: NativeImpl.kotlin)
abstract class Math extends HybridObject {
static final Math instance = _MathImpl(NitroRuntime.loadLib('math'));
// Synchronous FFI call โ executes in < 1 ยตs
double add(double a, double b);
// Async โ dispatched on a background isolate, returns to main isolate
@nitroAsync
Future<String> compute(String expression);
}
2. Annotations #
| Annotation | Where | Effect |
|---|---|---|
@NitroModule(ios:, android:) |
class | Marks an abstract class as a Nitrogen module spec |
@nitroAsync |
method | Generated code dispatches call to a background isolate |
@NitroStream(backpressure:) |
getter | Streams native events to Dart via Dart_PostCObject |
@HybridStruct(zeroCopy:) |
class | Turns a Dart class into a C-struct with optional zero-copy fields |
@HybridEnum(startValue:) |
enum | Maps a Dart enum to a C int32 enum |
@zeroCopy |
parameter | Marks a Uint8List param as a raw native pointer (no copy) |
3. @HybridStruct โ zero-copy data #
When a native method returns a large buffer (e.g. camera frame), mark the class with @HybridStruct and list the Uint8List fields that should be zero-copy:
@HybridStruct(zeroCopy: ['data'])
class CameraFrame {
final Uint8List data; // โ mapped as Pointer<Uint8>, no copy
final int width;
final int height;
final int stride; // bytes per row โ auto-detected as byte-length
final int timestampNs;
CameraFrame(this.data, this.width, this.height, this.stride, this.timestampNs);
}
The generator produces a final class _CameraFrameFfi extends Struct with correct @Int64() annotations, and a toDart() extension that calls data.asTypedList(stride) โ zero allocations, zero copies.
4. @NitroStream โ native โ Dart streaming #
@NitroModule(ios: NativeImpl.swift, android: NativeImpl.kotlin)
abstract class Camera extends HybridObject {
static final Camera instance = _CameraImpl(NitroRuntime.loadLib('camera'));
@NitroStream(backpressure: Backpressure.dropLatest)
Stream<CameraFrame> get frames; // 30fps camera frames, zero-copy
}
Backpressure options:
| Value | Behaviour |
|---|---|
Backpressure.dropLatest |
Drop new item if Dart hasn't consumed yet โ best for sensors/camera |
Backpressure.block |
Block the native thread until Dart consumes |
Backpressure.bufferDrop |
Ring buffer โ oldest item dropped when full |
Usage โ app developer side #
Once a Nitrogen plugin is added as a dependency, the API is completely type-safe Dart:
import 'package:my_camera/my_camera.dart';
// Sync call โ instant
final sum = Math.instance.add(3.14, 2.71);
// Async call โ runs on background isolate
final result = await Math.instance.compute('sqrt(144)');
print(result); // "12"
// Stream โ zero-copy frames at 30 fps
MyCamera.instance.frames.listen((frame) {
// frame.data is a Uint8List backed by native hardware memory โ NO copy
// frame.stride ร frame.height = total bytes
print('${frame.width}ร${frame.height} ${frame.data.length} bytes');
});
Usage โ plugin author side #
You write 3 files only:
1. lib/src/my_plugin.native.dart (Dart spec) #
import 'dart:typed_data';
import 'package:nitro/nitro.dart';
part 'my_plugin.g.dart';
@HybridStruct(zeroCopy: ['data'])
class ImageBuffer {
final Uint8List data;
final int stride; // auto-detected as length source
final int width;
final int height;
ImageBuffer(this.data, this.stride, this.width, this.height);
}
@NitroModule(ios: NativeImpl.swift, android: NativeImpl.kotlin)
abstract class MyPlugin extends HybridObject {
static final MyPlugin instance = _MyPluginImpl(NitroRuntime.loadLib('my_plugin'));
int add(int a, int b);
@nitroAsync
Future<String> processImage(String path);
@NitroStream(backpressure: Backpressure.dropLatest)
Stream<ImageBuffer> get frames;
}
Then run the generator (from your plugin root):
dart pub global run nitrogen_cli:nitrogen generate
2. android/.../MyPluginImpl.kt (Kotlin implementation) #
import nitro.myplugin_module.HybridMyPluginSpec
import nitro.myplugin_module.MyPluginJniBridge
import nitro.myplugin_module.ImageBuffer
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.delay
class MyPluginImpl : HybridMyPluginSpec {
override fun add(a: Long, b: Long): Long = a + b
override suspend fun processImage(path: String): String {
delay(100) // simulate native work
return "Processed: $path"
}
override val frames: Flow<ImageBuffer> = flow {
val buf = java.nio.ByteBuffer.allocateDirect(1920 * 1080 * 4)
while (true) {
emit(ImageBuffer(buf, 1920L * 4L, 1920L, 1080L))
delay(33) // ~30fps
}
}
}
class MyPluginPlugin : FlutterPlugin {
override fun onAttachedToEngine(binding: FlutterPluginBinding) {
MyPluginJniBridge.register(MyPluginImpl())
}
override fun onDetachedFromEngine(binding: FlutterPluginBinding) {}
}
3. ios/Classes/MyPluginImpl.swift (Swift implementation) #
import Flutter
import UIKit
import Combine
public class MyPluginImpl: NSObject, HybridMyPluginProtocol {
public func add(a: Int64, b: Int64) -> Int64 { a + b }
public func processImage(path: String) async throws -> String {
try await Task.sleep(nanoseconds: 100_000_000)
return "Processed: \(path)"
}
private let framesSubject = PassthroughSubject<ImageBuffer, Never>()
public var frames: AnyPublisher<ImageBuffer, Never> {
framesSubject.eraseToAnyPublisher()
}
override init() {
super.init()
let stride: Int64 = 1920 * 4
let buf = UnsafeMutablePointer<UInt8>.allocate(capacity: Int(stride * 1080))
Timer.scheduledTimer(withTimeInterval: 1.0 / 30.0, repeats: true) { [weak self] _ in
let frame = ImageBuffer(data: buf, stride: stride, width: 1920, height: 1080)
self?.framesSubject.send(frame)
}
}
}
public class MyPluginPlugin: NSObject, FlutterPlugin {
public static func register(with registrar: FlutterPluginRegistrar) {
MyPluginRegistry.register(MyPluginImpl())
}
}
Type mapping reference #
| Dart type | C type | Kotlin type | Swift type |
|---|---|---|---|
int |
int64_t |
Long |
Int64 |
double |
double |
Double |
Double |
bool |
int8_t |
Boolean |
Bool |
String |
const char* |
String |
String |
Uint8List |
uint8_t* |
ByteArray |
Data |
Uint8List + zeroCopy |
uint8_t* |
java.nio.ByteBuffer |
UnsafeMutablePointer<UInt8>? |
Future<T> |
N/A | suspend fun |
async throws |
Stream<T> |
SendPort reg. | Flow<T> |
AnyPublisher<T, Never> |
Repo structure #
nitro_ecosystem/
โโโ packages/
โ โโโ nitro/ โ this package (runtime: base classes, annotations, runtime)
โ โโโ nitro_generator/ โ code generator (build_runner builder)
โ โโโ nitrogen_cli/ โ CLI tool (nitrogen generate / init / doctor)
โโโ my_camera/ โ example plugin built with Nitrogen
โโโ lib/src/
โ โโโ my_camera.native.dart โ spec (author-written)
โ โโโ my_camera.g.dart โ generated FFI impl
โโโ android/ โ Kotlin implementation
โโโ ios/ โ Swift implementation
License #
MIT