Push Notifications — Mobile

Push Notifications di Mobile Push notification adalah cara utama re-engage user di mobile app. Ada dua layer: local notification (diatur oleh app sendiri) dan…

Push notification adalah pesan dari server yang muncul di HP walaupun aplikasinya sedang ditutup. Di Laundry Bu Rini, begitu Bu Rini memindahkan kartu #13 ke "Siap diambil", HP Mbak Wulan berbunyi: "Pesanan #13 siap diambil". Bayangin Bu Rini mau menitip kabar ke rumah Mbak Wulan di sebuah kompleks. Dia gak bisa masuk sendiri. Dia titip ke satpam kompleks, dan satpam cuma mau mengantar kalau tahu nomor rumahnya dan penghuninya sudah bilang "boleh antar pesan ke saya". Satpam itu layanan push milik Google dan Apple, nomor rumah itu token, dan "boleh" itu izin notifikasi.

🧪 Tebakan cepat: Kode push sudah benar. Mbak Wulan membuka aplikasi lewat Expo Go di HP Android, lalu getExpoPushTokenAsync() dipanggil. Apa yang terjadi? Jawabannya ada di bagian "Expo Go, development build, dan simulator".

Jalur satu push dari papan order ke HP

Langkah Siapa Yang terjadi
1 Aplikasi minta izin notifikasi ke pengguna
2 Aplikasi minta token: alamat HP ini untuk push
3 Aplikasi ke server Laravel kirim token, server menyimpannya per perangkat
4 Server ke Expo Push Service "kirim 'Pesanan #13 siap diambil' ke token ini"
5 Expo ke FCM / APNs Android lewat FCM (Firebase Cloud Messaging), iOS lewat APNs (Apple Push Notification service)
6 HP notifikasi tampil; diketuk, aplikasi membuka Order #13

Jadi sebelum bisa mengirim apa pun, aplikasi harus mendapat izin dan token dulu. Tanpa token, server gak tahu HP mana yang dituju.

Izin dan token di aplikasi

npx expo install expo-notifications expo-constants   # SDK 57: expo-notifications 57.0.22

Tambahkan "expo-notifications" ke plugins di app.json. Plugin-nya menambah hak aps-environment di iOS, dan library-nya membawa izin POST_NOTIFICATIONS untuk Android.

// src/lib/push.ts
import Constants from 'expo-constants';
import * as Notifications from 'expo-notifications';
import { Platform } from 'react-native';

export async function daftarkanPush(): Promise<string | null> {
  if (Platform.OS === 'android') {
    // Android 13+: dialog izin baru muncul setelah ada minimal satu channel
    await Notifications.setNotificationChannelAsync('status-order', {
      name: 'Status order',
      importance: Notifications.AndroidImportance.HIGH,
    });
  }

  let izin = await Notifications.getPermissionsAsync();
  if (!izin.granted && izin.canAskAgain) {
    izin = await Notifications.requestPermissionsAsync();
  }
  if (!izin.granted) return null; // ditolak: aplikasi tetap jalan, cuma tanpa push

  const projectId = Constants.expoConfig?.extra?.eas?.projectId ?? Constants.easConfig?.projectId;
  const { data } = await Notifications.getExpoPushTokenAsync({ projectId });
  return data; // "ExponentPushToken[...]"
}

Server menyimpan token dan mengirim push

Aplikasi mengirim token ke POST /api/me/push-tokens dengan {"token":"ExponentPushToken[...]","platform":"android"} dan token login Bearer. Server menyimpannya satu baris per perangkat: kolom token unik, plus user_id dan platform. Mbak Wulan punya HP dan tablet: dua baris. HP dipakai akun lain: baris yang sama pindah user_id.

Saat #13 jadi "Siap diambil", server mengirim ke https://exp.host/--/api/v2/push/send (sampai 100 pesan per request):

// di server papan order (Laravel)
$res = Http::acceptJson()->post('https://exp.host/--/api/v2/push/send', [[
    'to' => 'ExponentPushToken[xxxxxxxxxxxxxxxxxxxxxx]',
    'title' => 'Laundry Bu Rini',
    'body' => 'Pesanan #13 siap diambil',
    'data' => ['url' => '/order/13'],
    'channelId' => 'status-order',
    'sound' => 'default',
]]);
{ "data": [{ "status": "ok", "id": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX" }] }

Mengetuk notifikasi membuka Order #13

data.url berisi /order/13, alamat yang sama dengan layar src/app/order/[id].tsx. Di layout akar:

// src/app/_layout.tsx (bagian push)
Notifications.setNotificationHandler({
  handleNotification: async () => ({
    shouldShowBanner: true, shouldShowList: true, shouldPlaySound: true, shouldSetBadge: false,
  }),
});

function bukaDariNotifikasi(n: Notifications.Notification) {
  const url = n.request.content.data?.url;
  if (typeof url === 'string' && url.startsWith('/order/')) router.push(url); // cuma alamat yang dikenal
}

// di dalam RootLayout
useEffect(() => {
  const terakhir = Notifications.getLastNotificationResponse(); // aplikasi dibuka DARI notifikasi
  if (terakhir) bukaDariNotifikasi(terakhir.notification);
  const sub = Notifications.addNotificationResponseReceivedListener((r) => bukaDariNotifikasi(r.notification));
  return () => sub.remove();
}, []);

Expo Go, development build, dan simulator

Sejak SDK 53, push di Expo Go untuk Android dihapus. getExpoPushTokenAsync() di Expo Go Android melempar error: expo-notifications: Android Push notifications (remote notifications) functionality provided by expo-notifications was removed from Expo Go with the release of SDK 53. Use a development build instead of Expo Go. Itu jawaban tebakannya. Di iOS, Expo Go cuma memberi peringatan, tapi tetap uji dengan development build supaya izin, channel, dan nama aplikasinya milikmu sendiri. Notifikasi lokal (dijadwalkan aplikasi sendiri) masih jalan di Expo Go.

Push bisa diuji di HP asli, emulator Android yang punya Google Play services, dan Simulator iOS dari Xcode 14 ke atas. Pengecekan Device.isDevice di tutorial lama sudah gak perlu.

⚠️ Jebakan yang sering ketemu

TL;DR: Push "Pesanan #13 siap diambil" butuh izin dan token dulu: Android lewat FCM, iOS lewat APNs, dan Expo Push Service membungkus keduanya dengan token ExponentPushToken[...]. Di Android buat channel dulu (status-order), karena dialog izin Android 13 baru muncul setelah ada channel; iOS bertanya sekali, atau pakai provisional tanpa dialog (ios.status PROVISIONAL). Server menyimpan token per perangkat, mengirim ke exp.host/--/api/v2/push/send (ticket "ok" berarti diterima Expo), lalu membaca receipt sekitar 15 menit kemudian dan menghapus token DeviceNotRegistered. data: { url: '/order/13' } plus getLastNotificationResponse() dan addNotificationResponseReceivedListener membuka Order #13 saat diketuk, dan setNotificationHandler wajib untuk notifikasi saat aplikasi terbuka. Push Android gak jalan di Expo Go sejak SDK 53: pakai development build.

Yang akan kamu pelajari