> ## Documentation Index
> Fetch the complete documentation index at: https://docs.vibely.sh/llms.txt
> Use this file to discover all available pages before exploring further.

# Native capabilities

> Camera, push, biometrics, geolocation, secure storage, deep links

Anything beyond `<View>` and `<Text>` — camera, microphone, push notifications, biometrics, files — uses an Expo SDK module. The agent picks the module, asks for permissions correctly, and wires it into your screens. This page is what happens under the hood.

<Frame>
  <img src="https://cdn.vibely.sh/doc/v1/mobile-app-native.webp" alt="Native capabilities" width="1200" height="675" />
</Frame>

## Permissions, the right way

Expo's permission helpers are wrapped consistently:

```ts theme={"system"}
const [status, requestPermission] = useCameraPermissions()

if (status?.granted !== true) {
  await requestPermission()
}
```

The agent always **requests at the moment the user takes the action that needs it**, never on app launch. iOS and Play Store reviewers reject apps that ask for everything up front, and users decline.

## Camera

Module: `expo-camera`. Use cases: photo capture, barcode scanning, ID upload.

```ts theme={"system"}
import { CameraView } from "expo-camera"

<CameraView facing="back" onBarcodeScanned={…} />
```

Web preview limitation: camera does **not** work in the browser preview. Test on Expo Go.

## Push notifications

Module: `expo-notifications`. Wire-up:

1. Request permission on first interaction with notifications (e.g. "Enable reminders").
2. Get the device token (`getExpoPushTokenAsync`).
3. Store the token against the user row in Supabase.
4. Send pushes server-side via the [Expo push API](https://docs.expo.dev/push-notifications/sending-notifications/) — easiest from a Supabase Edge Function.

For local notifications (e.g. "remind me at 8 AM"), schedule with `Notifications.scheduleNotificationAsync`. No server needed.

Push doesn't work in the web preview or in iOS Simulator without a paid Apple Developer account. Test on a real device via Expo Go (or a custom EAS build for production).

## Biometrics

Module: `expo-local-authentication`. Use for "Unlock with Face ID" / "Confirm with fingerprint."

```ts theme={"system"}
import * as LocalAuthentication from "expo-local-authentication"

const { success } = await LocalAuthentication.authenticateAsync({
  promptMessage: "Unlock your habits",
  fallbackLabel: "Use passcode",
})
```

Don't use biometrics for primary auth — the agent will pair it with a Supabase session token and use biometrics only to **gate access to a token already on the device**.

## Secure storage

Module: `expo-secure-store`. Backed by Keychain on iOS and EncryptedSharedPreferences on Android. Use for **session tokens, refresh tokens, anything sensitive**.

```ts theme={"system"}
import * as SecureStore from "expo-secure-store"

await SecureStore.setItemAsync("supabase.access_token", token)
const token = await SecureStore.getItemAsync("supabase.access_token")
```

Use `AsyncStorage` (also pre-installed) for non-sensitive data — it's faster and supports larger payloads.

## Geolocation

Module: `expo-location`. Two permission tiers:

* **When in use** — fine grained, works while the app is foregrounded. Default ask.
* **Always** — required for background tracking. Triggers an OS-level "always allow" prompt that's notoriously hard to get past on iOS. Avoid unless the feature genuinely needs it.

```ts theme={"system"}
const { status } = await Location.requestForegroundPermissionsAsync()
const loc = await Location.getCurrentPositionAsync({})
```

## Deep links

Configured in `app.json`:

```json theme={"system"}
{
  "expo": {
    "scheme": "habits",
    "deepLinking": true
  }
}
```

Open `habits://habit/123` in any browser or message and Expo Router lands on `app/habit/[id].tsx`. Universal links (`https://habits.example.com/...`) need an `apple-app-site-association` and `assetlinks.json` published on your domain — the agent will tell you the exact files.

## In-app purchases

iOS and Play Store policy require IAP for digital goods. Web checkout (Stripe) is **not allowed** for unlocking app features. Two routes:

* **`expo-in-app-purchases`** — direct integration with StoreKit / Play Billing. Free, but you handle receipt validation server-side yourself.
* **RevenueCat** (via [Connectors](/integrations/connectors/overview)) — managed receipt validation, entitlements, analytics. Free up to \$10K MTR.

For physical goods or real-world services (Uber-style), Stripe is fine. The store rules apply only to digital content.

## What doesn't work in Expo Go

A small set of features require a custom EAS build:

* Custom native modules (anything not in the Expo SDK).
* Some payment SDKs (RevenueCat works in Expo Go via a config plugin; raw `react-native-purchases` does not).
* Some push providers other than Expo's own.
* App Clips / App Extensions.

The agent will tell you when a request crosses the Expo Go line. From there it's a one-time `eas build --profile development` and you install the resulting build instead of Expo Go.


## Related topics

- [From idea to the App Store](/guides/from-idea-to-store.md)
- [Build your first mobile app](/features/mobile-apps/quickstart.md)
- [Mobile App Mode](/features/mobile-apps/overview.md)
- [Prompt library](/prompting/library.md)
- [Manage connectors in your workspace](/integrations/admin-controls.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.