呼叫手機硬體:相機、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,讓我們能優雅地處理這件事:

  1. 檢查目前權限狀態。
  2. 如果尚未詢問,跳出系統對話框詢問使用者。
  3. 如果使用者拒絕,我們必須顯示一段文字告訴他為什麼我們需要這個權限,並引導他去系統設定裡開啟。

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

運作原理:

  1. App 第一次啟動時,向系統要求推播權限。
  2. 取得一組專屬的 ExpoPushToken(這就像是這支手機的地址)。
  3. 將這個 Token 存回你的後端資料庫(例如 Supabase)。
  4. 當你想發送推播時,你的後端只要對 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)



手機硬體功能:App 與 Web 的差異

App 最大的優勢是可以直接呼叫手機硬體——相機、GPS、推播通知。Expo 提供了跨平台的 API 來使用這些功能。

常用硬體 API

| 功能 | Expo 套件 | 用途 | |:----|:---------|:----| | 相機 | expo-camera | 掃描 QR Code、拍照上傳 | | GPS | expo-location | 打卡定位、地圖導航 | | 推播 | expo-notifications | 訂單通知、行銷推播 | | 指紋/Face ID | expo-local-authentication | 生物辨識登入 |

下一章預告:狀態管理

手機硬體功能讓 App 更強大,但 App 的狀態管理也更複雜——頁面切換時資料不能丟失。下一章教你用 Zustand 或 Redux 管理 App 的全域狀態。

解鎖完整教學內容

本章為付費內容。加入專案即可解鎖超過 5000 字的深度解析,包含 10 個以上神級 Prompt 與真實 Source Code 範例!