API Integration di Mobile — Mobile

API Integration di React Native Mengambil data dari API di React Native sangat mirip dengan web. fetch tersedia built-in, dan library populer seperti Axios…

Integrasi API berarti aplikasi mengambil data dari server lewat HTTP, bukan dari data contoh di dalam aplikasi. Mulai pelajaran ini, daftar pesanan Laundry Bu Rini datang dari server Laravel di papan order Bu Rini, dan server cuma mengirim order milik akun yang login. Bayangin Mbak Wulan menelepon toko: "Cucian saya sudah selesai?" Dia harus tahu nomor yang benar, menyebut namanya supaya kasir tahu ordernya yang mana, dan siap kalau teleponnya gak diangkat, berdering terlalu lama, atau ternyata belum ada order. Request ke API sama persis: alamat, identitas (token), dan jawaban yang bisa lambat, gagal, atau kosong.

🧪 Tebakan cepat: Server Laravel jalan di laptop dengan php artisan serve, alamatnya http://localhost:8000. Aplikasi di emulator Android memanggil fetch('http://localhost:8000/api/me/orders'). Apa yang terjadi? Jawabannya ada di bagian "Alamat server per lingkungan".

Kontrak API pesanan

Endpoint ini ditambahkan ke server papan order (Laravel, routes/api.php, di belakang middleware auth:sanctum):

Request Jawaban
GET /api/me/orders 200 {"data":[{"id":"13","nama":"Mbak Wulan","kg":2,"status":"Disetrika"}]}: cuma order aktif milik akun yang login
GET /api/orders/13 200 {"data":{"id":"13", ...}}. Order milik pelanggan lain: 404
tanpa token yang sah 401 {"message":"Unauthenticated."}

Alamat server per lingkungan

Aplikasi jalan di Alamat laptop Kenapa
Emulator Android http://10.0.2.2:8000 10.0.2.2 adalah alias ke 127.0.0.1 milik laptop. localhost di emulator = emulator itu sendiri
Simulator iOS http://localhost:8000 simulator memakai jaringan Mac yang sama
HP asli, Wi-Fi sama http://192.168.100.96:8000 IP laptop di Wi-Fi. Jalankan php artisan serve --host=0.0.0.0, karena bawaannya 127.0.0.1 cuma menerima dari laptop sendiri
Aplikasi rilis https://api.laundryburini.id wajib HTTPS, lihat bagian terakhir

Jadi dari emulator Android, localhost:8000 menunjuk ke emulator itu sendiri, gak ada server di sana, dan request-nya gagal tersambung. Itu jawaban tebakannya. Pakai 10.0.2.2. Alamatnya ditaruh di .env.local di root project:

# .env.local (gak masuk git)
EXPO_PUBLIC_API_URL=http://10.0.2.2:8000

Variabel berawalan EXPO_PUBLIC_ dibaca lewat process.env.EXPO_PUBLIC_API_URL dan nilainya ditanam ke dalam bundle JavaScript. Jadi jangan pernah menaruh rahasia di sana.

Mengambil pesanan: fetch dan TanStack Query

// src/lib/api.ts
import * as SecureStore from 'expo-secure-store';
import type { Order } from '@/data/orders';

const API_URL = process.env.EXPO_PUBLIC_API_URL ?? 'http://localhost:8000';

export async function ambilPesananSaya(): Promise<Order[]> {
  const token = await SecureStore.getItemAsync('token'); // disimpan saat login
  const res = await fetch(`${API_URL}/api/me/orders`, {
    headers: { Accept: 'application/json', Authorization: `Bearer ${token}` },
    signal: AbortSignal.timeout(10_000), // gak dijawab dalam 10 detik: batalkan
  });
  if (!res.ok) throw new Error(`Server menjawab ${res.status}`);
  const body: { data: Order[] } = await res.json();
  return body.data;
}
// src/app/(tabs)/index.tsx
import { useQuery } from '@tanstack/react-query';
import { ActivityIndicator, Button, FlatList, Text, View } from 'react-native';
import { ambilPesananSaya } from '@/lib/api';

export default function Pesanan() {
  const { data, isPending, isError, error, refetch, isRefetching } = useQuery({
    queryKey: ['orders', 'saya'],
    queryFn: ambilPesananSaya,
  });

  if (isPending) return <ActivityIndicator style={{ flex: 1 }} />;

  if (isError) {
    return (
      <View style={{ flex: 1, justifyContent: 'center', padding: 16, gap: 12 }}>
        <Text>Gagal memuat pesanan: {error.message}</Text>
        <Button title="Coba lagi" onPress={() => refetch()} />
      </View>
    );
  }

  return (
    <FlatList
      data={data}
      keyExtractor={(o) => o.id}
      renderItem={({ item }) => <Text>#{item.id} {item.nama}: {item.status}</Text>}
      refreshing={isRefetching}
      onRefresh={refetch}
      ListEmptyComponent={<Text>Belum ada order aktif.</Text>}
    />
  );
}

Empat keadaan layar: loading (isPending), error (dengan tombol coba lagi), kosong (ListEmptyComponent), dan berisi. Saat request gagal, TanStack Query mencoba lagi 3 kali dengan jeda 1, 2, lalu 4 detik. Kalau server selalu menjawab 500:

Detik ke Yang terjadi
0 percobaan 1: 500
1 percobaan 2: 500
3 percobaan 3: 500
7 percobaan 4: 500, isError jadi true, tampil "Gagal memuat pesanan: Server menjawab 500"

Tarik untuk refresh, dan status yang berubah

refreshing dan onRefresh di FlatList membuat RefreshControl bawaan: tarik daftar ke bawah, spinner muncul, refetch dipanggil. Pakai isRefetching, bukan isLoading: isLoading cuma true di muatan pertama, jadi spinner tarik-untuk-refresh gak akan pernah tampil.

GET /api/me/orders  200  {"data":[{"id":"13","nama":"Mbak Wulan","kg":2,"status":"Disetrika"}]}
  (Bu Rini memindahkan kartu #13 di papan)
  (Mbak Wulan menarik daftar ke bawah)
GET /api/me/orders  200  {"data":[{"id":"13","nama":"Mbak Wulan","kg":2,"status":"Siap diambil"}]}

Status #13 berubah dari "Disetrika" ke "Siap diambil" tanpa menutup aplikasi. Cara lain: muat ulang saat aplikasi kembali ke depan (focusManager di pelajaran state), refetchInterval: 60_000 untuk tiap menit, atau kabar langsung dari server lewat realtime (track Realtime) dan push notification (pelajaran berikutnya).

HTTP polos diblokir di build rilis

⚠️ Jebakan yang sering ketemu

TL;DR: Aplikasi mengambil pesanan Mbak Wulan dari GET /api/me/orders (cuma order akun yang login, {"data":[...]}), dengan Accept: application/json dan Authorization: Bearer <token> dari expo-secure-store. Alamat server per lingkungan lewat EXPO_PUBLIC_API_URL: 10.0.2.2:8000 dari emulator Android (localhost di emulator adalah emulator itu sendiri), localhost dari simulator iOS, IP laptop dari HP asli dengan php artisan serve --host=0.0.0.0. Cara yang direkomendasikan: fetch bawaan (atau axios) plus TanStack Query untuk cache dan retry (3 kali, jeda 1, 2, 4 detik). Periksa res.ok, pasang batas waktu dengan AbortSignal.timeout, tampilkan keadaan loading, error, dan kosong, dan pakai refreshing={isRefetching} dengan onRefresh={refetch} untuk tarik-untuk-refresh, yang mengubah #13 dari "Disetrika" ke "Siap diambil". HTTP polos cuma jalan di development: build rilis Android (sejak Android 9) dan iOS (ATS) memblokirnya, jadi server sungguhan wajib HTTPS.

Yang akan kamu pelajari