skip to content

SDK Packages

The Expo SDK's native packages move as one set pinned to a React Native release, share one permissions model and extend in Swift and Kotlin. Interviewers probe upgrades and permissions.

part ofExpo (React Native)overview, primer and where to startread it →
on this pageshow

explore

questions

19

In an Expo app, how do you take a photo with expo-camera's CameraView, and what must the code wait for before capturing?

level: juniorimportance: must knowfreq 50%

answer

  1. a ref to the camera component
  2. permission first, preview second
  3. onCameraReady before capturing
  4. takePictureAsync returns a temporary uri
  5. one active preview at a time

basics

~20 s

Render CameraView once camera permission is granted, keep a ref, wait for onCameraReady, then call ref.current.takePictureAsync(options). It resolves with a uri to a temporary cache file, so copy it somewhere permanent before relying on it.

solid answer

~40 s

`CameraView` from `expo-camera` renders the live preview; `facing` defaults to `'back'` and `flash` to `'off'`. I gate it on `useCameraPermissions`, hold a ref, and enable the capture button only after `onCameraReady` fires, because the docs say to wait for it before calling `takePictureAsync`. The call accepts options such as `quality` (0 to 1, default 1), `base64`, `exif` and `skipProcessing`, and resolves with `uri`, `width` and `height`. The `uri` points to a temporary file in the app's cache, so a receipt must be copied to permanent storage. Only one camera preview can be active at a time, so the `CameraView` should unmount when its screen loses focus. On iOS the `active` prop can also stop the session without unmounting.

code

tsx · 27 lines
tsx
import { CameraView, useCameraPermissions } from 'expo-camera';
import { useRef, useState } from 'react';
import { Button, View } from 'react-native';

export function ReceiptCamera({ onCaptured }: { onCaptured: (uri: string) => void }) {
  const [permission, requestPermission] = useCameraPermissions();
  const camera = useRef<CameraView>(null);
  const [ready, setReady] = useState(false);

  if (!permission) return null;
  if (!permission.granted) {
    return <Button title="Allow camera to scan receipts" onPress={requestPermission} />;
  }

  const capture = async () => {
    const photo = await camera.current?.takePictureAsync({ quality: 0.7 });
    // photo.uri is a temporary cache file: copy it before saving the receipt.
    if (photo) onCaptured(photo.uri);
  };

  return (
    <View style={{ flex: 1 }}>
      <CameraView ref={camera} style={{ flex: 1 }} facing="back" onCameraReady={() => setReady(true)} />
      <Button title="Capture receipt" disabled={!ready} onPress={capture} />
    </View>
  );
}

go deeper

for a junior

Recall the sequence: permission, a ref to CameraView, wait for onCameraReady, then takePictureAsync, and that the returned uri is temporary.

for a middle

Explain the capture options, quality, base64, exif and skipProcessing, and why only one preview can be active at a time.

for a senior

Diagnose black previews and failed captures across navigators and platforms, and decide which native options, such as barcode scanning or audio permission, a receipt app should drop.

for a principal

Weigh an in-app camera against the system camera UI for a capture flow, considering control, permissions, app size and maintenance.

## The component and its defaults `expo-camera` exports **`CameraView`**, a native view that shows the live camera preview and exposes capture methods through a **ref**. The props that matter for a receipt scanner: | prop | default | purpose | |---|---|---| | `facing` | `'back'` | which camera, `'front'` or `'back'` | | `flash` | `'off'` | `'on'`, `'off'` or `'auto'` for the still photo | | `enableTorch` | `false` | keeps the light on, useful for dim receipts | | `mode` | `'picture'` | `'picture'` or `'video'` | | `zoom` | `0` | 0 to 1 of the device's maximum zoom | | `active` | `true` | iOS only: stop the session without unmounting | | `onCameraReady` | none | fires when the preview has started | | `onMountError` | none | fires when the preview could not start | ## The capture sequence 1. **Permission**: read and request camera access with `useCameraPermissions` from `expo-camera`; render the preview only once it is granted. 2. **Ref**: keep a ref to the `CameraView` instance; capture methods live on it. 3. **Ready**: wait for **`onCameraReady`**. The source documents this explicitly for `takePictureAsync`: calling earlier risks a failed or empty capture. 4. **Capture**: `await ref.current.takePictureAsync(options)`. 5. **Persist**: move the result out of the cache before relying on it. ## What takePictureAsync takes and returns Options include: - **`quality`**: 0 to 1, default **1**; lower values shrink the file, which suits receipts that only need to be legible. - **`base64`** and **`exif`**: also return the JPEG as Base64 or the EXIF tags. - **`skipProcessing`**: skips orientation correction and the processing pipeline, so it is faster but can leave the photo rotated, and `quality` is ignored. - **`shutterSound`**: default `true`; set `false` to silence it. The promise resolves with **`uri`**, **`width`**, **`height`** and the optional extras. On native platforms the **`uri` is a temporary file in the app's cache directory**; the docs recommend copying it with the file-system package to keep it. Two timing details from the source: - Calling `takePictureAsync` **while the preview is paused** throws on Android and returns the last on-screen frame on iOS. - Passing `onPictureSaved` makes the promise resolve immediately, with the picture delivered to that callback once saved. ## One preview at a time The docs warn that **only one camera preview can be active at any given time**. In a tabbed or stacked app, a camera screen that stays mounted in the background can leave the next camera screen black. The fix is to render `CameraView` only while its screen is focused. On iOS, the `active` prop can additionally stop the session while the view stays mounted. ## Reading a code printed on the receipt Many receipts carry a QR code. `CameraView` can read it during the preview: - pass **`barcodeScannerSettings={{ barcodeTypes: ['qr'] }}`** to limit the types scanned; - handle **`onBarcodeScanned`**, which receives the code's `type` and `data`; - guard against repeats: the component drops an identical result only if it arrives within 500 ms of the previous one, so the same code keeps firing while it stays in frame. Set a flag, or stop passing `onBarcodeScanned`, after the first read. Scanning relies on the barcode support that the `barcodeScannerEnabled` plugin option controls. ## Native configuration `expo-camera` has a config plugin whose options become part of the native build: - **`recordAudioAndroid`**, default `true`, adds Android's audio recording permission for video; a photo-only receipt scanner can set it to `false`. - **`barcodeScannerEnabled`**, default `true`, includes barcode scanning support; setting it to `false` reduces app size when the app never reads codes. - The iOS camera and microphone prompt texts are set through the same plugin. Changing any of them requires a new native build. ## Common mistakes - Calling `takePictureAsync` from a `useEffect` on mount, before `onCameraReady`. - Storing the returned `uri` in the database as if it were permanent. - Leaving a camera screen mounted under a navigator while another camera screen opens.

  • Why can a second camera screen show a black preview in an Expo app with tabs?
    Only one camera preview can be active at a time. If the first screen's `CameraView` stays mounted in an inactive tab, it can hold the camera. Render the `CameraView` only while its screen is focused, and on iOS the `active` prop can stop the session without unmounting.
  • When would you set skipProcessing: true in takePictureAsync?
    When capture latency matters more than orientation, for example for a quick throwaway frame. It skips the processing pipeline, so the photo can come back rotated on some devices and `quality` is ignored. For receipts that must be read later, keep processing on.

saying these in an interview costs you the question

  • takePictureAsync can be called as soon as the component mounts.
  • The uri returned by takePictureAsync is a permanent file.
  • Several CameraView previews can run at once on different screens.
  • CameraView's active prop works the same on Android and iOS.
  • skipProcessing only makes capture faster with no side effects.
open as a page

In an Expo project, why should you add libraries with npx expo install rather than npm install, and which version does it choose?

level: juniorimportance: must knowfreq 60%

basics

~20 s

npx expo install looks the package up in the installed SDK's list of known-compatible versions and installs that version with your package manager. npm install takes the latest release, which may target a different React Native version.

open as a page

When would you write a native module with the Expo Modules API rather than a spec-based Turbo Module, and how do the two differ?

level: middleimportance: must knowfreq 45%

basics

~20 s

The Expo Modules API declares a module in a Swift and Kotlin DSL and validates arguments at runtime; a Turbo Module starts from a TypeScript spec that Codegen turns into native interfaces. Both run over JSI; C++ work favours Turbo Modules.

open as a page

In an Expo app, how do you use a PermissionResponse's status and canAskAgain to handle a user who permanently denied location access?

level: middleimportance: must knowfreq 55%

basics

~20 s

When status is 'denied' and canAskAgain is false, the system will not show the prompt again, so explain why location helps, offer Linking.openSettings(), and re-read the permission with the get function when the user comes back.

open as a page

What does it mean that each Expo SDK targets one React Native version, and how does that constrain which React Native version your Expo app runs?

level: middleimportance: must knowfreq 50%

basics

~20 s

An Expo SDK release is built and tested against a single React Native version, and its packages expect that version. SDK 57 targets React Native 0.86.3, so moving to a newer React Native means moving to the SDK that targets it.

open as a page

In an Expo SDK package such as expo-location, what is the difference between the get and request permission functions, and when do you call each?

level: juniorimportance: should knowfreq 45%

basics

~20 s

getForegroundPermissionsAsync only reads the current permission state and never shows a dialog; requestForegroundPermissionsAsync asks the system to prompt when it still may. Both resolve to the same PermissionResponse: check with get, request when the feature is used.

open as a page

In an Expo module's ModuleDefinition, how do Constant, Property, Function and AsyncFunction differ in what JavaScript sees and where they run?

level: middleimportance: should knowfreq 35%

basics

~20 s

Constant exposes a value computed once and cached; Property runs its getter, and optional setter, on each access; Function is synchronous and blocks JavaScript; AsyncFunction returns a Promise and runs off the JavaScript thread by default.

open as a page

In expo-image, what do the cachePolicy and placeholder props control, and how would you use them for a list of receipt thumbnails?

level: middleimportance: should knowfreq 38%

basics

~20 s

cachePolicy chooses where expo-image caches a loaded image: 'none', 'disk' (the default), 'memory' or 'memory-disk'. placeholder shows something, such as a blurhash, until the real image loads. For signed receipt URLs, set a stable cacheKey too.

open as a page

With expo-image-picker, what does launchImageLibraryAsync return, and how should a receipt-import flow handle the result?

level: middleimportance: should knowfreq 40%

basics

~20 s

launchImageLibraryAsync opens the system picker and resolves with { canceled, assets }. When canceled is false, assets holds objects with uri, width, height and file details; a cancel is a normal outcome, not an error.

open as a page

In expo-location, when do you use getCurrentPositionAsync versus watchPositionAsync, and what does each cost in time and battery?

level: middleimportance: should knowfreq 40%

basics

~20 s

getCurrentPositionAsync asks for one fresh fix, which can take seconds; watchPositionAsync streams updates to a callback until you call remove() on its subscription, and only while the app is in the foreground. getLastKnownPositionAsync is the fast, possibly stale option.

open as a page

In the Expo SDK, what does a permission hook such as useForegroundPermissions return, and what does it do when the component mounts?

level: middleimportance: should knowfreq 35%

basics

~20 s

An Expo permission hook returns [response, requestPermission, getPermission]. The response is null until the first read resolves; by default the hook only reads the status on mount, and passing { request: true } makes it request on mount instead.

open as a page

In an Expo project, how do you set the iOS usage text shown in expo-location's permission prompt, and when does a change reach users?

level: middleimportance: should knowfreq 30%

basics

~20 s

Pass expo-location's config plugin options in app config, such as locationWhenInUsePermission, which prebuild writes into Info.plist as NSLocationWhenInUseUsageDescription. It is native configuration, so the change ships only in a new build, never over the air.

open as a page

In an Expo project, what is the difference between npx expo install --check and --fix, and how would you use each in CI?

level: middleimportance: should knowfreq 38%

basics

~10 s

npx expo install --check compares installed packages with the SDK's expected versions and reports mismatches, prompting to fix when interactive and exiting non-zero in CI. --fix installs the expected versions unconditionally.

open as a page

How does an Expo module deliver native events to JavaScript with Events and sendEvent, and how do view events differ?

level: seniorimportance: should knowfreq 25%

basics

~20 s

An Expo module lists its event names with Events and emits them with sendEvent(name, payload); JavaScript subscribes through the module's addListener or the useEvent hooks. A view event instead uses an EventDispatcher property and arrives as a prop callback with nativeEvent.

open as a page

How do you expose a native view from an Expo module with View and Prop, and how does JavaScript render and call it?

level: seniorimportance: should knowfreq 20%

basics

~20 s

Declare View(MyView) inside the module definition with a Prop setter per prop, Events for callbacks and optional AsyncFunctions; the view class extends ExpoView. JavaScript gets a component from requireNativeView('ModuleName') and calls view functions through a ref.

open as a page

In an Expo app, why can expo-location report granted: true while a nearby-cafes screen still receives only approximate positions?

level: seniorimportance: should knowfreq 22%

basics

~20 s

expo-location's granted flag means some foreground location access, not precise access. The user can allow only approximate location, which the response reports as ios.accuracy 'reduced' or android.accuracy 'coarse', so read those fields and widen the search instead.

open as a page

How would you upgrade an Expo pet-adoption app from SDK 54 to SDK 57, and why go one SDK at a time instead of jumping straight there?

level: seniorimportance: should knowfreq 45%

basics

~20 s

Upgrade in three steps, 54 to 55, 55 to 56 and 56 to 57: bump expo, run npx expo install --fix, regenerate native folders, follow that SDK's release notes and test. Each step then breaks in a small, attributable way.

open as a page

In an Expo app, what does npx create-expo-module --local set up, and what does expo-module.config.json tell the build?

level: juniorimportance: nice to knowfreq 20%

basics

~20 s

npx create-expo-module --local scaffolds a module under the app's modules directory with Swift, Kotlin and TypeScript files and an expo-module.config.json, which lists the platforms and the native module classes autolinking registers. Native changes need a rebuild.

open as a page

In an Expo receipt-scanning app, why might expo-haptics feedback on capture work on Android but do nothing on iOS?

level: seniorimportance: nice to knowfreq 18%

basics

~20 s

On iOS the Taptic Engine does nothing while the camera is active, in Low Power Mode, during dictation, or when the user disabled it, and the call still resolves. Android simulates impacts with the Vibrator service, which has no such documented rule.

open as a page