Expo SDK
Expo が提供するネイティブ機能のラッパ群。カメラ、GPS、通知、ファイル、認証、センサーなど、
モバイルアプリで必要になるほぼすべてが「expo-*」のパッケージとして揃っている。
共通の流れ
npx expo install expo-xxxでインストールapp.jsonのpluginsに config plugin を追加(必要な場合)- パーミッション文言を設定(写真、カメラ、位置、通知など)
- JS から API を呼ぶ。多くは
requestPermissionsAsync()から始める
プロジェクトに新しいネイティブモジュールを追加した場合、Expo Go では動かないので Dev Build を作り直す必要がある。
パーミッション
ほぼ全ての機能で OS レベルの許可が必要。パーミッション文言("なぜ必要か")は
app.json の各プラグイン options に書く。書かないとストア審査で落ちる。
{
"expo": {
"plugins": [
"expo-router",
["expo-camera", { "cameraPermission": "プロフィール画像を撮影します" }],
["expo-location", { "locationAlwaysAndWhenInUsePermission": "近くのスポットを表示します" }],
["expo-image-picker", { "photosPermission": "写真をアップロードします" }]
]
}
}
カメラ・写真ライブラリ
expo-camera
ライブカメラビュー。QR コードスキャン、写真撮影、動画撮影。
import { CameraView, useCameraPermissions } from "expo-camera"
function Scanner() {
const [permission, requestPermission] = useCameraPermissions()
if (!permission) return null
if (!permission.granted) return (
<Pressable onPress={requestPermission}><Text>許可</Text></Pressable>
)
return (
<CameraView
style={{ flex: 1 }}
facing="back"
onBarcodeScanned={({ data }) => console.log("scanned:", data)}
barcodeScannerSettings={{ barcodeTypes: ["qr"] }}
/>
)
}
expo-image-picker
ユーザーに写真ライブラリから選択させるか、カメラを起動して撮影させる。
import * as ImagePicker from "expo-image-picker"
async function pick() {
const result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ["images"],
allowsEditing: true,
aspect: [1, 1],
quality: 0.8,
})
if (!result.canceled) {
const uri = result.assets[0].uri
upload(uri)
}
}
expo-media-library
撮影した画像をカメラロールに保存したり、ライブラリ全体にアクセスする時。
位置情報
expo-location。GPS、ヘディング、ジオコーディング、バックグラウンド位置情報まで。
import * as Location from "expo-location"
async function getLocation() {
const { status } = await Location.requestForegroundPermissionsAsync()
if (status !== "granted") return
const loc = await Location.getCurrentPositionAsync({
accuracy: Location.Accuracy.Balanced,
})
console.log(loc.coords.latitude, loc.coords.longitude)
}
// 連続的な追跡
const sub = await Location.watchPositionAsync(
{ accuracy: Location.Accuracy.High, distanceInterval: 10 },
(location) => { /* 10m ごと */ },
)
sub.remove() // 終わったら必ず
バックグラウンド位置情報はストア審査が厳しい。本当に必要かどうか吟味する。
通知(Push / Local)
expo-notifications。ローカル通知(時刻指定)とプッシュ通知(リモート)の両方。
ローカル通知
import * as Notifications from "expo-notifications"
async function scheduleAlarm() {
await Notifications.requestPermissionsAsync()
await Notifications.scheduleNotificationAsync({
content: { title: "起きる時間", body: "おはよう" },
trigger: { seconds: 60 * 60 * 8 }, // 8 時間後
})
}
プッシュ通知(Expo Push)
APNs / FCM のラッパとしてExpo の Push サービスが使える。トークンを取ってサーバに送り、サーバから Expo のエンドポイントに POST するだけ。
const token = (await Notifications.getExpoPushTokenAsync()).data
// → サーバへ保存
// サーバ側
fetch("https://exp.host/--/api/v2/push/send", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
to: token,
title: "新しいメッセージ",
body: "..."
}),
})
独自に APNs / FCM を直接叩きたい場合は別のセットアップ(証明書、サービスアカウント)が必要。
FileSystem
アプリ専用ディレクトリの読み書き、ダウンロード、アップロード。
import * as FileSystem from "expo-file-system"
const path = FileSystem.documentDirectory + "data.json"
// 書き込み
await FileSystem.writeAsStringAsync(path, JSON.stringify({ ok: true }))
// 読み込み
const text = await FileSystem.readAsStringAsync(path)
// ダウンロード
const result = await FileSystem.downloadAsync(
"https://example.com/big.zip",
FileSystem.documentDirectory + "big.zip",
)
console.log(result.uri)
ディレクトリの種類:
documentDirectory— 永続的なユーザーデータ(バックアップ対象)cacheDirectory— キャッシュ。OS が消す可能性あり
センサー
加速度・ジャイロ・地磁気・歩数計など。expo-sensors。
import { Accelerometer } from "expo-sensors"
const sub = Accelerometer.addListener(({ x, y, z }) => {
console.log(x, y, z)
})
Accelerometer.setUpdateInterval(100) // 100ms
// 終わったら
sub.remove()
歩数計
import { Pedometer } from "expo-sensors"
const isAvailable = await Pedometer.isAvailableAsync()
const result = await Pedometer.getStepCountAsync(start, end)
音声・動画
expo-audio / expo-video
旧 expo-av は分割され、expo-audio と expo-video が現在の標準。
import { useAudioPlayer } from "expo-audio"
function Player() {
const player = useAudioPlayer(require("../assets/click.mp3"))
return <Pressable onPress={() => player.play()}><Text>再生</Text></Pressable>
}
import { VideoView, useVideoPlayer } from "expo-video"
function Watch() {
const player = useVideoPlayer("https://example.com/video.mp4", (p) => {
p.loop = true
p.play()
})
return <VideoView player={player} style={{ width: "100%", height: 240 }} contentFit="cover" />
}
触覚(バイブ)
import * as Haptics from "expo-haptics"
await Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium)
await Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success)
await Haptics.selectionAsync()
クリップボード
import * as Clipboard from "expo-clipboard"
await Clipboard.setStringAsync("コピーされる文字")
const text = await Clipboard.getStringAsync()
共有
import * as Sharing from "expo-sharing"
if (await Sharing.isAvailableAsync()) {
await Sharing.shareAsync(localFileUri)
}
生体認証(Face ID / Touch ID)
import * as LocalAuthentication from "expo-local-authentication"
const has = await LocalAuthentication.hasHardwareAsync()
const enrolled = await LocalAuthentication.isEnrolledAsync()
if (has && enrolled) {
const result = await LocalAuthentication.authenticateAsync({
promptMessage: "ロック解除",
})
if (result.success) { /* 通過 */ }
}
外部ブラウザ・WebView
expo-web-browser
SFSafariViewController(iOS)/ Custom Tabs(Android)でリンクを開く。 OAuth コールバックを取りたい時に使う。
import * as WebBrowser from "expo-web-browser"
const result = await WebBrowser.openAuthSessionAsync(
"https://auth.example.com/login",
"myapp://callback",
)
if (result.type === "success") {
const url = result.url
// url からトークン抽出
}
WebView をアプリ内に埋め込む
react-native-webview パッケージ(Expo SDK ではなく定番のサードパーティ)。
OS / デバイス情報
expo-device— モデル名、メモリ、エミュレータかどうかexpo-constants— アプリのバージョン、ビルド ID、エミュレータ判定expo-application— bundle ID、バージョン、初回インストール時刻expo-network— ネットワーク状態(NetInfo の代替)expo-localization— 言語・タイムゾーン・通貨
バックグラウンドタスク
expo-background-fetch— 定期的に走る(OS 依存で間隔は緩い)expo-task-manager— タスク登録expo-locationのバックグラウンド更新
モバイル OS はバックグラウンド処理に厳しい。「30 分ごとに必ず動く」は保証されない。 重要な処理はサーバ側 + プッシュ通知で実装するのが定石。
位置・地図
react-native-maps— 地図表示の定番(Apple Maps / Google Maps)@rnmapbox/maps— Mapbox- OpenStreetMap タイルを
react-native-mapsに貼ることも可能
実用例: 写真投稿フロー
import * as ImagePicker from "expo-image-picker"
import * as FileSystem from "expo-file-system"
async function uploadPhoto() {
const result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ["images"],
quality: 0.7,
})
if (result.canceled) return
const uri = result.assets[0].uri
const base64 = await FileSystem.readAsStringAsync(uri, {
encoding: FileSystem.EncodingType.Base64,
})
await fetch("/api/photos", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ image: base64 }),
})
}
公式の Expo SDK Reference に全パッケージが網羅されている。
機能名で検索すれば大抵見つかる。「自前でネイティブモジュールを書く前にここをチェック」が鉄則。