Best Practices
Overview
Following mobile best practices ensures your app delivers a smooth user experience, passes store review, and handles system permissions correctly. This guide covers the essential patterns for Expo-based mobile apps built on Emergent.
Tip
For better results, start with a strong prompt. See Write prompts that work for prompting patterns that lead to higher-quality mobile builds.
Device Permission Best Practices
Modern mobile apps must request runtime permissions for sensitive features like the camera, location, or contacts. Expo provides a consistent cross-platform API, but the user experience and system behavior differ between iOS and Android.
Request Permissions Contextually
Always ask for a permission immediately before you need it, not on app launch. Users are far more likely to grant access when they understand why your app needs it.
Show value first
Display a screen or modal explaining what the feature does before calling
requestPermissionsAsync. For example, show a "Scan a barcode to add items" prompt before requesting camera access.The Four Permission States
Expo permission hooks return one of four states:
| State | Description | Action |
|---|---|---|
| User has not been asked yet | Call |
| Permission allowed | Proceed with feature |
| User declined, but can be asked again | Show rationale, then re-request |
+ | User declined and selected "Don't ask again" (Android) or denied twice (iOS) | Deep-link to Settings |
The
canAskAgain boolean tells you whether calling requestPermissionsAsync will show the system dialog or silently return denied.
import * as Camera from 'expo-camera';
const { status, canAskAgain } = await Camera.getCameraPermissionsAsync();
if (status === 'granted') {
// proceed
} else if (canAskAgain) {
const { status: newStatus } = await Camera.requestCameraPermissionsAsync();
if (newStatus === 'granted') {
// proceed
}
} else {
// blocked - guide user to Settings
}Deep-Link to Settings When Blocked
When
canAskAgain is false, the only way forward is for the user to manually enable the permission in system Settings. Use expo-linking to open the app's settings page:
import * as Linking from 'expo-linking';
if (!canAskAgain) {
Alert.alert(
'Camera Access Required',
'Please enable camera access in Settings to use this feature.',
[
{ text: 'Cancel', style: 'cancel' },
{ text: 'Open Settings', onPress: () => Linking.openSettings() }
]
);
}Test the blocked state
During development, intentionally deny permissions multiple times to verify your Settings deep-link flow works on both platforms.
iOS Usage Description Strings
Apple requires a human-readable explanation for every permission your app requests. These strings appear in the system permission dialog and are mandatory for App Store review.
Add them to the
ios section of your app.json:
{
"expo": {
"ios": {
"infoPlist": {
"NSCameraUsageDescription": "This app uses the camera to scan barcodes and take photos of receipts.",
"NSPhotoLibraryUsageDescription": "This app accesses your photo library to let you upload images.",
"NSLocationWhenInUseUsageDescription": "This app uses your location to show nearby stores."
}
}
}
}Each
NS*UsageDescription key corresponds to a specific permission. Common keys include:
- CameraNSCameraUsageDescription
- Photo library readNSPhotoLibraryUsageDescription
- Photo library write (iOS 11+)NSPhotoLibraryAddUsageDescription
- MicrophoneNSMicrophoneUsageDescription
- Location while app is openNSLocationWhenInUseUsageDescription
- Background locationNSLocationAlwaysUsageDescription
- ContactsNSContactsUsageDescription
- CalendarNSCalendarsUsageDescription
- RemindersNSRemindersUsageDescription
- Motion & fitness sensorsNSMotionUsageDescription
- Face IDNSFaceIDUsageDescription
Emergent regenerates the native project
When you update
app.json and rebuild, Emergent agents automatically merge your usage descriptions into the iOS Info.plist. No manual Xcode changes are needed.Write clear, specific descriptions that explain the user benefit - not just the technical capability. Apple rejects vague strings like "This app needs camera access."
Android Permissions in app.json
Android permissions are declared in the
android.permissions array. Expo auto-includes common permissions based on the libraries you import, but you can add extras if needed:
{
"expo": {
"android": {
"permissions": [
"CAMERA",
"ACCESS_FINE_LOCATION"
]
}
}
}Unlike iOS, Android does not require usage descriptions in the manifest (though showing your own rationale UI is still recommended).

