Examples
Examples
The example apps do real work through generated bindings, with no plugin and no hand-written native code: a live device dashboard, text-to-speech, the share sheet, the clipboard, a persistent note, haptics and opening URLs. The same tasks run in Flutter and React Native on Android and iOS, and every task is covered by an on-device test.
| Task | Android APIs | iOS APIs |
|---|---|---|
| Device dashboard | battery broadcast + BatteryManager, ActivityManager.MemoryInfo, StatFs, ConnectivityManager | UIDevice, NSProcessInfo, NSFileManager |
| Speak text | TextToSpeech + an OnInitListener written in Dart or JS | AVSpeechSynthesizer |
| Share text | ACTION_SEND + Intent.createChooser | UIActivityViewController |
| Copy and paste | ClipboardManager + ClipData | UIPasteboard |
| Persistent note | SharedPreferences | NSUserDefaults |
| Feedback | Vibrator + VibrationEffect (API 26+, runtime-guarded) | UIImpactFeedbackGenerator, UINotificationFeedbackGenerator |
| Location settings | Settings.ACTION_LOCATION_SOURCE_SETTINGS; the app's permission page via ACTION_APPLICATION_DETAILS_SETTINGS + package: URI | the app's page in Settings (UIApplicationOpenSettingsURLString), where its location permission is shown |
| Open a URL | ACTION_VIEW + startActivity | openURL:options:completionHandler: (block → Dart / JS) |
Apps: examples/flutter/android_slice, examples/flutter/ios_slice and examples/react-native/slice (Android and iOS).
Device dashboard
Flutter on Android: battery from the sticky broadcast, memory, free storage and the active network.
final battery = ctx.registerReceiver(
null, IntentFilter.new$String(Intent.ACTION_BATTERY_CHANGED.toJString()))!;
final level = battery.getIntExtra(BatteryManager.EXTRA_LEVEL.toJString(), -1);
final am = ctx.getSystemService(Context.ACTIVITY_SERVICE.toJString())!.as(ActivityManager.type);
final mem = ActivityManager_MemoryInfo();
am.getMemoryInfo(mem); // mem.availMem, mem.totalMem
final fs = StatFs(Environment.getDataDirectory()!.getAbsolutePath());
print('${fs.getAvailableBytes()} of ${fs.getTotalBytes()} bytes free');
Flutter on iOS:
final device = ios.UIDevice.currentDevice..batteryMonitoringEnabled = true;
final info = ios.NSProcessInfo.processInfo;
print('${device.batteryLevel} · ${info.thermalState} · ${info.physicalMemory} bytes');
Text-to-speech with a callback
Android reports that the engine is ready through TextToSpeech.OnInitListener, a Java interface implemented here in JavaScript (React Native):
const engine = TextToSpeech.new(applicationContext(), TextToSpeech_OnInitListener.implement(
{ onInit: status => {
if (status === TextToSpeech.SUCCESS)
engine.speak$CharSequence$int$Bundle$String(text, TextToSpeech.QUEUE_FLUSH, null, 'id');
} },
{ async: ['onInit'] },
));
iOS (React Native):
const utterance = AVSpeechUtterance.speechUtteranceWithString(text);
utterance.voice = AVSpeechSynthesisVoice.voiceWithLanguage(AVSpeechSynthesisVoice.currentLanguageCode());
synth.speakUtterance(utterance);
Share sheet
Flutter on iOS: find the key window through the connected scenes, then present UIActivityViewController.
final sheet = ios.UIActivityViewController.alloc()
.initWithActivityItems(objc.NSArray.of([text.toNSString()]));
sheet.popoverPresentationController?.sourceView = root.view; // iPad
await root.presentViewControllerAsync(sheet, animated: true);
React Native on Android:
const send = Intent.new$String(Intent.ACTION_SEND)
.setType('text/plain')
.putExtra$String$String(Intent.EXTRA_TEXT, text);
currentActivity()!.startActivity(Intent.createChooser(send, 'Share via')!);
Clipboard and a persistent note
// Flutter on Android
clipboard.setPrimaryClip(ClipData.newPlainText('label'.toJString(), text.toJString())!);
prefs.edit()!.putString('note'.toJString(), note.toJString())!.apply();
// React Native on iOS: setObject:forKey: takes `id`, so wrap the string
UIPasteboard.generalPasteboard.string_ = text;
NSUserDefaults.standardUserDefaults.setObject(nsString(note), 'showcase.note');
Location settings
Flutter on Android: open the system Location page, or this app's own settings page, where location permission is granted.
activity.startActivity(Intent.new$String(Settings.ACTION_LOCATION_SOURCE_SETTINGS.toJString()));
activity.startActivity(Intent.new$String$Uri(
Settings.ACTION_APPLICATION_DETAILS_SETTINGS.toJString(),
Uri.parse('package:${ctx.getPackageName()!.toDartString()}'.toJString()),
));
APIs newer than your minimum SDK
VibrationEffect is API 26+, newer than the example's minApi: 24. The generated call checks the device and throws instead of crashing:
try {
vibrator.vibrate$VibrationEffect(VibrationEffect.createOneShot(60, VibrationEffect.DEFAULT_AMPLITUDE));
} on NativeApiUnavailableException {
vibrator.vibrate(60); // legacy API on older devices
}
More binding features
// Kotlin: suspend -> Future / Promise, Flow -> Stream
final greeting = await greeter.greetLater(20);
await for (final i in greeter.countTo(3)) print(i);
// Objective-C: NSError ** -> exception, completion handler -> Future
try {
fileManager.contentsOfDirectoryAtPath('/missing'.toNSString());
} on ios.NativeObjCError catch (e) {
print(e.domain); // NSCocoaErrorDomain
}
// Blocks and protocols written in JavaScript
fileManager.delegate = NSFileManagerDelegate.implement({
fileManager$shouldRemoveItemAtPath: () => false,
});
// A native view in the widget tree / component tree
NativeView.ios(view: ios.UILabel.new$()..text = 'Hello'.toNSString());
<NativeView view={label} style={{ width: 200, height: 48 }} />