呼叫手機硬體:相機、GPS 與推播
如果你的服務只需要顯示文字和按鈕,那做一個 RWD 手機版網頁就夠了。 我們之所以要大費周章開發「原生 App」,為的就是要存取手機硬體:相機、GPS 定位、推播通知、生物辨識 (Face ID / 指紋) 等等。
在過去的 React Native 開發中,要串接這些硬體功能,你必須去修改底層的 iOS Info.plist 和 Android 的 AndroidManifest.xml,非常痛苦且容易出錯。
好消息是,Expo SDK 幫我們把這些髒活全包了!
在這堂課中,我們將學習三個最常用的硬體功能。
1. 權限請求 (Permissions) 的黃金法則
在存取任何硬體之前,我們都必須遵守蘋果和 Google 的規定:必須先取得使用者的同意授權。
如果在未授權的情況下直接呼叫硬體 API,你的 App 會直接閃退 (Crash)。
Expo 大部分的硬體模組都提供了一個類似 usePermissions() 的 Hook,讓我們能優雅地處理這件事:
- 檢查目前權限狀態。
- 如果尚未詢問,跳出系統對話框詢問使用者。
- 如果使用者拒絕,我們必須顯示一段文字告訴他為什麼我們需要這個權限,並引導他去系統設定裡開啟。
2. 實戰一:啟動相機與挑選照片 (Image Picker)
讓使用者拍照或從相簿挑選照片來上傳大頭貼。
安裝套件:
npx expo install expo-image-picker
程式碼實作:
import { useState } from 'react';
import { Button, Image, View, Alert } from 'react-native';
import * as ImagePicker from 'expo-image-picker';
export default function CameraScreen() {
const [image, setImage] = useState<string | null>(null);
// 1. 請求相機權限的函數
const requestPermissionAndPick = async () => {
const { status } = await ImagePicker.requestCameraPermissionsAsync();
if (status !== 'granted') {
Alert.alert('需要權限', '我們需要相機權限才能讓你拍照喔!');
return;
}
// 2. 啟動相機
let result = await ImagePicker.launchCameraAsync({
mediaTypes: ImagePicker.MediaTypeOptions.Images, // 限制只能拍照,不能錄影
allowsEditing: true, // 允許使用者拍完後裁切
aspect: [4, 3], // 裁切比例
quality: 0.8, // 壓縮圖片品質 (0-1) 以節省頻寬
});
if (!result.canceled) {
// 3. 將拍好的照片 URI 存入 State 並顯示
setImage(result.assets[0].uri);
}
};
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Button title="打開相機拍照" onPress={requestPermissionAndPick} />
{image && (
<Image source={{ uri: image }} style={{ width: 300, height: 300, marginTop: 20, borderRadius: 10 }} />
)}
</View>
);
}
提示:如果你想讓使用者從相簿挑照片,只要把 launchCameraAsync 換成 launchImageLibraryAsync 即可!
3. 實戰二:取得 GPS 定位資訊 (Location)
製作像 Uber 或 Foodpanda 那樣的地圖功能,第一步就是要知道使用者現在在哪裡。
安裝套件:
npx expo install expo-location
程式碼實作:
import { useState, useEffect } from 'react';
import { Text, View, ActivityIndicator } from 'react-native';
import * as Location from 'expo-location';
export default function LocationScreen() {
const [location, setLocation] = useState<Location.LocationObject | null>(null);
const [errorMsg, setErrorMsg] = useState<string | null>(null);
useEffect(() => {
(async () => {
// 1. 請求定位權限 (前景使用權限)
let { status } = await Location.requestForegroundPermissionsAsync();
if (status !== 'granted') {
setErrorMsg('拒絕了定位權限,無法提供服務');
return;
}
// 2. 取得當前位置座標
let currentLocation = await Location.getCurrentPositionAsync({});
setLocation(currentLocation);
})();
}, []);
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
{errorMsg ? (
<Text style={{ color: 'red' }}>{errorMsg}</Text>
) : location ? (
<>
<Text>你的緯度:{location.coords.latitude}</Text>
<Text>你的經度:{location.coords.longitude}</Text>
</>
) : (
<ActivityIndicator size="large" color="#0000ff" />
)}
</View>
);
}
4. 實戰三:推播通知 (Push Notifications)
推播通知是喚回使用者 (Retargeting) 最強大的武器。 在 Expo 中,我們不需要自己去跟 Apple APNs 或是 Google FCM 奮鬥,我們可以使用 Expo Push Notifications 服務,它幫我們統整了雙平台的發送邏輯!
安裝套件:
npx expo install expo-notifications expo-device
運作原理:
- App 第一次啟動時,向系統要求推播權限。
- 取得一組專屬的
ExpoPushToken(這就像是這支手機的地址)。 - 將這個 Token 存回你的後端資料庫(例如 Supabase)。
- 當你想發送推播時,你的後端只要對 Expo 的 API 伺服器發送一個 HTTP 請求,帶上這個 Token,Expo 就會幫你把通知送到使用者的手機上!
import * as Notifications from 'expo-notifications';
// 設定通知在 App 處於前景時的行為
Notifications.setNotificationHandler({
handleNotification: async () => ({
shouldShowAlert: true, // 會在畫面上方彈出通知
shouldPlaySound: true, // 會發出叮咚聲
shouldSetBadge: false,
}),
});
// 觸發一個本地端推播 (不經過網路,適合用作鬧鐘或提醒)
async function scheduleLocalNotification() {
await Notifications.scheduleNotificationAsync({
content: {
title: "你有一封新訊息!📬",
body: '這是一則由 Vibe Tutor 教學程式觸發的測試推播。',
data: { url: '/chat/123' }, // 可以夾帶隱藏資料,點擊通知後可根據此資料跳轉頁面
},
trigger: { seconds: 5 }, // 5 秒後觸發
});
}
[!CAUTION] 推播的陷阱 iOS 模擬器 (Simulator) 不支援接收真正的遠端推播!你必須將 App 編譯並安裝到「實體 iPhone 手機」上才能測試完整的推播功能。
現在你的 App 已經有了眼睛 (相機) 和方向感 (GPS)。在下一章,我們將學習如何管理 App 內部複雜的資料流與 API 串接——狀態管理 (State Management)。