Offline Protocol Flutter plugin
Offline Protocol has a Flutter plugin, offline_protocol, that gives iOS and Android apps the mesh engine with its transports, encryption and events from Dart. It was added in 0.30.0, lives in the SDK repository under bindings/flutter, and is a preview as of 0.31.0 (October 2026).
The plugin is not a separate implementation. It links the same generated Kotlin and Swift as the React Native module and compiles the same hand-written native bridge code under bindings/react-native/{ios,android}, less the files that need React. So the transports, secure storage and configuration parser are the ones React Native apps run. The Dart side talks to them over a method channel, and events arrive as JSON on an event stream.
Flutter, iOS and Android requirements
The plugin needs these versions:
| Requirement | Version |
|---|---|
| Flutter | 3.10 or later |
| iOS | 13.0 or later |
| Android | API 24 (Android 7.0) or later |
| Build tools | The Rust toolchain and a one-time build of the mobile native libraries |
It also needs the same permissions as React Native. On iOS that means Bluetooth usage descriptions and the bluetooth-central and bluetooth-peripheral background modes, declared before Bluetooth LE starts. On Android it means the Bluetooth, location and nearby Wi-Fi runtime permissions where they apply. The Swift and Kotlin previews are the routes for native apps without Flutter.
Adding the plugin as a path dependency
The plugin is added by path, because it is not published to pub.dev yet. First build the mobile native libraries from the repository root, the same steps as the React Native module:
./scripts/generate-bindings.sh
cd bindings/react-native
npm run build:uniffi:all
That produces the Android .so files, the iOS XCFramework and the generated Kotlin and Swift. Then depend on the plugin in your app’s pubspec.yaml:
dependencies:
offline_protocol:
path: ../bindings/flutter/offline_protocol # adjust for your layout
On iOS, pod install copies the shared Swift bridge sources into the plugin, because CocoaPods only indexes files under the pod root. If those shared sources change, run bindings/flutter/offline_protocol/ios/sync_bridge_rn.sh and pod install again, or the build compiles the old copy without saying so.
Flutter quick start
Subscribe to events before createFromConfig if you need every event, including gap recovery through native replay. This is the plugin’s own quick start:
import 'package:offline_protocol/offline_protocol.dart';
final sub = OfflineProtocol.events.listen((e) {
final type = e.decodeEvent()['type'];
// message_received, diagnostics, …
});
await OfflineProtocol.createFromConfig({
'appId': 'com.example.app',
'profile': 'default',
'bleEnabled': true,
'internetEnabled': true,
'transports': {
'internet': {
'enabled': true,
'serverAddress': 'wss://relay.example.com',
},
'wifiDirect': {'enabled': true},
},
});
await OfflineProtocol.initializeMlsWithSecureStorage();
await OfflineProtocol.start();
final messageId = await OfflineProtocol.sendMessage(
recipient: 'off1…',
content: 'Hello',
);
The engine and transport work run on a dedicated engine thread, one per process, and method-channel results and events are delivered on the main isolate’s platform thread. Nothing blocking runs on the UI thread, so a teardown that waits on transport I/O does not freeze the app.
Flutter plugin coverage in 0.31.0
As of 0.31.0, the Flutter plugin reaches the React Native module’s full native method surface. The release added the plugin’s engine channel, generators for its method handlers on Android and iOS, parity guards and an iOS bridge job in CI:
| Area | In the Flutter plugin |
|---|---|
| Lifecycle, identity, messaging | createFromConfig, initializeMlsWithSecureStorage, start, stop, destroy, localAddress, sendMessage |
| Events | OfflineProtocol.events, with sequence numbers and replayEvents when you subscribe early |
| Full native surface | MeshSdk, whose method channel carries the React Native module’s native methods plus replayEvents (171 methods) |
| Transports | Internet, Bluetooth LE, Wi-Fi Direct, Nostr and Reticulum, on the same configuration model as React Native |
| Telemetry | On the method channel and through MeshSdk |
| Android background | MeshForegroundService keeps mesh work alive while the engine runs |
A CI guard holds the channel’s method list equal across Dart, Android and iOS, so a method added on one side reaches all three. The one React Native feature that stays in React Native is the headless wake service, which brings mesh work back after the operating system kills the process. The public docs page still describes the 0.30.0 plugin, whose Dart API was narrower.
Flutter plugin status and testing
The plugin is a preview, added to an app by path while it is not on pub.dev, and upgrading means moving your SDK checkout to a new release tag. CI covers it on both platforms:
- Dart.
flutter testandflutter analyzerun on every change to the plugin. - Android. CI builds the native library, assembles the plugin’s release library and checks its size against a committed baseline.
- iOS. A bridge job typechecks the plugin’s Swift together with the shared bridge sources the pod compiles, against the Flutter engine framework. The Swift package job is the one that links the iOS library.
The example app runs on Android from the committed Gradle tree, and a script generates its Xcode Runner project for iOS. It starts with the internet transport flag only, subscribes to events and prints the device’s off1… address.
For a team choosing a framework from scratch, React Native is the stable integration, published on npm. For a team already on Flutter, the plugin runs the same native bridge code, so the same transports work across an iPhone and an Android phone. The SDK is AGPL-3.0-only, with a commercial licence for proprietary apps.