Kamera, GPS, dan sensor adalah perangkat keras HP yang bisa dipakai aplikasi, asal pemiliknya mengizinkan. Laundry Bu Rini memakai dua: kamera untuk scan QR di nota cucian, dan lokasi untuk alamat jemput. Bayangin kurir Bu Rini yang mau menjemput cucian. Dia gak boleh masuk rumah tanpa izin, dia harus bilang untuk apa ("saya mau ambil cucian"), dan Mbak Wulan boleh memberi alamat persis atau cuma "dekat masjid". Aplikasi juga begitu: minta izin, tulis alasannya, dan terima kalau pengguna cuma memberi lokasi perkiraan.
🧪 Tebakan cepat: Tombol "Pakai lokasi saat ini" memanggil
Location.getCurrentPositionAsync()tanpa opsi apa pun. Titik yang didapat kurir akurat sampai berapa meter? Jawabannya ada di bagian "Lokasi untuk alamat jemput".
Izin dulu, dengan alasan yang jelas
npx expo install expo-camera expo-location # SDK 57: 57.0.6 dan 57.0.20
{
"expo": {
"plugins": [
["expo-camera", { "cameraPermission": "Izinkan Laundry Bu Rini memotret nota cucianmu.", "microphonePermission": false, "recordAudioAndroid": false }],
["expo-location", { "locationWhenInUsePermission": "Izinkan Laundry Bu Rini memakai lokasimu untuk alamat jemput." }]
]
}
}
Setelah npx expo prebuild, Info.plist berisi NSCameraUsageDescription dan NSLocationWhenInUseUsageDescription dengan dua kalimat itu, dan AndroidManifest.xml berisi CAMERA, ACCESS_COARSE_LOCATION, serta ACCESS_FINE_LOCATION. Kalimat alasan itu yang tampil di dialog iOS. Tanpa opsi itu, plugin menulis teks bawaan bahasa Inggris, misalnya Allow $(PRODUCT_NAME) to access your camera.
- Aturan dialognya sama dengan pelajaran platform: iOS cuma bertanya sekali, Android dua kali (sejak Android 11). Setelah itu
canAskAgainjadifalse, dan satu-satunya jalan adalahLinking.openSettings(). - Minta izin saat tombolnya diketuk, bukan saat aplikasi dibuka.
- Prebuild juga menulis dua kunci izin lokasi "Always" dengan teks bawaan bahasa Inggris. Teks itu cuma tampil kalau aplikasi meminta lokasi background, dan aplikasi ini gak memintanya.
Scan QR nota dengan expo-camera
Setiap nota punya QR berisi kode order, misalnya LBR-13.
// src/components/ScanNota.tsx
import { CameraView, useCameraPermissions, type BarcodeScanningResult } from 'expo-camera';
import { router } from 'expo-router';
import { useRef } from 'react';
import { Button, Linking } from 'react-native';
const POLA_NOTA = /^LBR-(\d{1,6})$/; // isi QR di nota: LBR-13
export default function ScanNota() {
const [izin, mintaIzin] = useCameraPermissions();
const sudahScan = useRef(false);
if (!izin) return null; // status izin belum terbaca
if (!izin.granted) {
return izin.canAskAgain
? <Button title="Izinkan kamera untuk scan nota" onPress={mintaIzin} />
: <Button title="Buka Pengaturan" onPress={() => Linking.openSettings()} />;
}
function onScan({ data }: BarcodeScanningResult) {
if (sudahScan.current) return; // terpanggil terus selama QR terlihat
const cocok = POLA_NOTA.exec(data);
if (!cocok) return; // QR lain (promo, link asing): abaikan
sudahScan.current = true;
router.push(`/order/${cocok[1]}`); // server tetap memeriksa: order orang lain = 404
}
return <CameraView style={{ flex: 1 }} facing="back" barcodeScannerSettings={{ barcodeTypes: ['qr'] }} onBarcodeScanned={onScan} />;
}
CameraViewmenampilkan kamera.barcodeScannerSettingsmembatasi ke QR, danonBarcodeScannedmenerima{ type: 'qr', data: 'LBR-13', ... }.- Callback-nya dipanggil berkali-kali selama QR ada di depan kamera. Tanpa
sudahScan, layar Order #13 bisa terbuka beberapa kali. - Isi QR datang dari kertas, jadi periksa bentuknya.
LBR-12lolos pola, tapi itu order Pak Darto:GET /api/orders/12menjawab404, dan layar menampilkan "Order gak ketemu". - Cuma satu preview kamera yang boleh aktif. Lepas
CameraViewsaat layarnya ditinggal. - Library lain:
react-native-vision-camera(5.2.3 di npm) untuk kebutuhan berat seperti memproses tiap frame; butuh development build. Untuk sekadar memotret atau memilih foto,expo-image-pickermembuka kamera atau galeri bawaan.
Lokasi untuk alamat jemput
// src/lib/lokasi.ts
import * as Location from 'expo-location';
export async function lokasiJemput() {
const izin = await Location.requestForegroundPermissionsAsync();
if (!izin.granted) return null; // tetap bisa ketik alamat manual
const posisi = await Location.getCurrentPositionAsync({ accuracy: Location.Accuracy.High }); // sekitar 10 m
const { latitude, longitude, accuracy } = posisi.coords; // accuracy: radius ketidakpastian, dalam meter
const [alamat] = await Location.reverseGeocodeAsync({ latitude, longitude });
return { latitude, longitude, akurasiMeter: accuracy, alamat };
}
Location.Accuracy |
Nilai | Akurat sampai |
|---|---|---|
Lowest |
1 | 3 km |
Low |
2 | 1 km |
Balanced |
3 | 100 m (bawaan) |
High |
4 | 10 m |
Highest |
5 | yang terbaik yang tersedia |
BestForNavigation |
6 | tertinggi, plus data sensor, untuk navigasi |
Tanpa opsi, getCurrentPositionAsync() memakai Balanced: akurat sampai sekitar 100 meter, cukup untuk "kelurahan mana", terlalu kasar untuk pagar rumah Mbak Wulan. Itu jawaban tebakannya. Untuk alamat jemput, minta High sekali saja.
- Baterai: akurasi rendah bisa menghindari GPS, yang paling boros. Ambil posisi sekali dengan
getCurrentPositionAsync, janganwatchPositionAsyncterus-menerus. Mendapat posisi bisa makan beberapa detik;getLastKnownPositionAsync({ maxAge: 60000 })lebih cepat kalau posisi semenit lalu sudah cukup. - Lokasi perkiraan: sejak Android 12 dan iOS 14, pengguna bisa memilih "perkiraan". Hasilnya
izin.android?.accuracy === 'coarse'atauizin.ios?.accuracy === 'reduced', dan memintaHighgak mengubahnya. Tampilkan kolom alamat manual. reverseGeocodeAsyncmengubah koordinat jadi alamat. Panggil sekali per ketukan, jangan dalam loop.
Sensor: pakai kalau memang perlu
expo-sensors (57.0.3) memberi Accelerometer, Barometer, DeviceMotion, Gyroscope, LightSensor, Magnetometer, dan Pedometer. Aplikasi laundry ini gak butuh satu pun, jadi gak dipasang. Kalau suatu fitur butuh (misalnya goyang HP untuk lapor masalah), lepas listener-nya dengan sub.remove() saat layarnya ditinggal, isi motionPermission untuk iOS, dan ingat Android 12 ke atas membatasi pembacaan di 200 Hz.
Simulator dan emulator
| Simulator iOS | Emulator Android | HP asli | |
|---|---|---|---|
| Kamera dan scan QR | gak ada kamera | dokumentasi expo-camera menandainya "device only" | ya |
| Lokasi | pilih lewat menu Device > Location | nyalakan Location di pengaturan emulator, atur titiknya dari emulator | ya, termasuk pilihan "perkiraan" |
Jadi scan nota diuji di HP asli. Lokasi bisa diuji di simulator, dengan titik buatan.
⚠️ Jebakan yang sering ketemu
- Membuka layar order di setiap panggilan
onBarcodeScanned. Kunci denganuseRefsetelah scan pertama yang cocok. - Percaya isi QR. Periksa bentuknya, dan biarkan server memutuskan order itu milik siapa.
getCurrentPositionAsync()tanpaaccuracy. BawaannyaBalanced, sekitar 100 m.- Lupa kalimat alasan izin. Tanpa
cameraPermissiondanlocationWhenInUsePermission, iOS menampilkan teks bawaan bahasa Inggris. watchPositionAsyncdibiarkan jalan. GPS terus menyala dan baterai cepat habis. Ambil sekali, atauremove()langganannya.
TL;DR: Kamera dan lokasi butuh izin plus kalimat alasan dari config plugin (cameraPermission, locationWhenInUsePermission), yang ditulis ke Info.plist dan AndroidManifest.xml saat prebuild. iOS bertanya sekali, Android dua kali, setelah itu Linking.openSettings(). Library kamera yang umum: expo-camera atau react-native-vision-camera. CameraView dengan barcodeTypes: ['qr'] membaca LBR-13 dari nota; kunci setelah scan pertama, periksa polanya, dan biarkan server menjawab 404 untuk order orang lain. getCurrentPositionAsync() bawaannya Balanced (sekitar 100 m); untuk alamat jemput minta Accuracy.High (sekitar 10 m) sekali saja, dan siapkan alamat manual untuk lokasi perkiraan. Sensor cuma dipasang kalau fiturnya butuh. Scan diuji di HP asli; lokasi bisa diuji di simulator.