nitro_generator 0.1.2 copy "nitro_generator: ^0.1.2" to clipboard
nitro_generator: ^0.1.2 copied to clipboard

Code generator for Nitro Modules (Nitrogen). Converts *.native.dart specs to Dart FFI, Kotlin, Swift, and C++ bindings.

nitro_generator 🔬 #

Code generator for Nitrogen plugins. nitro_generator is a build_runner builder that reads your *.native.dart spec file and generates type-safe Dart FFI bindings, Kotlin JNI bridges, Swift @_cdecl bridges, and C headers — all in one pass.


Requirements #

Tool Minimum
Dart SDK 3.3.0+
build_runner 2.4.0+

Installation #

Add to your plugin package's pubspec.yaml (not the end-user app):

dependencies:
  nitro: ^0.1.0          # runtime annotations

dev_dependencies:
  nitro_generator: ^0.1.0       # code generator
  build_runner: ^2.4.0

Tip: Prefer the CLI instead — nitrogen_cli lets you generate without adding build_runner to dev dependencies.


How it works #

my_plugin.native.dart
        │
        ▼ nitro_generator (build_runner / CLI)
        │
        ├── my_plugin.g.dart          Dart FFI impl class
        ├── my_plugin.bridge.g.kt     Kotlin JNI bridge + spec interface
        ├── my_plugin.bridge.g.swift  Swift @_cdecl bridge + protocol
        └── my_plugin.bridge.g.h      C header (extern "C" declarations)

Regenerating is always safe — all generated files begin with:

// Generated by Nitrogen Modules. Do not edit.

Commit them to VCS so consumers without build_runner can build normally.


Usage #

Step 1 — write my_plugin.native.dart #

import 'dart:typed_data';
import 'package:nitro/nitro.dart';

part 'my_plugin.g.dart';  // generated output

// ── Optional: zero-copy data struct ──────────────────────────────────────────
@HybridStruct(zeroCopy: ['data'])
class VideoFrame {
  final Uint8List data;   // zero-copy — no malloc, no memcpy
  final int stride;       // bytes per row (auto-detected as byte length)
  final int width;
  final int height;
  final int timestampNs;
  VideoFrame(this.data, this.stride, this.width, this.height, this.timestampNs);
}

// ── Optional: native enum ─────────────────────────────────────────────────────
@HybridEnum(startValue: 0)
enum Quality { low, medium, high, ultra }

// ── Module spec ───────────────────────────────────────────────────────────────
@NitroModule(ios: NativeImpl.swift, android: NativeImpl.kotlin)
abstract class MyPlugin extends HybridObject {
  static final MyPlugin instance = _MyPluginImpl();

  // Synchronous — direct FFI, sub-microsecond
  int add(int a, int b);
  double multiply(double x, double y);

  // Async — dispatched on a background isolate
  @nitroAsync
  Future<String> processFile(String path, Quality quality);

  // Stream — native events pushed to Dart via SendPort (zero overhead)
  @NitroStream(backpressure: Backpressure.dropLatest)
  Stream<VideoFrame> get frames;

  // Properties — get/set via named C symbols
  double get zoom;
  set zoom(double value);
}

Step 2 — generate #

# With build_runner:
dart run build_runner build --delete-conflicting-outputs

# Or with the CLI (recommended):
dart pub global activate nitrogen_cli
nitrogen generate

Step 3 — look at what was generated #

my_plugin.g.dart — Pure Dart FFI implementation:

// Generated by Nitrogen Modules. Do not edit.
part of 'my_plugin.native.dart';

// dart:ffi struct layout matching the C struct
final class _VideoFrameFfi extends Struct {
  external Pointer<Uint8> data;
  @Int64() external int stride;
  @Int64() external int width;
  @Int64() external int height;
  @Int64() external int timestampNs;
}

extension _VideoFrameFfiExt on _VideoFrameFfi {
  VideoFrame toDart() => VideoFrame(
    data.asTypedList(stride), // ← zero-copy backed Uint8List
    stride, width, height, timestampNs,
  );
}

class _MyPluginImpl extends MyPlugin {
  // ... FFI lookupFunction pointers, implementations
  @override
  Stream<VideoFrame> get frames => NitroRuntime.openStream<VideoFrame>(
    register: (port) => _registerFramesPtr(port),
    unpack: (rawPtr) => Pointer<_VideoFrameFfi>.fromAddress(rawPtr).ref.toDart(),
    release: (port) => _releaseFramesPtr(port),
    backpressure: Backpressure.dropLatest,
  );
}

my_plugin.bridge.g.kt — Kotlin contract + JNI bridge:

// Generated by Nitrogen Modules. Do not edit.

@Keep
data class VideoFrame(
  val data: java.nio.ByteBuffer,  // ← DirectByteBuffer, zero-copy
  val stride: Long, val width: Long, val height: Long, val timestampNs: Long
)

interface HybridMyPluginSpec {
  fun add(a: Long, b: Long): Long
  suspend fun processFile(path: String, quality: Int): String
  val frames: Flow<VideoFrame>
  var zoom: Double
}

@Keep object MyPluginJniBridge {
  fun register(impl: HybridMyPluginSpec) { ... }
  // JNI @JvmStatic methods, Flow collection coroutines, Job cancellation
}

my_plugin.bridge.g.swift — Swift protocol + C-bridge stubs:

// Generated by Nitrogen Modules. Do not edit.
import Foundation
import Combine

public struct VideoFrame {
  public var data: UnsafeMutablePointer<UInt8>?  // ← zero-copy pointer
  public var stride: Int64
  public var width: Int64; public var height: Int64; public var timestampNs: Int64
}

public protocol HybridMyPluginProtocol: AnyObject {
  func add(a: Int64, b: Int64) -> Int64
  func processFile(path: String, quality: Int64) async throws -> String
  var frames: AnyPublisher<VideoFrame, Never> { get }
  var zoom: Double { get set }
}

// Registry — pure Swift, no @objc / NSObject needed
public class MyPluginRegistry {
  public static var impl: HybridMyPluginProtocol?
  public static func register(_ impl: HybridMyPluginProtocol) { ... }
}

// C-bridge stubs — exported as C symbols, called by the generated .cpp shim
@_cdecl("_call_add")
public func _call_add(_ a: Int64, _ b: Int64) -> Int64 {
  return MyPluginRegistry.impl?.add(a: a, b: b) ?? 0
}

// Async functions use DispatchSemaphore + Task.detached to bridge async → sync C ABI
@_cdecl("_call_processFile")
public func _call_processFile(_ path: String, _ quality: Int64) -> String {
  guard let impl = MyPluginRegistry.impl else { return "" }
  let sema = DispatchSemaphore(value: 0)
  var result: String? = nil
  Task.detached { result = try? await impl.processFile(path: path, quality: quality); sema.signal() }
  sema.wait()
  return result ?? ""
}

What @HybridStruct generates #

For a zero-copy field like Uint8List data with a sibling int stride field, the generator automatically detects stride as the byte-length source and emits:

data.asTypedList(stride),  // zero-copy; lifetime = this struct

Auto-detected length field names: length, size, stride, bytelength, bytelen, len

If no matching sibling exists, it emits a TODO comment so you can fill in the correct expression.


Spec validator rules #

The generator validates your spec before generating and will exit with helpful errors:

Level Rule
ERROR @HybridStruct field type is not int/double/bool/String/Uint8List
ERROR @nitroAsync method does not return Future<T>
ERROR @NitroStream getter does not return Stream<T>
ERROR Class does not extend HybridObject
ERROR zeroCopy field name in @HybridStruct does not match any declared field
WARNING Large @HybridStruct returned synchronously — consider @nitroAsync
WARNING High-frequency stream with String item type — prefer Uint8List

File naming convention #

File Author Notes
Foo.native.dart Plugin author Spec — triggers codegen
Foo.g.dart Generated Dart FFI impl — commit to VCS
Foo.bridge.g.kt Generated Kotlin stub — commit to VCS
Foo.bridge.g.swift Generated Swift stub — commit to VCS
Foo.bridge.g.h Generated C header — commit to VCS
FooImpl.kt Plugin author Real Kotlin implementation
FooImpl.swift Plugin author Real Swift implementation

  • nitro — Runtime (base classes, annotations, runtime helpers)
  • nitrogen_cli — CLI tool (generate / init / doctor)
0
likes
0
points
60
downloads

Documentation

Documentation

Publisher

verified publishershreeman.dev

Weekly Downloads

Code generator for Nitro Modules (Nitrogen). Converts *.native.dart specs to Dart FFI, Kotlin, Swift, and C++ bindings.

Homepage
Repository (GitHub)
View/report issues

Topics

#codegen #ffi #native #cross-platform #bridge

License

unknown (license)

Dependencies

analyzer, build, dart_style, nitro, path, source_gen

More

Packages that depend on nitro_generator