Mobile Apps

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:

StateDescriptionAction
undetermined
User has not been asked yetCall
requestPermissionsAsync
granted
Permission allowedProceed with feature
denied
User declined, but can be asked againShow rationale, then re-request
denied
+
canAskAgain=false
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
.

TypeScript
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
}

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:

TypeScript
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
:

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:

  • NSCameraUsageDescription
    - Camera
  • NSPhotoLibraryUsageDescription
    - Photo library read
  • NSPhotoLibraryAddUsageDescription
    - Photo library write (iOS 11+)
  • NSMicrophoneUsageDescription
    - Microphone
  • NSLocationWhenInUseUsageDescription
    - Location while app is open
  • NSLocationAlwaysUsageDescription
    - Background location
  • NSContactsUsageDescription
    - Contacts
  • NSCalendarsUsageDescription
    - Calendar
  • NSRemindersUsageDescription
    - Reminders
  • NSMotionUsageDescription
    - Motion & fitness sensors
  • NSFaceIDUsageDescription
    - Face ID

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:

JSON
{
 "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).

Was this page helpful?

Related pages