native_kit

One Dart call invokes the real OS-level UI. native_kit presents genuine
system view controllers, sheets, zero-permission pickers and hardware scanners
on iOS (UIKit / SwiftUI / VisionKit / PencilKit) and Android (Material 3 /
Jetpack / ML Kit / system intents) — no Flutter look-alikes.
final result = await NativeKit.showAlert(
context: context,
title: 'Delete draft?',
actions: const [
NativeAlertAction.cancel(id: 'cancel', title: 'Cancel'),
NativeAlertAction.destructive(id: 'delete', title: 'Delete'),
],
);
if (result?.actionId == 'delete') { /* … */ }
- 30 unified components across 6 domains, 32 typed methods.
- Clean Dart models —
DateTime, Color, Rect, Uint8List and typed
result classes. Transport DTOs are never exposed.
- Context aware — pass a
BuildContext and the native UI anchors on that
widget (iPad-safe popovers) and follows the Flutter theme's brightness and
primary color.
- No hanging futures — swipe-to-dismiss, back and predictive-back
gestures always resolve the call (with
null, false or an empty list).
- No
MainActivity changes — Android activity results run through a
headless fragment, even with a plain FlutterActivity.
Installation
flutter pub add native_kit
import 'package:native_kit/native_kit.dart';
iOS (16.0+)
Set platform :ios, '16.0' in ios/Podfile (Swift Package Manager is
supported too). Add the usage descriptions for the components you use to
ios/Runner/Info.plist:
| Key |
Needed by |
NSCameraUsageDescription |
scanDocument, scanCode, extractLiveText (camera) |
NSMicrophoneUsageDescription, NSSpeechRecognitionUsageDescription |
listenSpeechPrompt |
NSFaceIDUsageDescription |
authenticate on Face ID devices |
NSCalendarsUsageDescription |
createCalendarEvent on iOS 16 only (iOS 17+ needs none) |
Missing keys never crash the app: the call throws a NativeKitException
with NativeKitErrorCode.missingUsageDescription.
Android (API 26+)
Set minSdk = 26 in android/app/build.gradle(.kts). That's it: the plugin
ships its own manifest entries (a transparent host activity, a
FileProvider, package-visibility queries and the install-time INTERNET,
VIBRATE and USE_BIOMETRIC permissions). ML Kit models are downloaded by
Google Play services on first use.
Architecture
┌──────────────────────── packages/native_kit ────────────────────────┐
App code ─▶│ NativeKit.showAlert(context: …) → domain models → Pigeon DTOs │
└──────────────────────────────────┬──────────────────────────────────┘
│ NativeKitPlatform (6 Pigeon host APIs)
┌──────────────── native_kit_platform_interface ──────────────────────┐
│ pigeons/native_kit_api.dart → Dart / Swift / Kotlin │
└───────────────┬─────────────────────────────────┬───────────────────┘
▼ ▼
native_kit_ios (Swift, @MainActor) native_kit_android (Kotlin, coroutines)
OverlaysHandler · ValuePickersHandler · same 6 handlers, Material 3 + Jetpack,
ZeroPermPickersHandler · VisionAndMediaHandler NativeKitResultFragment for every
SystemUtilitiesHandler · SecurityAndHardware… ActivityResultContract
Every call carries an optional NativeRect (the calling widget's global
rect) and NativeThemeConfig (brightness + seed color) extracted from the
BuildContext.
Components
Module 1 — Overlays
| Dart method |
iOS |
Android |
showAlert |
UIAlertController(.alert) + addTextField |
MaterialAlertDialogBuilder + TextInputEditText |
showActionSheet |
UIAlertController(.actionSheet) (iPad popover) |
Material 3 BottomSheetDialog list |
showContextMenu |
UIMenu via UIEditMenuInteraction |
ListPopupWindow at the anchor |
showNativeTooltip |
.popover UIViewController with arrow |
Material 3 PopupWindow card with arrow |
showAdaptiveSheet |
UISheetPresentationController detents |
BottomSheetDialog + BottomSheetBehavior |
Module 2 — Value pickers
| Dart method |
iOS |
Android |
pickDateTime / pickDateRange |
UIDatePicker in a .medium() sheet |
MaterialDatePicker / MaterialTimePicker |
pickColor |
UIColorPickerViewController (eyedropper, alpha) |
Material 3 sheet: preview, hex, ARGB / HSL sliders |
pickValueWheel |
multi-column UIPickerView sheet |
NumberPicker row in a BottomSheetDialog |
pickFont |
UIFontPickerViewController |
searchable system typeface sheet |
Module 3 — Zero-permission pickers
| Dart method |
iOS |
Android |
pickMedia |
PHPickerViewController |
PickVisualMedia / PickMultipleVisualMedia |
pickContact |
CNContactPickerViewController |
PickContact / ACTION_PICK + ContentResolver |
pickDocument |
UIDocumentPickerViewController(forOpeningContentTypes:asCopy:) |
OpenDocument / OpenMultipleDocuments |
saveDocument |
UIDocumentPickerViewController(forExporting:asCopy:) |
CreateDocument + stream write |
createCalendarEvent |
EKEventEditViewController (.ics QuickLook on iOS 16) |
ACTION_INSERT on CalendarContract.Events |
pickLocation |
MKMapView + MKLocalSearchCompleter sheet |
map sheet + android.location.Geocoder |
| Dart method |
iOS |
Android |
scanDocument |
VNDocumentCameraViewController → JPEGs + PDFDocument |
ML Kit GmsDocumentScanner |
scanCode |
DataScannerViewController / AVCaptureMetadataOutput |
GmsBarcodeScanner (no camera permission) |
extractLiveText |
VNRecognizeTextRequest / DataScannerViewController(.text()) |
ML Kit Text Recognition + review sheet |
extractImageSubject |
VNGenerateForegroundInstanceMaskRequest (iOS 17+) |
ML Kit Subject Segmentation |
showMarkupSheet |
PKCanvasView + PKToolPicker |
low-latency Canvas full-screen dialog |
trimVideo |
UIVideoEditorController |
trim sheet with MediaExtractor + MediaMuxer |
Module 5 — System utilities
| Dart method |
iOS |
Android |
share |
UIActivityViewController |
Intent.createChooser + FileProvider |
previewFile |
QLPreviewController |
PdfRenderer pager / ACTION_VIEW |
openInAppBrowser |
SFSafariViewController (.pageSheet) |
CustomTabsIntent (Partial Custom Tabs) |
printDocument |
UIPrintInteractionController (+ UIMarkupTextPrintFormatter) |
PrintManager + PrintDocumentAdapter / PrintHelper |
showTranslationSheet |
SwiftUI .translationPresentation (iOS 17.4+) |
ACTION_PROCESS_TEXT / ACTION_TRANSLATE / TTS sheet |
showDictionaryLookUp |
UIReferenceLibraryViewController |
ACTION_DEFINE / Partial Custom Tab search |
listenSpeechPrompt |
SFSpeechRecognizer + AVAudioEngine sheet |
RecognizerIntent.ACTION_RECOGNIZE_SPEECH |
Module 6 — Security & hardware
| Dart method |
iOS |
Android |
authenticate |
LAContext.evaluatePolicy |
BiometricPrompt / KeyguardManager |
requestAppReview |
StoreKit AppStore.requestReview(in:) |
Play ReviewManager (+ store fallback) |
triggerHaptic |
UIImpact/Notification/SelectionFeedbackGenerator, CHHapticEngine |
HapticFeedbackConstants, VibrationEffect |
showSystemPanel |
AVRoutePickerView, NEHotspotConfigurationManager, MPVolumeView, RPScreenRecorder |
MediaRouter2 switcher, Settings.Panel.*, MediaProjectionManager |
Usage
// Anchored, themed action sheet (popover on iPad).
final choice = await NativeKit.showActionSheet(
context: buttonContext,
items: const [
NativeMenuItem(id: 'share', title: 'Share', iosSymbol: 'square.and.arrow.up'),
NativeMenuItem(id: 'delete', title: 'Delete', style: NativeActionStyle.destructive),
],
);
// Zero-permission photo picker.
final List<PickedMedia> photos = await NativeKit.pickMedia(
type: NativeMediaType.images,
selectionLimit: 5,
);
// Native color picker returns a Flutter Color.
final Color? color = await NativeKit.pickColor(context: context);
// Document scanner returns page JPEGs and a combined PDF.
final DocumentScanResult? scan = await NativeKit.scanDocument();
// The instance API is the same surface, handy for dependency injection.
final NativeKitClient kit = NativeKit.instance;
await kit.triggerHaptic(HapticType.success);
Error handling
Cancellation is never an error. Real failures throw NativeKitException
with a NativeKitErrorCode: noUiContext, unavailable (e.g. VisionKit on
the simulator), invalidArgument, missingUsageDescription,
permissionDenied, ioError or nativeError.
try {
await NativeKit.scanCode();
} on NativeKitException catch (e) {
if (e.code == NativeKitErrorCode.unavailable) showFallbackUi();
}
- Files returned to Dart (media, documents, scans, cutouts, trimmed videos)
are copies in the app's temporary / cache directory. Move what you keep.
pickLocation on Android renders OpenStreetMap tiles by default. Pass
androidTileUrlTemplate to use your own tile server in production.
showSystemPanel(SystemPanel.screenCapture) records in-app on iOS (call it
again to stop and receive the movie) and returns the MediaProjection
consent result on Android.
showSystemPanel(SystemPanel.nfc) is Android only.
Packages
License
MIT — see LICENSE.