platform-app-native-sdk-kit
v1.0.8
Published
Platform Native SDK Kit for MRI OTA Mono
Maintainers
Readme
Platform Native SDK Kit
Unified React Native SDK for MRI OTA Monorepo
Streamlined native features with built-in permission management
📦 20+ Native Modules • 🔐 6 Permission Helpers • 📱 iOS & Android • 🎯 TypeScript First
Table of Contents
- Why This SDK?
- Key Features
- Quick Start
- Installation & Configuration
- Permission Helpers
- Components
- Available Modules
- Common Use Cases
- Permission Handling
- Best Practices
- Troubleshooting
- API Reference
- Package Information
Why This SDK?
The Problem
Building React Native apps with native features involves:
- Managing 20+ separate dependencies with different APIs and patterns
- Complex permission flows that require dozens of lines of boilerplate code
- Version conflicts when multiple mini apps in a monorepo use different versions
- Inconsistent UX when each team implements permissions differently
- Difficult onboarding for new developers learning multiple APIs
The Solution
@platform-app/native-sdk-kit provides a unified interface to native features with built-in permission management, specifically designed for monorepo architectures.
How It Works
MRI-Mono-Repo/
├── main-container-app/ # 📦 Installs all peer dependencies once
├── platform-app-sdk-kit/ # 🔧 This SDK (declares peer dependencies)
└── mini-apps/ # 🚀 Your feature apps (use the SDK)
├── feature-app-1/
├── feature-app-2/
└── ...- Main container installs all native dependencies (expo modules, react-native-date-picker, etc.)
- SDK package declares them as peer dependencies (doesn't install them)
- Mini apps import and use the SDK
- At runtime, mini apps inherit the peer dependencies from the main container
Benefits
✅ Reduces permission handling code by 70% - Built-in helpers handle check + request flows
✅ Eliminates version conflicts - Peer dependencies install once in main container
✅ Standardizes patterns - Consistent API across all mini apps
✅ Faster development - Start building features immediately
✅ Type-safe - Full TypeScript definitions catch errors at compile time
✅ Smaller bundles - No duplicate native modules across packages
Key Features
🔐 Permission Helpers
Simplified permission management for:
- Speech Recognition (Microphone + Speech Recognizer)
- Camera (Camera + Microphone for video)
- Location (Foreground + Background)
- Media Library (Read + Write, Limited Photos support)
- Image Picker (Camera + Photo Library)
- Audio Recording (Microphone + Audio Mode setup)
📅 Native Components
- RNNativeDatePicker - Native date/time picker with modal support
📚 20+ Expo Modules
Pre-configured, namespaced exports:
Media: AV, Camera, Image, ImagePicker, ImageManipulator, Video, MediaLibrary
Files: FileSystem, DocumentPicker, Sharing
System: Clipboard, Device, Constants, Haptics, Linking, Localization
Communication: Location, MailComposer
Speech: Speech Recognition (re-exported)
Quick Start
Installation
# In your mini app
npm install @platform-app/native-sdk-kitBasic Usage
import {
ImagePicker,
Location,
FileSystem,
ensureCameraPermissionForImagePicker
} from '@platform-app/native-sdk-kit';
// Permission helper automatically checks + requests
const result = await ensureCameraPermissionForImagePicker();
if (result.granted) {
const photo = await ImagePicker.launchCameraAsync({
quality: 0.8,
allowsEditing: true,
});
console.log('Photo taken:', photo.assets[0].uri);
}Installation & Configuration
Architecture Overview
This SDK is designed for monorepo architectures where:
MRI-Mono-Repo/
├── main-container-app/ # Main Expo app
├── platform-app-sdk-kit/ # This SDK
└── mini-apps/ # Feature packages
├── feature-app-1/
├── feature-app-2/
└── ...Key Concept:
- Peer dependencies install once in the main container
- Mini apps use the SDK without duplicating native modules
- This prevents version conflicts and reduces bundle size
Step 1: Install Peer Dependencies (Main Container Only)
⚠️ IMPORTANT: Install peer dependencies ONLY in your main container app, not in the SDK or mini apps.
# Navigate to your main container app
cd main-container-app/
# Install all peer dependencies
npx expo install \
react-native-date-picker \
expo-av \
expo-camera \
expo-image \
expo-image-picker \
expo-image-manipulator \
expo-media-library \
expo-video \
expo-document-picker \
expo-file-system \
expo-sharing \
expo-clipboard \
expo-constants \
expo-device \
expo-haptics \
expo-linking \
expo-localization \
expo-location \
expo-mail-composer \
expo-speech-recognitionStep 2: Configure Permissions (Main Container)
In your main container's app.json, add the required plugins:
{
"expo": {
"name": "Main Container App",
"plugins": [
[
"expo-camera",
{
"cameraPermission": "Allow $(PRODUCT_NAME) to access your camera",
"microphonePermission": "Allow $(PRODUCT_NAME) to access your microphone for video recording"
}
],
[
"expo-location",
{
"locationAlwaysAndWhenInUsePermission": "Allow $(PRODUCT_NAME) to use your location",
"locationAlwaysPermission": "Allow $(PRODUCT_NAME) to use your location in the background",
"locationWhenInUsePermission": "Allow $(PRODUCT_NAME) to use your location while using the app"
}
],
[
"expo-image-picker",
{
"photosPermission": "Allow $(PRODUCT_NAME) to access your photos",
"cameraPermission": "Allow $(PRODUCT_NAME) to access your camera to take photos"
}
],
[
"expo-media-library",
{
"photosPermission": "Allow $(PRODUCT_NAME) to access your photos",
"savePhotosPermission": "Allow $(PRODUCT_NAME) to save photos to your library",
"isAccessMediaLocationEnabled": true
}
],
[
"expo-av",
{
"microphonePermission": "Allow $(PRODUCT_NAME) to access your microphone for audio recording"
}
]
]
}
}Step 3: Install SDK in Mini Apps
In each mini app/package that needs native features:
# Navigate to your mini app
cd mini-apps/feature-app-1/
# Install the SDK
npm install @platform-app/native-sdk-kit
# or
yarn add @platform-app/native-sdk-kit✅ Do NOT install peer dependencies in mini apps - they automatically inherit them from the main container at runtime.
Step 4: Use the SDK in Mini Apps
// mini-apps/feature-app-1/src/CameraScreen.tsx
import {
ImagePicker,
Camera,
Location,
ensureCameraPermissionForImagePicker,
ensureForegroundLocationPermission
} from '@platform-app/native-sdk-kit';
export default function CameraScreen() {
const takePhoto = async () => {
// Permission helper checks + requests automatically
const result = await ensureCameraPermissionForImagePicker();
if (result.granted) {
const photo = await ImagePicker.launchCameraAsync({
quality: 0.8,
allowsEditing: true,
});
console.log('Photo:', photo.assets[0].uri);
} else if (!result.canAskAgain) {
// User denied permanently - direct to settings
Alert.alert(
'Camera Permission Required',
'Please enable camera access in Settings',
[{ text: 'Open Settings', onPress: () => Linking.openSettings() }]
);
}
};
return (
<Button title="Take Photo" onPress={takePhoto} />
);
}Step 5: Rebuild Main Container
After installing peer dependencies and configuring permissions:
# Navigate back to main container
cd main-container-app/
# Rebuild native code
npx expo prebuild --clean
# Run on iOS
npx expo run:ios
# Or run on Android
npx expo run:android
Comprehensive React Native SDK package providing native functionality through namespaced Expo modules:
- **RNNativeDatePicker** – Type-safe interface to `react-native-date-picker`
- **Permission helpers** – Internal helpers to check/request microphone and speech recognition permissions
- **Speech Recognition** – Complete speech-to-text functionality
- **Media & Camera** – Camera access, image/video picking, media library management, audio/video playback
- **File Management** – File system operations, document picking, image manipulation
- **System Integration** – Clipboard, sharing, haptics, linking, device info, location
- **Utilities** – Constants, localization, mail composer
All Expo modules are exported as **namespaces** to avoid naming conflicts and provide organized access to functionality.
## 📖 Documentation
- **[Quick Reference Guide](./docs/QUICK_REFERENCE.md)** - Fast examples and common patterns
- **[Complete API Documentation](./docs/API_DOCUMENTATION.md)** - Full API reference with UI styles
- **[Permission Helpers Usage](./docs/PERMISSION_HELPERS_USAGE.md)** - How to use permission helper utilities
- **[Permissions Guide](./docs/PERMISSIONS_GUIDE.md)** - Comprehensive permissions setup
- **[Permissions Required](./docs/PERMISSIONS_REQUIRED.md)** - Quick list of libraries needing permissions
## Installation
```bash
npm install platform-native-sdk-kit
# or
yarn add platform-native-sdk-kitPeer Dependencies
This package requires the following peer dependencies (all use * version):
Core:
reactreact-nativeexpo
Date & UI:
react-native-date-picker
Media & Camera:
expo-av- Audio/Video playback and recordingexpo-camera- Camera accessexpo-image- Optimized image componentexpo-image-picker- Pick images/videos from library or cameraexpo-image-manipulator- Resize, crop, rotate imagesexpo-media-library- Access device media libraryexpo-video- Video player component
File & Document:
expo-file-system- File system operationsexpo-document-picker- Pick documents from device
System & Utilities:
expo-clipboard- Clipboard operationsexpo-constants- System constants and app infoexpo-device- Device informationexpo-haptics- Haptic feedbackexpo-linking- Deep linking and URL handlingexpo-localization- Locale and timezone infoexpo-location- Location servicesexpo-mail-composer- Compose and send emailsexpo-sharing- Share files with other appsexpo-speech-recognition- Speech-to-text
# Install peer dependencies as needed
npm install expo expo-av expo-camera expo-image-picker # etc...For Non-Monorepo Projects
If you're using this SDK in a standalone app (not a monorepo):
# Install the SDK
npm install @platform-app/native-sdk-kit
# Install peer dependencies in the same project
npx expo install react-native-date-picker expo-av expo-camera expo-image \
expo-image-picker expo-image-manipulator expo-media-library expo-video \
expo-document-picker expo-file-system expo-sharing expo-clipboard \
expo-constants expo-device expo-haptics expo-linking expo-localization \
expo-location expo-mail-composer expo-speech-recognition
# Configure app.json with plugins (see Step 2 above)
# Rebuild
npx expo prebuild
npx expo run:ios # or run:androidPermission Helpers
The SDK includes 6 permission helper categories that simplify permission handling by automatically checking current status and requesting if needed.
Pattern
All helpers follow this pattern:
const result = await ensureXxxPermission();
if (result.granted) {
// ✅ Permission granted - use the feature
} else if (result.canAskAgain) {
// ⚠️ User denied but can ask again
Alert.alert('Permission needed', 'This feature requires permission');
} else {
// 🚫 User denied permanently - direct to settings
Alert.alert(
'Permission Required',
'Please enable permission in Settings',
[{ text: 'Open Settings', onPress: () => Linking.openSettings() }]
);
}1. Speech Recognition Permissions
import {
ensureSpeechRecognitionPermissions,
checkMicrophonePermission,
requestMicrophonePermission,
useSpeechRecognitionEvent
} from '@platform-app/native-sdk-kit';
// All-in-one: checks + requests both microphone and speech recognizer
const result = await ensureSpeechRecognitionPermissions();
if (result.granted) {
// Start speech recognition
}
// Check individual permissions
const micResult = await checkMicrophonePermission();
const speechResult = await checkSpeechRecognizerPermission();
// Request individual permissions
await requestMicrophonePermission();
await requestSpeechRecognizerPermission();Use Cases: Voice commands, transcription, voice search
2. Camera Permissions
⚠️ Note: expo-camera uses hooks for permission handling. Use the Camera namespace hooks directly:
import { Camera } from '@platform-app/native-sdk-kit';
function CameraComponent() {
const [cameraPermission, requestCameraPermission] = Camera.useCameraPermissions();
const [micPermission, requestMicPermission] = Camera.useMicrophonePermissions();
if (!cameraPermission?.granted) {
return <Button onPress={requestCameraPermission} title="Grant Camera Permission" />;
}
return (
<Camera.CameraView style={{ flex: 1 }} facing="back">
{/* Camera UI */}
</Camera.CameraView>
);
}Use Cases: Taking photos/videos, barcode scanning, AR features
3. Location Permissions
import {
ensureForegroundLocationPermission,
ensureBackgroundLocationPermission,
ensureFullLocationPermissions,
Location
} from '@platform-app/native-sdk-kit';
// Foreground location (while app is open)
const result = await ensureForegroundLocationPermission();
if (result.granted) {
const location = await Location.getCurrentPositionAsync({
accuracy: Location.Accuracy.High,
});
console.log('Lat:', location.coords.latitude);
console.log('Lon:', location.coords.longitude);
}
// Background location (requires foreground first)
const bgResult = await ensureBackgroundLocationPermission();
// Both foreground and background
const fullResult = await ensureFullLocationPermissions();
console.log('All granted:', fullResult.allGranted);Use Cases: Maps, navigation, geofencing, location tracking
4. Media Library Permissions
import {
ensureMediaLibraryPermission,
ensureFullMediaLibraryAccess,
hasLimitedPhotoAccess,
presentLimitedLibraryPicker,
MediaLibrary
} from '@platform-app/native-sdk-kit';
// Read access
const readResult = await ensureMediaLibraryPermission(false);
// Write access (for saving photos)
const writeResult = await ensureMediaLibraryPermission(true);
// Full access (read + write)
const fullResult = await ensureFullMediaLibraryAccess();
if (fullResult.allGranted) {
// Access all photos
const assets = await MediaLibrary.getAssetsAsync({
first: 20,
mediaType: 'photo',
});
}
// iOS 14+ Limited Photos
if (hasLimitedPhotoAccess(readResult)) {
// User selected limited photos
await presentLimitedLibraryPicker(); // Let user select more photos
}Use Cases: Photo gallery, saving images, photo backup
5. Image Picker Permissions
import {
ensureCameraPermissionForImagePicker,
ensureMediaLibraryPermissionForImagePicker,
ensureFullImagePickerPermissions,
hasLimitedPhotoAccessForImagePicker,
ImagePicker
} from '@platform-app/native-sdk-kit';
// Camera permission (for taking photos)
const cameraResult = await ensureCameraPermissionForImagePicker();
if (cameraResult.granted) {
const photo = await ImagePicker.launchCameraAsync({
quality: 0.8,
allowsEditing: true,
aspect: [4, 3],
});
}
// Media library permission (for picking photos)
const libraryResult = await ensureMediaLibraryPermissionForImagePicker();
if (libraryResult.granted) {
const photo = await ImagePicker.launchImageLibraryAsync({
allowsMultipleSelection: true,
mediaTypes: 'images',
});
}
// Both permissions
const fullResult = await ensureFullImagePickerPermissions();
console.log('All granted:', fullResult.allGranted);Use Cases: Profile pictures, photo uploads, image selection
6. Audio Recording Permissions
import {
ensureAudioRecordingPermission,
prepareForAudioRecording,
setAudioModeForRecording,
setAudioModeForPlayback,
AV
} from '@platform-app/native-sdk-kit';
// Simple permission check + request
const result = await ensureAudioRecordingPermission();
// Prepare for recording (permission + audio mode)
const prepared = await prepareForAudioRecording();
if (prepared.canRecord) {
const recording = new AV.Audio.Recording();
await recording.prepareToRecordAsync(
AV.Audio.RecordingOptionsPresets.HIGH_QUALITY
);
await recording.startAsync();
}
// Set audio mode manually
await setAudioModeForRecording({
allowsRecordingIOS: true,
playsInSilentModeIOS: true,
});
// Switch to playback mode
await setAudioModeForPlayback();Use Cases: Voice memos, audio messages, podcasts
Components
RNNativeDatePicker
Native date and time picker component with modal support.
Props
interface RNNativeDatePickerProps {
date: Date; // Current selected date
onDateChange?: (date: Date) => void; // Callback when date changes (inline mode)
mode?: 'date' | 'time' | 'datetime'; // Picker mode (default: 'date')
// Modal props
modal?: boolean; // Show as modal
open?: boolean; // Control modal visibility
onConfirm?: (date: Date) => void; // Callback when user confirms (modal)
onCancel?: () => void; // Callback when user cancels (modal)
// Constraints
minimumDate?: Date; // Minimum selectable date
maximumDate?: Date; // Maximum selectable date
// Localization
locale?: string; // Locale for date formatting (e.g., 'en', 'es')
// Styling (additional props from react-native-date-picker)
title?: string;
confirmText?: string;
cancelText?: string;
theme?: 'light' | 'dark' | 'auto';
}Example: Modal Date Picker
import { useState } from 'react';
import { View, Button, Text } from 'react-native';
import { RNNativeDatePicker } from '@platform-app/native-sdk-kit';
export default function DatePickerExample() {
## Exports (from `index.ts`)
| Category | Namespace Export | Description |
|----------|------------------|-------------|
| **Date Picker** | `RNNativeDatePicker` | Native date/time picker component |
| **Permission Helpers** | Direct exports | Speech and microphone permission utilities |
| **Speech Recognition** | Direct exports | All exports from `expo-speech-recognition` |
| **Media & Camera** | `AV`, `Video`, `ImagePicker`, `Image`, `MediaLibrary` | Audio/video playback, camera, image operations |
| **File & Document** | `FileSystem`, `DocumentPicker`, `Image` | File operations, document selection |
| **System Integration** | `Clipboard`, `Sharing`, `Haptics`, `Linking`, `MailComposer` | System-level integrations |
| **Device & Utilities** | `Device`, `Constants`, `Localization`, `Location` | Device info, app constants, locale, GPS |
---
## Usage
### RNNativeDatePicker
```tsx
import React, { useState } from 'react';
import { View, Button } from 'react-native';
import { RNNativeDatePicker } from 'platform-native-sdk-kit';
export default function App() {
const [date, setDate] = useState(new Date());
const [open, setOpen] = useState(false);
return (
<View>
<Button title="Select Date" onPress={() => setOpen(true)} />
<Text>Selected: {date.toLocaleDateString()}</Text>
<Button title="Open Date Picker" onPress={() => setOpen(true)} />
<RNNativeDatePicker
modal
open={open}
date={date}
onConfirm={(selectedDate) => {
setOpen(false);
setDate(selectedDate);
}}
onCancel={() => setOpen(false)}
/>
</View>
);
}Example: Time Picker
const [time, setTime] = useState(new Date());
const [showPicker, setShowPicker] = useState(false);
<RNNativeDatePicker
modal
open={showPicker}
date={time}
mode="time"
onConfirm={(selectedTime) => {
setTime(selectedTime);
setShowPicker(false);
}}
onCancel={() => setShowPicker(false)}
/>Example: DateTime Picker with Constraints
const [dateTime, setDateTime] = useState(new Date());
const [visible, setVisible] = useState(false);
const tomorrow = new Date();
tomorrow.setDate(tomorrow.getDate() + 1);
const nextMonth = new Date();
nextMonth.setMonth(nextMonth.getMonth() + 1);
<RNNativeDatePicker
modal
open={visible}
date={dateTime}
mode="datetime"
minimumDate={tomorrow}
maximumDate={nextMonth}
onConfirm={setDateTime}
onCancel={() => setVisible(false)}
title="Select appointment time"
confirmText="Book"
cancelText="Close"
/>Available Modules
All modules are exported with namespaces to avoid naming conflicts.
Media & Camera
| Module | Description | Key Functions |
|--------|-------------|---------------|
| AV | Audio/Video playback & recording | Audio.Sound(), Audio.Recording() |
| Camera | Camera access | CameraView, useCameraPermissions() |
| Image | Optimized image component | <Image /> component |
| ImagePicker | Pick/take photos | launchCameraAsync(), launchImageLibraryAsync() |
| ImageManipulator | Image editing | manipulateAsync() (resize, rotate, crop) |
| Video | Video player | <VideoView />, useVideoPlayer() |
| MediaLibrary | Photo library access | getAssetsAsync(), saveToLibraryAsync() |
File Management
| Module | Description | Key Functions |
|--------|-------------|---------------|
| FileSystem | File operations | readAsStringAsync(), writeAsStringAsync(), downloadAsync() |
| DocumentPicker | Document selection | getDocumentAsync() |
| Sharing | Share files/content | shareAsync() |
System Integration
| Module | Description | Key Functions |
|--------|-------------|---------------|
| Clipboard | Copy/paste | setStringAsync(), getStringAsync() |
| Device | Device information | deviceName, osVersion, modelName |
| Constants | App constants | expoConfig, systemVersion |
| Haptics | Vibration feedback | impactAsync(), notificationAsync() |
| Linking | URLs & deep links | openURL(), canOpenURL(), openSettings() |
| Localization | Locale information | locale, timezone, isRTL |
Communication
| Module | Description | Key Functions |
|--------|-------------|---------------|
| Location | GPS & geolocation | getCurrentPositionAsync(), watchPositionAsync() |
| MailComposer | Email composition | composeAsync() |
Speech
All exports from expo-speech-recognition are re-exported directly:
import {
useSpeechRecognitionEvent,
ExpoSpeechRecognitionModule
} from '@platform-app/native-sdk-kit';📚 For detailed API documentation of each module, see:
Common Use Cases
Taking and Saving a Photo
import {
ImagePicker,
MediaLibrary,
ensureCameraPermissionForImagePicker,
ensureMediaLibraryPermission
} from '@platform-app/native-sdk-kit';
async function takeAndSavePhoto() {
// Check camera permission
const cameraResult = await ensureCameraPermissionForImagePicker();
if (!cameraResult.granted) {
Alert.alert('Camera permission required');
return;
}
// Take photo
const photo = await ImagePicker.launchCameraAsync({
quality: 0.8,
allowsEditing: true,
aspect: [4, 3],
});
if (photo.canceled) return;
// Save to library
const libraryResult = await ensureMediaLibraryPermission(true);
if (libraryResult.granted) {
await MediaLibrary.saveToLibraryAsync(photo.assets[0].uri);
Alert.alert('Success', 'Photo saved to library!');
}
}Recording Audio
import {
prepareForAudioRecording,
setAudioModeForPlayback,
AV
} from '@platform-app/native-sdk-kit';
async function recordAudio() {
// Prepare for recording (permission + audio mode)
const result = await prepareForAudioRecording();
if (!result.canRecord) {
Alert.alert('Microphone permission required');
return;
}
// Start recording
const recording = new AV.Audio.Recording();
await recording.prepareToRecordAsync(
AV.Audio.RecordingOptionsPresets.HIGH_QUALITY
);
await recording.startAsync();
// ... later, stop recording
await recording.stopAndUnloadAsync();
const uri = recording.getURI();
// Play back
await setAudioModeForPlayback();
const sound = new AV.Audio.Sound();
await sound.loadAsync({ uri });
await sound.playAsync();
// Cleanup
await sound.unloadAsync();
}Getting Location
import {
ensureForegroundLocationPermission,
Location
} from '@platform-app/native-sdk-kit';
async function getUserLocation() {
const result = await ensureForegroundLocationPermission();
if (!result.granted) {
Alert.alert('Location permission required');
return;
}
const location = await Location.getCurrentPositionAsync({
accuracy: Location.Accuracy.High,
});
console.log('Latitude:', location.coords.latitude);
console.log('Longitude:', location.coords.longitude);
console.log('Altitude:', location.coords.altitude);
// Watch location changes
const subscription = await Location.watchPositionAsync(
{
accuracy: Location.Accuracy.High,
distanceInterval: 10, // Update every 10 meters
},
(newLocation) => {
console.log('New location:', newLocation.coords);
}
);
// Cleanup
// subscription.remove();
}File Operations
import { FileSystem } from '@platform-app/native-sdk-kit';
async function fileOperations() {
// Define file path
const filePath = FileSystem.documentDirectory + 'data.json';
// Write data
const data = { user: 'John', timestamp: Date.now() };
await FileSystem.writeAsStringAsync(
filePath,
JSON.stringify(data, null, 2)
);
// Read data
const content = await FileSystem.readAsStringAsync(filePath);
const parsedData = JSON.parse(content);
console.log('Read data:', parsedData);
// Check if file exists
const fileInfo = await FileSystem.getInfoAsync(filePath);
console.log('File exists:', fileInfo.exists);
console.log('File size:', fileInfo.size);
// Download file
const downloadResult = await FileSystem.downloadAsync(
'https://example.com/file.pdf',
FileSystem.documentDirectory + 'download.pdf'
);
console.log('Downloaded to:', downloadResult.uri);
// Delete file
await FileSystem.deleteAsync(filePath);
}Clipboard Operations
import { Clipboard } from '@platform-app/native-sdk-kit';
async function clipboardOperations() {
// Copy text
await Clipboard.setStringAsync('Hello, world!');
// Paste text
const text = await Clipboard.getStringAsync();
console.log('Clipboard:', text);
// Check if clipboard has content
const hasContent = await Clipboard.hasStringAsync();
if (hasContent) {
console.log('Clipboard has content');
}
// Copy URL
await Clipboard.setUrlAsync('https://example.com');
}Sharing Content
import { Sharing, FileSystem } from '@platform-app/native-sdk-kit';
async function shareContent() {
// Share text (save as file first)
const textPath = FileSystem.cacheDirectory + 'share.txt';
await FileSystem.writeAsStringAsync(textPath, 'Text to share');
await Sharing.shareAsync(textPath);
// Share file with options
await Sharing.shareAsync(fileUri, {
mimeType: 'application/pdf',
dialogTitle: 'Share Document',
UTI: 'public.pdf', // iOS only
});
}Haptic Feedback
import { Haptics } from '@platform-app/native-sdk-kit';
function hapticExamples() {
// Button press feedback
await Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
await Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium);
await Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Heavy);
// Notification feedback
await Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success);
await Haptics.notificationAsync(Haptics.NotificationFeedbackType.Warning);
await Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error);
// Selection feedback (e.g., picker wheel)
await Haptics.selectionAsync();
}Opening URLs
import { Linking } from '@platform-app/native-sdk-kit';
async function openLinks() {
// Open website
await Linking.openURL('https://example.com');
// Make phone call
const phoneNumber = '+1234567890';
const canCall = await Linking.canOpenURL(`tel:${phoneNumber}`);
if (canCall) {
await Linking.openURL(`tel:${phoneNumber}`);
}
// Send email
await Linking.openURL('mailto:[email protected]?subject=Help&body=Hello');
// Open maps
await Linking.openURL('geo:37.7749,-122.4194');
// Open app settings
await Linking.openSettings();
}Permission Handling
Permission Result Interface
All permission helpers return a consistent interface:
interface PermissionResult {
granted: boolean; // Whether permission is granted
canAskAgain: boolean; // Whether we can request again
status: 'granted' | 'denied' | 'undetermined';
response: PermissionResponseLike; // Raw response
}Handling Permission States
const result = await ensureCameraPermission();
if (result.granted) {
// ✅ Permission granted - use the feature
await Camera.takePictureAsync();
} else if (result.canAskAgain) {
// ⚠️ User denied but can ask again
Alert.alert(
'Camera Permission',
'This feature requires camera access',
[
{ text: 'Cancel', style: 'cancel' },
{ text: 'Try Again', onPress: () => requestCameraPermission() }
]
);
} else {
// 🚫 User denied permanently - direct to settings
Alert.alert(
'Camera Permission Required',
'Please enable camera access in Settings to use this feature',
[
{ text: 'Cancel', style: 'cancel' },
{ text: 'Open Settings', onPress: () => Linking.openSettings() }
]
);
}iOS 14+ Limited Photos Access
When users select "Limited Photos" on iOS 14+:
import {
ensureMediaLibraryPermissionForImagePicker,
hasLimitedPhotoAccessForImagePicker,
ImagePicker
} from '@platform-app/native-sdk-kit';
const result = await ensureMediaLibraryPermissionForImagePicker();
if (result.granted) {
if (hasLimitedPhotoAccessForImagePicker(result)) {
// User selected limited photos
Alert.alert(
'Limited Photos Access',
'You have limited photo access. Select more photos?',
[
{ text: 'No', style: 'cancel' },
{
text: 'Select Photos',
onPress: async () => {
// This opens iOS photo selector to add more photos
await presentLimitedLibraryPicker();
}
}
]
);
}
// Continue with image picker
const photo = await ImagePicker.launchImageLibraryAsync();
}Best Practices
✅ Do
// Use namespace imports
import { FileSystem, ImagePicker } from '@platform-app/native-sdk-kit';
FileSystem.writeAsStringAsync(...);
// Use permission helpers
const result = await ensureCameraPermission();
if (result.granted) {
// Use feature
}
// Handle all permission states
if (!result.canAskAgain) {
// Direct to settings
Linking.openSettings();
}
// Clean up resources
const sound = new AV.Audio.Sound();
await sound.loadAsync(require('./audio.mp3'));
await sound.playAsync();
// ... later
await sound.unloadAsync(); // ✅ Always unload
// Handle errors
try {
const photo = await ImagePicker.launchCameraAsync();
} catch (error) {
console.error('Camera error:', error);
Alert.alert('Error', 'Failed to access camera');
}
// Use TypeScript for type safety
const location: Location.LocationObject = await Location.getCurrentPositionAsync();❌ Don't
// Don't destructure directly from package
import { writeAsStringAsync } from '@platform-app/native-sdk-kit'; // ❌ Won't work
// Don't skip permission checks
await Camera.takePictureAsync(); // ❌ No permission check
// Don't forget cleanup
const sound = new AV.Audio.Sound();
await sound.playAsync(); // ❌ Never unloaded (memory leak)
// Don't ignore errors
const photo = await ImagePicker.launchCameraAsync(); // ❌ No error handling
// Don't forget to check canAskAgain
if (!result.granted) {
Alert.alert('Permission denied'); // ❌ Doesn't handle permanent denial
}Memory Management
// Audio cleanup
const sound = new AV.Audio.Sound();
await sound.loadAsync(require('./audio.mp3'));
await sound.playAsync();
// ... when done
await sound.unloadAsync(); // ✅
// Location subscription cleanup
const subscription = await Location.watchPositionAsync(...);
// ... when done
subscription.remove(); // ✅
// Use in React components
useEffect(() => {
const subscription = Location.watchPositionAsync(...);
return () => {
subscription.then(sub => sub.remove()); // ✅ Cleanup on unmount
};
}, []);Performance Tips
- Lazy load audio files - Only load when needed
- Compress images - Use
qualityoption in ImagePicker (0.7-0.8 recommended) - Throttle location updates - Use
distanceIntervaloption to reduce updates - Cache files - Use
FileSystem.cacheDirectoryfor temporary files - Clean up subscriptions - Always remove event listeners and subscriptions
Troubleshooting
"Module has no exported member"
Problem:
import { writeAsStringAsync } from '@platform-app/native-sdk-kit'; // ❌ ErrorSolution:
import { FileSystem } from '@platform-app/native-sdk-kit'; // ✅ Use namespace
FileSystem.writeAsStringAsync(...);All modules are namespaced - import the namespace, not individual functions.
Permission Always Denied
Problem: Permission request always returns denied.
Solution:
const result = await Camera.getCameraPermissionsAsync();
if (!result.canAskAgain) {
// User denied permanently - direct to settings
Alert.alert(
'Permission Required',
'Please enable camera access in Settings',
[{ text: 'Open Settings', onPress: () => Linking.openSettings() }]
);
}Peer Dependencies Not Found
Problem: Cannot find module 'expo-camera' or similar error in mini apps.
Solution:
- Ensure peer dependencies are installed in the main container app
- Mini apps inherit them at runtime through the monorepo structure
- Verify your build process correctly resolves peer dependencies
- Try rebuilding:
cd main-container-app && npx expo prebuild --clean
TypeScript Errors
Problem: TypeScript can't find types.
Solution:
- Restart TypeScript server:
Cmd+Shift+P→ "TypeScript: Restart TS Server" - Check
tsconfig.jsonincludes the SDK - Ensure
@types/reactand@types/react-nativeare installed - Use autocomplete/IntelliSense to discover available functions
Audio Won't Play
Problem: Audio playback fails silently.
Solution:
// Set audio mode first
await AV.Audio.setAudioModeAsync({
playsInSilentModeIOS: true,
shouldDuckAndroid: true,
staysActiveInBackground: false,
});
// Then play
const sound = new AV.Audio.Sound();
await sound.loadAsync(require('./audio.mp3'));
await sound.playAsync();Or use the helper:
import { setAudioModeForPlayback } from '@platform-app/native-sdk-kit';
await setAudioModeForPlayback();
// Then play audioCamera Shows Black Screen
Problem: Camera view is black or doesn't load.
Solutions:
- Check permissions:
const [permission, requestPermission] = Camera.useCameraPermissions();
if (!permission?.granted) {
await requestPermission();
}Test on real device - Camera doesn't work in iOS Simulator
Check app.json configuration:
{
"expo": {
"plugins": [
[
"expo-camera",
{
"cameraPermission": "Allow app to access camera"
}
]
]
}
}- Rebuild native code:
npx expo prebuild --clean
npx expo run:iosFile Not Found
Problem: FileSystem operations fail with "file not found".
Solution:
// Always use full paths
const filePath = FileSystem.documentDirectory + 'file.txt';
// Check if file exists first
const fileInfo = await FileSystem.getInfoAsync(filePath);
if (fileInfo.exists) {
// File exists - read it
const content = await FileSystem.readAsStringAsync(filePath);
} else {
// Create file first
await FileSystem.writeAsStringAsync(filePath, 'Initial content');
}
// Create directory if needed
await FileSystem.makeDirectoryAsync(
FileSystem.documentDirectory + 'myFolder/',
{ intermediates: true }
);API Reference
For detailed API documentation, see:
- Expo Documentation - Official Expo module documentation
- React Native Date Picker - Date picker component docs
Package Information
- Package Name:
@platform-app/native-sdk-kit - Version: 1.0.6
- License: MIT
- Author: MRI Software
- TypeScript: ✅ Full support with type definitions
Summary
This SDK provides:
✅ 20+ Native Modules - Camera, Location, Files, Audio, Video, and more
✅ 6 Permission Helper Categories - Simplified permission management
✅ Monorepo Optimized - Peer dependencies install once in main container
✅ Type-Safe - Full TypeScript definitions
✅ Cross-Platform - iOS & Android support
✅ Production Ready - Used in MRI OTA Monorepo
Get started now:
# In your mini app
npm install @platform-app/native-sdk-kitimport { ImagePicker, ensureCameraPermissionForImagePicker } from '@platform-app/native-sdk-kit';
const result = await ensureCameraPermissionForImagePicker();
if (result.granted) {
const photo = await ImagePicker.launchCameraAsync();
}Happy coding! 🚀
MIT License © 2024 MRI Software
Permission helpers (speech / microphone)
Request or check permissions before starting speech recognition:
import {
ensureSpeechRecognitionPermissions,
type SpeechRecognitionPermissionResult,
} from 'platform-native-sdk-kit';
// Before starting recognition: check, then request if needed
const result = await ensureSpeechRecognitionPermissions();
if (result.granted) {
// Start speech recognition
} else if (!result.canAskAgain) {
// Open app settings
} else {
// User denied; show message
}Available helpers:
checkSpeechRecognitionPermissions()– Check both speech + microphone (no dialog).requestSpeechRecognitionPermissions()– Request both (shows system dialog).ensureSpeechRecognitionPermissions()– Check first; request if not granted.checkMicrophonePermission()/requestMicrophonePermission()– Microphone only.checkSpeechRecognizerPermission()/requestSpeechRecognizerPermission()– Speech recognizer only (mainly iOS).
SpeechRecognitionPermissionResult: { granted, canAskAgain, status, response }.
Speech recognition (expo-speech-recognition)
All APIs from expo-speech-recognition are re-exported. Use them as you would from that package:
import {
ExpoSpeechRecognitionModule,
useSpeechRecognitionEvent,
} from 'platform-native-sdk-kit';See expo-speech-recognition for full API and setup (e.g. config plugin, permissions in app.json).
Namespaced Expo Modules
All Expo modules are exported as namespaces. Import and use them with their namespace:
import {
FileSystem,
Clipboard,
ImagePicker,
Sharing,
AV,
Device,
Location
} from '@platform-app/native-sdk-kit';
// File System
const file = new FileSystem.File(FileSystem.Paths.cache, 'example.txt');
await file.write('Hello, world!');
const content = await file.text();
// Clipboard
await Clipboard.setStringAsync('Copied text');
const text = await Clipboard.getStringAsync();
// Image Picker
const result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ImagePicker.MediaTypeOptions.Images,
});
// Sharing
await Sharing.shareAsync(fileUri, {
mimeType: 'application/pdf',
dialogTitle: 'Share this document'
});
// Audio/Video
const sound = new AV.Audio.Sound();
await sound.loadAsync(require('./assets/sound.mp3'));
await sound.playAsync();
// Device Info
const deviceName = Device.deviceName;
const osVersion = Device.osVersion;
// Location
const location = await Location.getCurrentPositionAsync({
accuracy: Location.Accuracy.High
});Quick Reference - Namespaced Modules
| Namespace | Use Case | Key Functions |
|-----------|----------|---------------|
| AV | Audio/video playback & recording | Audio.Sound(), Video component |
| Clipboard | Copy/paste operations | setStringAsync(), getStringAsync() |
| Constants | App and system info | expoConfig, systemVersion |
| Device | Device information | deviceName, osVersion, modelName |
| DocumentPicker | Pick documents | getDocumentAsync() |
| FileSystem | File operations | File, Directory, Paths |
| Haptics | Vibration feedback | impactAsync(), notificationAsync() |
| Image | Optimized images | <Image /> component |
| ImagePicker | Pick/take photos | launchImageLibraryAsync(), launchCameraAsync() |
| Linking | URLs and deep links | openURL(), canOpenURL() |
| Localization | Locale info | locale, timezone, isRTL |
| Location | GPS and positioning | getCurrentPositionAsync() |
| MailComposer | Send emails | composeAsync() |
| MediaLibrary | Access photos/videos | getAssetsAsync(), createAssetAsync() |
| Sharing | Share files | shareAsync(), isAvailableAsync() |
| Video | Video player | <Video /> component |
See the API_DOCUMENTATION.md for detailed examples and all available APIs.
API
RNNativeDatePicker
Accepts all props from react-native-date-picker.
| Prop | Type | Description |
|------|------|--------------|
| modal | boolean | Display as modal |
| open | boolean | Modal open state |
| date | Date | Currently selected date |
| onConfirm | (date: Date) => void | Called when date is confirmed |
| onCancel | () => void | Called when picker is cancelled |
| mode | 'date' | 'time' | 'datetime' | Picker mode |
| locale | string | Locale for date formatting |
TypeScript
This package is written in TypeScript and ships with type definitions.
License
MIT ©
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
Issues
If you encounter any problems, please file an issue along with a detailed description.
