zello 0.1.1
zello: ^0.1.1 copied to clipboard
Flutter plugin wrapping the native Zello Work Mobile SDKs (Android/iOS) for push-to-talk, voice RX/TX, text messaging, channels and presence.
zello — Flutter plugin for the Zello Work Mobile SDKs #
A Flutter plugin that wraps the native Zello Work Mobile SDKs (Android Kotlin, iOS Swift) and exposes a clean, idiomatic Dart API for:
- Push-to-talk (works with the screen off / app in background)
- Channel connect / disconnect with auto-reconnect events
- Voice TX (start/stopTalking) and RX (incoming voice start/stop events)
- Text messages
- Presence / status
- Hardware PTT buttons and Apple PushToTalk integration on iOS
There is no official Zello Flutter SDK — this package is a community platform-channel wrapper around the official native SDKs.
Installation #
dependencies:
zello: ^0.1.0
import 'package:zello/zello.dart';
Quick start #
await Zello.instance.initialize(
config: const ZelloConfig(appKey: '<your-zello-sdk-key>'),
);
final sub = Zello.instance.events.listen((event) {
// event is a sealed ZelloEvent – switch on its subtypes
});
await Zello.instance.connect(
network: 'mycompany.zellowork.com',
token: jwtFromYourBackend, // RS256, short-lived
);
// PTT button pressed
await Zello.instance.startTalking('dispatch');
// PTT button released
await Zello.instance.stopTalking();
Public Dart API #
| Member | Purpose |
|---|---|
Zello.initialize({required ZelloConfig config}) |
One-time native init. |
connect({required String network, required String token, String? username}) |
Connect to a Zello Work network with a short-lived JWT. |
disconnect() |
Disconnect. Idempotent. |
startTalking(String channel) |
Begin TX. Wire to PTT press. |
stopTalking() |
End TX. Wire to PTT release. |
sendTextMessage(String channel, String text) |
Send a text message. |
setStatus(ZelloStatus status) |
Update presence. |
getChannelState(String channel) → ZelloChannel |
Snapshot of a channel. |
events |
Stream<ZelloEvent> (broadcast). |
connectionState |
ValueListenable<ZelloConnectionState> for UI. |
dispose() |
Tear down native resources. |
All native failures surface as ZelloException(code, message, details).
Events are a sealed Dart-3 hierarchy — switch exhaustively over
ZelloConnectionStateChanged, ZelloIncomingVoiceStarted/Stopped,
ZelloOutgoingTalkStateChanged, ZelloIncomingTextMessage,
ZelloChannelStatusChanged, ZelloReconnectAttempt, ZelloErrorEvent,
ZelloUnknownEvent.
Setup & Native SDK Installation #
The Zello Mobile SDKs are gated behind a Zello Work subscription and cannot be redistributed via this repo. You must obtain them yourself and wire them in.
1. Obtain the SDK artifacts #
- Sign in to your Zello Work admin portal.
- Provision an SDK key for your app.
- Download:
- Android: AAR or Maven coordinates of the Zello Channels SDK.
- iOS: XCFramework (or CocoaPods coordinate, if your subscription ships one).
2. Android wiring #
In android/build.gradle.kts of this plugin (already scaffolded):
allprojects {
repositories {
google()
mavenCentral()
maven { url = uri("https://maven.zello.com/release") } // example
}
}
dependencies {
implementation("com.zello:zello-channel-sdk:<version>")
// or, for a local AAR:
// implementation(files("libs/zello-channel-sdk.aar"))
}
The plugin already declares (in android/src/main/AndroidManifest.xml):
RECORD_AUDIOFOREGROUND_SERVICE,FOREGROUND_SERVICE_MICROPHONEPOST_NOTIFICATIONS(Android 13+)BLUETOOTH_CONNECT(for headset PTT discovery)MODIFY_AUDIO_SETTINGS,WAKE_LOCK,INTERNET,ACCESS_NETWORK_STATE- A foreground service
ZelloForegroundServicewithandroid:foregroundServiceType="microphone"so PTT keeps working with the screen off.
You must request the runtime permissions in your host app
(RECORD_AUDIO, POST_NOTIFICATIONS, BLUETOOTH_CONNECT) using
permission_handler or your tool of choice before calling connect().
Then wire the real Zello SDK calls inside
android/src/main/kotlin/com/zello/flutter/ZelloSdkAdapter.kt — every
TODO in that file is a single SDK call (one line typically). The channel
plumbing (ZelloPlugin.kt) does not need to change.
3. iOS wiring #
In ios/zello.podspec (already scaffolded), uncomment one of:
# CocoaPods
s.dependency 'ZelloChannelKit', '~> 1.0'
# Or vendored XCFramework
# s.vendored_frameworks = 'Frameworks/ZelloSDK.xcframework'
In the host app's ios/Runner/Info.plist add:
<key>NSMicrophoneUsageDescription</key>
<string>Used for push-to-talk voice.</string>
<key>UIBackgroundModes</key>
<array>
<string>audio</string>
<string>push-to-talk</string>
</array>
In the host app's Runner.entitlements add:
<key>com.apple.developer.push-to-talk</key>
<true/>
…and enable the Push to Talk capability in Xcode (Signing & Capabilities).
Without that entitlement, PT calls will fail silently — set
enableApplePushToTalk: false in ZelloConfig if you cannot get the
entitlement yet (background PTT will then be limited).
The plugin already configures AVAudioSession for
playAndRecord / .voiceChat / .allowBluetooth / .defaultToSpeaker.
Wire the real Zello iOS SDK calls inside
ios/Classes/ZelloSdkAdapter.swift — same pattern as Android, every TODO
is one SDK call.
4. Backend token minting (auth) #
The plugin only accepts a short-lived JWT (RS256) in connect(token:).
It must never see the issuer private key.
Your backend should:
- Hold the Zello issuer + RS256 private key in a secret store.
- Mint a JWT per user / per session with a short expiry (e.g. 60–120 s).
- Return the JWT to the app over HTTPS.
The Flutter app simply receives the token and passes it to connect().
Threading & lifecycle #
- All
MethodChannel.ResultandEventSinkcalls are dispatched on the platform main thread (Handler(Looper.getMainLooper())on Android,DispatchQueue.mainon iOS). - The plugin holds a single native client per Flutter engine.
dispose()cancels native listeners, tears down the session, and stops the Android foreground service. - Auto-reconnect attempts from the native SDK are surfaced as
ZelloReconnectAttemptevents — never swallowed.
Manual device-test checklist #
The unit tests cover the Dart layer with a mocked MethodChannel. The
native voice path must be exercised on real hardware:
- Connect on Android, lock the screen, press PTT (button or wired) — TX audio reaches the channel; foreground-service notification is visible.
- Receive a transmission while the app is backgrounded — RX audio plays through the speaker / Bluetooth headset.
- Kill network connectivity for 10 s, restore —
ZelloReconnectAttemptthenZelloConnectionStateChanged(connected)arrive in Dart. - iOS: with the PTT entitlement, background PTT keeps working and the system PTT UI appears in Control Center.
- iOS: route audio to a Bluetooth headset —
AVAudioSessionswitches without dropping the session. - Hardware PTT button on a Sonim/Crosscall device triggers start/stopTalking.
- Send/receive a text message; verify
ZelloIncomingTextMessage. setStatus(ZelloStatus.busy)— other Zello clients see the new status.
Known limitations / TODO #
- All Zello SDK symbol references are placeholders behind
ZelloSdkAdapter(Android + iOS). Replace the// TODO: confirm against Zello SDK vXblocks with the real SDK calls from your Zello Work portal. The channel layer does not need to change. - Image messages, location messages, and direct (1:1) voice are not yet exposed in the Dart API.
- Channel user-list streaming is not yet exposed; only
getChannelState(channel)snapshots. - Hardware PTT button auto-routing on Android relies on the Zello SDK's built-in handling; custom HID key mapping is not implemented here.
- The iOS Apple-PushToTalk delegate (
ApplePTTBridge) implements the minimum required methods; extend as you wire real Zello TX/RX. - No federated split (single-package). If/when this plugin gains macOS or web support, splitting into platform interface + implementations would be appropriate.
- The example app's
android/andios/projects are not committed — runflutter create --platforms=android,ios .insideexample/once, then build.