vex_channel 1.0.0
vex_channel: ^1.0.0 copied to clipboard
VexChannel — Zero-boilerplate platform channels for Flutter. Write pure Dart, access every native API on Android, iOS, macOS, Windows, Linux and Web without touching Kotlin/Swift/C++ once.
⚡ VexChannel #
Zero-boilerplate Flutter platform channels.
Write pure Dart. Access every native API on Android, iOS, macOS, Windows, Linux and Web — without writing a single line of Kotlin, Swift, or C++.
Why VexChannel? #
Traditional Flutter platform channels require you to:
- Write a
MethodChannelin Dart - Write matching native code in Kotlin/Java (Android)
- Write matching native code in Swift/ObjC (iOS)
- Repeat for every platform you target
VexChannel eliminates all of that.
You write Dart. VexChannel handles routing, type coercion, error mapping, retry logic, caching, timeouts, and streaming — for every platform simultaneously.
Installation #
dependencies:
vex_channel: ^1.0.0
Quick Start #
Option A — Use a built-in module (zero setup) #
import 'package:vex_channel/vex_channel.dart';
// Battery
final battery = VexChannel.bridge(VexBattery.instance);
final level = await battery.level; // 87.5
final info = await battery.info; // VexBatteryInfo
battery.stream.listen((info) { ... }); // real-time stream
// Device Info
final device = VexChannel.bridge(VexDeviceInfoModule.instance);
final model = await device.model; // "Pixel 8 Pro"
// Haptics
final haptics = VexChannel.bridge(VexHaptics.instance);
await haptics.success; // 🎉 success haptic
// Clipboard
final cb = VexChannel.bridge(VexClipboard.instance);
await cb.setText('Hello VexChannel!');
// Connectivity
final conn = VexChannel.bridge(VexConnectivity.instance);
print(await conn.isWifi); // true
conn.onConnectivityChanged.listen((type) { ... });
// Sensors
final sensors = VexChannel.bridge(VexSensors.instance);
sensors.accelerometer.listen((data) {
print('x=${data.x} y=${data.y} z=${data.z}');
});
// Storage (key-value + secure)
final store = VexChannel.bridge(VexStorage.instance);
await store.setString('token', 'abc123');
await store.setSecureString('api_key', 's3cr3t');
final token = await store.getString('token');
// File System
final fs = VexChannel.bridge(VexFileSystem.instance);
final docs = await fs.documentsDirectory;
await fs.writeAsString('$docs/note.txt', 'Hello!');
final text = await fs.readAsString('$docs/note.txt');
// Camera
final cam = VexChannel.bridge(VexCameraModule.instance);
final photo = await cam.capturePhoto(quality: 90); // returns file path
// Permissions
final perms = VexChannel.bridge(VexPermissions.instance);
final status = await perms.request(VexPermission.camera);
if (status == VexPermissionStatus.granted) { ... }
// Location
final loc = VexChannel.bridge(VexLocationModule.instance);
final pos = await loc.getCurrentLocation();
print('${pos.latitude}, ${pos.longitude}');
// Biometrics
final bio = VexChannel.bridge(VexBiometrics.instance);
final ok = await bio.authenticate(reason: 'Confirm your identity');
// Notifications
final notif = VexChannel.bridge(VexNotifications.instance);
await notif.show(VexNotification(id: '1', title: 'Hello', body: 'From VexChannel'));
// Audio
final audio = VexChannel.bridge(VexAudio.instance);
final playerId = await audio.loadUrl('https://example.com/song.mp3');
await audio.play(playerId);
audio.positionStream(playerId).listen((pos) => print(pos));
Option B — Create your OWN bridge (Dart only, no native changes) #
// 1. Extend VexBridgeBase — that's all the Dart side needs
class CameraBridge extends VexBridgeBase {
@override
String get channelName => 'com.myapp/camera';
// 2. Call invokeMethod for one-shot calls
Future<String?> capturePhoto({int quality = 90}) async {
final res = await invokeMethod<String>(
'capturePhoto',
args: {'quality': quality},
);
return res.unwrapOr(null);
}
// 3. Call openStream for real-time events
Stream<Map<String, dynamic>> get previewFrames =>
openStream<Map<String, dynamic>>(eventName: 'preview');
}
// 4. Register and use
final camera = VexChannel.bridge(CameraBridge());
final path = await camera.capturePhoto(quality: 95);
camera.previewFrames.listen((frame) { ... });
Option C — Low-level one-shot (no bridge class at all) #
// Invoke any native method on any channel in one line
final res = await VexChannel.invoke<double>(
channel: 'com.myapp/battery',
method: 'getLevel',
);
print(res.unwrap()); // 87.5
// Or with the shorthand (throws on error)
final level = await VexChannel.call<double>(
channel: 'vex_channel/battery',
method: 'getBatteryLevel',
);
Option D — Native → Dart callbacks #
// Register a handler that native can call at any time
VexChannel.registerHandler(
channel: 'com.myapp/push',
method: 'onMessage',
handler: (args) async {
final title = args?['title'];
print('Push: $title');
return {'handled': true};
},
);
Built-in Modules #
| Module | Class | Channel |
|---|---|---|
| Battery | VexBattery |
vex_channel/battery |
| Device Info | VexDeviceInfoModule |
vex_channel/device_info |
| Connectivity | VexConnectivity |
vex_channel/connectivity |
| Sensors | VexSensors |
vex_channel/sensors |
| Permissions | VexPermissions |
vex_channel/permissions |
| Haptics | VexHaptics |
vex_channel/haptics |
| Clipboard | VexClipboard |
vex_channel/clipboard |
| Location | VexLocationModule |
vex_channel/location |
| File System | VexFileSystem |
vex_channel/file_system |
| Storage | VexStorage |
vex_channel/storage |
| Notifications | VexNotifications |
vex_channel/notifications |
| Biometrics | VexBiometrics |
vex_channel/biometrics |
| Camera | VexCameraModule |
vex_channel/camera |
| Audio | VexAudio |
vex_channel/audio |
| Network (HTTP) | VexNetworkModule |
vex_channel/network |
VexResponse — never throw, always return #
Every invokeMethod returns a VexResponse<T>:
final res = await invokeMethod<double>('getLevel');
// Pattern match
res.when(
success: (level) => print('Level: $level'),
failure: (error) => print('Error: ${error.message}'),
);
// Or unwrap (throws VexException on failure)
final level = res.unwrap();
// Or provide a fallback
final level = res.unwrapOr(0.0);
// Map the value
final percent = res.map((v) => '${v.toStringAsFixed(1)}%');
Error Types #
| Exception | Cause |
|---|---|
VexException |
Generic native error |
VexTimeoutException |
Call exceeded timeout |
VexUnsupportedPlatformException |
Feature not available on current OS |
VexPermissionDeniedException |
Native permission denied |
Advanced Features #
Retry on transient failure #
final res = await invokeMethod<String>(
'getToken',
retryCount: 3, // retry up to 3 times with exponential back-off
);
Per-call caching #
final res = await invokeMethod<Map<String, dynamic>>(
'getDeviceInfo',
cacheable: true,
cacheTtl: Duration(hours: 24), // cached for 24 hours
);
Custom timeout per call #
final res = await invokeMethod<String>(
'authenticate',
timeout: Duration(minutes: 2),
);
Multi-codec support #
@VexBridge('com.myapp/data', codec: VexCodecType.json)
class DataBridge extends VexBridgeBase { ... }
Platform detection #
if (VexChannel.isRunningOn(VexOS.android)) {
// Android-specific path
}
switch (VexChannel.currentPlatform) {
case VexOS.ios: ...
case VexOS.android: ...
case VexOS.web: ...
default: ...
}
Logging & debugging #
VexChannel.enableLogging(verbose: true);
// Prints every channel call with timing:
// [VexChannel] → vex_channel/battery#getBatteryLevel
// [VexChannel] ✓ vex_channel/battery#getBatteryLevel → 3ms
VexChannel.registry.debugDump();
// ╔══════════════════════════════════════
// ║ VexChannel Registry — 3 bridge(s)
// ║ • VexBattery → VexBattery
// ║ • VexConnectivity → VexConnectivity
// ╚══════════════════════════════════════
Writing Native Handlers (one-time setup) #
VexChannel ships with complete Android (Kotlin) and iOS (Swift) handler implementations for all built-in modules. You only need to write native code when adding your own custom channel.
Android (Kotlin) #
// Register in VexChannelPlugin.kt — one line
MethodChannel(messenger, "com.myapp/camera").setMethodCallHandler { call, result ->
when (call.method) {
"capturePhoto" -> {
val quality = call.argument<Int>("quality") ?: 90
// ... actual native camera code ...
result.success("/path/to/photo.jpg")
}
else -> result.notImplemented()
}
}
iOS (Swift) #
FlutterMethodChannel(name: "com.myapp/camera", binaryMessenger: messenger)
.setMethodCallHandler { call, result in
if call.method == "capturePhoto" {
// ... actual native camera code ...
result("/path/to/photo.jpg")
} else {
result(FlutterMethodNotImplemented)
}
}
That's literally all the native code you ever need — one handler per channel. VexChannel takes care of everything else.
License #
MIT © Mysterious Coder