Device features
A phone's camera, its location and its notifications are what make a mobile app worth installing, and each one sits behind a permission the user can refuse. This lesson shows you how the Mangrove Rider app asks at the right moment, keeps working when the answer is no, captures a small proof photo and a drop location, and sends a "new job" push that riders want to receive.
By the end you can
- Design a permission flow as a small state machine: explain, ask, use, or send the rider to Settings, always with a fallback
- Take a proof-of-delivery photo with expo-camera and shrink it to a size that uploads on patchy mobile data
- Record a drop location in the foreground with the right accuracy, and judge when background location is really needed
- Explain how a push travels from your server through Expo, FCM and APNs to the phone, and register and refresh push tokens
- Set up Android notification channels, ask for notification permission on Android 13+ and iOS, and open the right job when a rider taps
The everyday picture
Think of a rider arriving at a tall apartment building with a guard at the gate. A good rider does not shout "let me in" from the street before he even has a parcel. He walks up when he has a delivery, shows the order on his phone and says which flat it is for. The guard has a reason, so he usually says yes.
Sometimes the guard still says no. A good rider does not argue every day. He calls the customer, who comes down to the gate and reads out a code, and the delivery still happens. And if the building office has put the rider on a "do not admit" list, asking the guard again is pointless: only the office can change it.
Phone permissions work the same way. Ask when the feature is needed, with a reason the person can see. Have a way to finish the job without it. And when the operating system has stopped asking, the only door left is the phone's Settings app, which the user opens and changes themselves.
Permissions are a small state machine
Camera, location and notifications are runtime permissions. The app cannot draw the system dialog itself or change its wording, and it gets very few chances to show it. In Expo every permission call returns the same shape, a PermissionResponse with three fields that matter:
status:'undetermined'(never asked),'granted'or'denied'.canAskAgain:falsewhen the operating system will no longer show its dialog. From then on, only the Settings app can change the answer.granted: a shortcut forstatus === 'granted'.
The two platforms spend your chances differently. On iOS the system dialog appears once: a single "Don't Allow" sets canAskAgain to false. On Android, the Android docs say that if the user taps Deny more than once for the same permission, the system treats it as "don't ask again" and stops showing the dialog. Since Android 11, camera and location dialogs also offer Only this time, so a permission can be gone the next time the rider opens the app. That is why you check the status every time the feature starts, never once at launch.
Because the system dialog is precious, show your own explanation screen first. It costs nothing: it is a normal screen with a short reason and two buttons, Continue and Not now. Only Continue calls the system request. If the rider taps Not now, you have spent nothing and can ask next time. The flow becomes four outcomes:
- use: permission granted, open the camera.
- explain: not granted yet and the rider has not seen the reason in this flow, so show the explanation screen.
- ask: the rider tapped Continue, so call the system request.
- settings:
canAskAgainisfalse, so show a button that opens the app's page in Settings withLinking.openSettings()from React Native.
Every branch except use also offers the fallback. For proof of delivery that is the one-time code the customer reads out from the Mangrove app. Both stores ask for this: Apple's review guidelines tell apps to offer alternatives where possible when people refuse access, and Android's docs say to degrade gracefully so the user can carry on.
Quick check
The Rider app asks for camera permission on its very first screen, before the rider has seen a job. An iPhone rider taps "Don't Allow". What happens next week, when she taps "Take photo" at a door?
On iOS one refusal sets canAskAgain to false, and no amount of explaining brings the system dialog back. Asking on the first screen spent the only chance at the moment it was least likely to work.