platform-app-native-sdk-kit
v1.0.9
Published
Platform Native SDK Kit for MRI OTA Mono
Downloads
226
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.
