Flutter
On-device ML inference for Flutter applications
The Flutter SDK (xybrid_flutter) provides Dart bindings to the Xybrid Rust core via FFI, enabling on-device ML inference in mobile and desktop applications.
Installation
Add to your pubspec.yaml:
dependencies:
xybrid_flutter:
git:
url: https://github.com/xybrid-ai/xybrid-flutter.git
ref: mainInitialization
Initialize the Rust library before using any Xybrid APIs:
import 'package:xybrid_flutter/xybrid_flutter.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Xybrid.init();
runApp(MyApp());
}Core Concepts
Loader → Model → Run Pattern
The SDK uses a consistent pattern for all inference:
- Create a Loader - Specify the model source (registry, bundle, or directory)
- Load - Download (if needed) and initialize the model
- Run - Execute inference with an input envelope
API Reference
Xybrid Class
Main entry point for loading models and pipelines.
Static Methods
/// Initialize the Rust library
static Future<void> init({bool? withLogging = true});
/// Create a ModelLoader for single-model inference
static XybridModelLoader model({
// Registry source
String? modelId,
String? version,
String? registry,
String? platform,
// Bundle source
String? bundlePath,
// Directory source (development)
String? modelDir,
});
/// Create a PipelineLoader for multi-stage inference
static XybridPipelineLoader pipeline({
String? yaml, // YAML content string
String? filePath, // Path to YAML file
});
/// Create instance with API key for cloud integrations
factory Xybrid.simple({String? registry, String? apiKey});Examples
// Load from registry
final loader = Xybrid.model(
modelId: 'whisper-tiny-candle',
version: '1.0',
registry: 'http://localhost:8080',
);
// Load from local bundle
final loader = Xybrid.model(bundlePath: 'models/whisper.xyb');
// Load from directory (development)
final loader = Xybrid.model(modelDir: 'test_models/whisper-tiny-candle');
// Load pipeline from YAML
final loader = Xybrid.pipeline(yaml: pipelineYaml);
final loader = Xybrid.pipeline(filePath: 'assets/pipelines/voice-assistant.yaml');XybridModelLoader
Loads a single model for inference.
Constructors
/// Load from HTTP registry
static XybridModelLoader fromRegistry({
required String url,
required String modelId,
required String version,
});
/// Load from local .xyb bundle file
static XybridModelLoader fromBundle({required String path});
/// Load from model directory (development)
static XybridModelLoader fromDirectory({required String path});Properties
String? get modelId; // Model ID (if known)
String? get version; // Model version (if known)
String get sourceType; // "registry", "bundle", or "directory"Methods
/// Load the model into memory (async)
/// Downloads from registry if needed
Future<XybridModel> load();XybridModel
A loaded model ready for inference.
Properties
String get modelId; // Model identifier
String get version; // Model version
bool get isLoaded; // Whether model is loaded
bool get supportsStreaming; // Whether streaming is supported
FfiOutputType get outputType; // Expected output typeMethods
/// Run batch inference
Future<FfiInferenceResult> run({required Envelope envelope});
/// Create streaming session (for ASR models)
XybridStream stream({required FfiStreamConfig config});
/// Unload model from memory
void unload();Example
// Load and run inference
final loader = Xybrid.model(
modelId: 'wav2vec2-base-960h',
version: '1.0',
registry: 'http://localhost:8080',
);
final model = await loader.load();
// Create audio envelope
final audioBytes = await File('audio.wav').readAsBytes();
final envelope = Envelope.audio(audioBytes: audioBytes);
// Run inference
final result = await model.run(envelope: envelope);
print('Transcription: ${result.unwrapText()}');
// Cleanup
model.unload();XybridPipelineLoader
Loads a multi-stage pipeline from YAML.
Constructors
/// Load from YAML content string
static XybridPipelineLoader fromYaml({required String yamlContent});
/// Load from YAML file path
static XybridPipelineLoader fromFile({required String path});Properties
String? get name; // Pipeline name from YAML
List<String> get stageNames; // Stage namesMethods
/// Load the pipeline
Future<XybridPipeline> load();XybridPipeline
A loaded pipeline ready for execution.
Properties
String? get name; // Pipeline name
BigInt get stageCount; // Number of stages
List<String> get stageNames; // Stage names
FfiPipelineInputType get inputType; // Expected input type
bool get isLoaded; // Whether models are loadedMethods
/// Run the pipeline
Future<FfiPipelineResult> run({required Envelope envelope});
/// Unload pipeline
void unload();Example
final yaml = '''
name: voice-assistant
registry: http://localhost:8080
stages:
- whisper-tiny-candle@1.0
- target: integration
provider: openai
model: gpt-4o-mini
- kokoro-82m@0.1
''';
final loader = Xybrid.pipeline(yaml: yaml);
final pipeline = await loader.load();
final result = await pipeline.run(
envelope: Envelope.audio(audioBytes: audioBytes),
);
// Pipeline output is TTS audio
final audioOutput = result.unwrapAudio();Envelope
Data container for model input/output.
Constructors
/// Create audio envelope
static Envelope audio({
required List<int> audioBytes,
int? sampleRate, // Default: 16000
int? channels, // Default: 1
});
/// Create text envelope
static Envelope text({required String text});
/// Create embedding envelope
static Envelope embedding({required List<double> embedding});FfiInferenceResult
Result from single-model inference.
Properties
FfiOutputType outputType; // Output type
int latencyMs; // Inference latency
String modelId; // Model that produced result
String? text; // Text output (if text type)
String? audioBase64; // Audio as base64 (if audio type)
Float32List? embedding; // Embedding (if embedding type)Methods
bool isText();
bool isAudio();
bool isEmbedding();
String unwrapText(); // Get text, panic if not text
Uint8List unwrapAudio(); // Get audio bytes, panic if not audio
Float32List unwrapEmbedding(); // Get embedding, panic if not embeddingFfiPipelineResult
Result from pipeline execution.
Properties
String? name; // Pipeline name
List<FfiStageTiming> stages; // Stage timing info
int totalLatencyMs; // Total pipeline latency
FfiOutputType outputType; // Final output type
String? text;
String? audioBase64;
Float32List? embedding;Methods
bool isText();
bool isAudio();
bool isEmbedding();
String unwrapText();
Uint8List unwrapAudio();
Float32List unwrapEmbedding();Streaming ASR
For real-time speech transcription, use XybridStreamer.
XybridStreamer
// Create from local model directory
final streamer = await XybridStreamer.create(
config: StreamingConfig(
modelDir: 'test_models/whisper-tiny-candle',
enableVad: true,
language: 'en',
),
);
// Or create from registry
final streamer = await XybridStreamer.createFromRegistry(
config: RegistryStreamingConfig(
registryUrl: 'http://localhost:8080',
modelId: 'whisper-tiny-candle',
version: '1.0',
platform: 'macos-arm64',
enableVad: true,
),
);Properties
Stream<PartialResult> get onPartialResult; // Partial results stream
StreamState get state; // Session state
bool get isDisposed;
bool get hasVad; // VAD enabled
String get modelId;
StreamStats get stats; // Session statisticsMethods
/// Feed float32 audio samples (16kHz mono)
Future<void> feed(List<double> samples);
/// Feed PCM 16-bit audio bytes
Future<void> feedPcm16(Uint8List pcmBytes);
/// Flush and get final transcription
Future<TranscriptionResult> flush();
/// Reset for new utterance
Future<void> reset();
/// Release resources
void dispose();Example
final streamer = await XybridStreamer.createFromRegistry(
config: RegistryStreamingConfig(
registryUrl: 'http://localhost:8080',
modelId: 'whisper-tiny-candle',
version: '1.0',
platform: 'macos-arm64',
enableVad: true,
),
);
// Listen for partial results
streamer.onPartialResult.listen((partial) {
print('Partial: ${partial.text}');
});
// Feed audio from microphone
micStream.listen((samples) {
streamer.feed(samples);
});
// Get final result
final result = await streamer.flush();
print('Final: ${result.text}');
streamer.dispose();Audio Recording
Use XybridRecorder for integrated microphone capture.
XybridRecorder
final recorder = XybridRecorder();
// Request permission
if (await recorder.requestPermission() == PermissionStatus.granted) {
// Start recording with auto-stop on silence
await recorder.start(config: RecordingConfig.asr);
// Wait for completion
final result = await recorder.waitForCompletion();
// Read audio bytes
final bytes = await recorder.readAudioBytes(result.path);
}
recorder.dispose();Recording Configurations
// Default ASR config (16kHz, mono, auto-stop on silence)
RecordingConfig.asr
// With VAD enabled
RecordingConfig.withVad
// Custom config
RecordingConfig(
sampleRate: 16000,
channels: 1,
autoStopOnSilence: true,
silenceThresholdDb: -40.0,
silenceDurationMs: 2000,
enableVad: true,
vadSensitivity: 0.5,
maxDurationMs: 60000,
)Methods
// Permissions
Future<bool> hasPermission();
Future<PermissionStatus> requestPermission();
// File-based recording
Future<void> start({RecordingConfig config});
Future<RecordingResult> stop();
Future<void> cancel();
Future<RecordingResult> waitForCompletion();
// Streaming mode (real-time samples)
Future<void> startStreaming({
required void Function(List<double> samples) onSamples,
});
Future<int> stopStreaming(); // Returns duration in ms
// Playback
Future<void> playLastRecording();
Future<void> playAudioFile(String path);
Future<void> stopPlayback();
// File operations
Future<Uint8List> readAudioBytes(String path);
Future<Uint8List> readLastRecordingBytes();
// Cleanup
Future<void> dispose();Callbacks
recorder.onStateChanged = (state) { /* RecordingState */ };
recorder.onAmplitude = (db) { /* double */ };
recorder.onVoiceStart = () { /* ... */ };
recorder.onVoiceEnd = () { /* ... */ };
recorder.onPlaybackComplete = () { /* ... */ };API Key Configuration
For pipelines that use cloud LLM integrations:
import 'package:xybrid_flutter/xybrid_flutter.dart';
// Set Xybrid Gateway API key
Xybrid.setApiKey('your-xybrid-api-key');
// Or set provider-specific keys for direct API calls
Xybrid.setProviderApiKey('openai', 'sk-...');
Xybrid.setProviderApiKey('anthropic', 'sk-ant-...');
// Check if configured
if (Xybrid.hasApiKey()) {
final key = Xybrid.getApiKey();
}Gateway URL Configuration
Configure the gateway URL for LLM API calls (used by pipelines with target: integration):
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Xybrid.init();
// Configure gateway URL for local development
// The SDK automatically appends /v1 for API endpoints
Xybrid.setGatewayUrl('http://localhost:8000');
// Set API key for authentication
Xybrid.setApiKey('your-api-key');
runApp(MyApp());
}URL Resolution Priority
The SDK determines the gateway URL using this priority:
- Per-pipeline override: Set
gateway_urlin pipeline YAML stage options XYBRID_GATEWAY_URL: Environment variable (explicit, must include/v1)XYBRID_PLATFORM_URL: Environment variable (SDK appends/v1automatically)Xybrid.setGatewayUrl(): Set programmatically (SDK appends/v1automatically)- Default:
https://api.xybrid.dev/v1
Per-Pipeline Override
Override the gateway URL for a specific LLM stage in your pipeline YAML:
stages:
- id: llm
model: gpt-4o-mini
target: integration
provider: openai
options:
gateway_url: "http://localhost:8000/v1" # Must include /v1
system_prompt: "You are a helpful assistant."Telemetry
Subscribe to real-time telemetry events:
import 'package:xybrid_flutter/xybrid_flutter.dart';
// Initialize telemetry stream
initTelemetryStream();
// Subscribe to events
subscribeTelemetryEvents().listen((event) {
print('[${event.eventType}] ${event.stageName} - ${event.latencyMs}ms');
});
// Get device capabilities
final caps = getDeviceCapabilities();
print('GPU: ${caps.hasGpu}, Metal: ${caps.hasMetal}');
print('Battery: ${caps.batteryLevel}%');
// Export session metrics
final json = exportTelemetryJson();Platform Support
| Platform | Status | Accelerators |
|---|---|---|
| macOS | ✅ | Metal, CoreML |
| iOS | ✅ | Metal, CoreML |
| Android | ✅ | NNAPI, Vulkan |
| Linux | ✅ | Vulkan |
| Windows | 🔄 | DirectML (planned) |
Complete Example
import 'package:flutter/material.dart';
import 'package:xybrid_flutter/xybrid_flutter.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Xybrid.init();
// Configure gateway URL for local development (optional)
// Xybrid.setGatewayUrl('http://localhost:8000');
// Set API key for cloud integrations
Xybrid.setApiKey('your-api-key');
runApp(MyApp());
}
class VoiceAssistant extends StatefulWidget {
@override
_VoiceAssistantState createState() => _VoiceAssistantState();
}
class _VoiceAssistantState extends State<VoiceAssistant> {
final recorder = XybridRecorder();
XybridPipeline? pipeline;
String result = '';
bool isLoading = false;
@override
void initState() {
super.initState();
_loadPipeline();
}
Future<void> _loadPipeline() async {
setState(() => isLoading = true);
final yaml = '''
name: voice-assistant
registry: http://localhost:8080
stages:
- whisper-tiny-candle@1.0
- target: integration
provider: openai
model: gpt-4o-mini
options:
system_prompt: "You are a helpful assistant."
- kokoro-82m@0.1
''';
final loader = Xybrid.pipeline(yaml: yaml);
pipeline = await loader.load();
setState(() => isLoading = false);
}
Future<void> _recordAndProcess() async {
if (pipeline == null) return;
// Record audio
await recorder.start(config: RecordingConfig.asr);
final recording = await recorder.waitForCompletion();
final audioBytes = await recorder.readAudioBytes(recording.path);
// Run pipeline
final envelope = Envelope.audio(audioBytes: audioBytes);
final pipelineResult = await pipeline!.run(envelope: envelope);
// Get TTS audio output
final ttsAudio = pipelineResult.unwrapAudio();
// Play response
// ... play ttsAudio
}
@override
void dispose() {
recorder.dispose();
pipeline?.unload();
super.dispose();
}
// ... build method
}