Push bildirishnomalar — expo-notifications

~20 daq · React Native · Qurilma imkoniyatlari

Muammodan boshlaylik

Buyurtma holati o'zgardi: "Kuryer yo'lga chiqdi". Foydalanuvchi buni qanday biladi? Ilova yopiq. U ilovani o'zi ochib, tekshirishi kerakmi?

Push bildirishnoma — ilova yopiq bo'lsa ham, server telefon ekraniga xabar yuboradi. Bosilganda — ilova aynan kerakli ekranda ochiladi.

Ikki tur:

  • Lokal — ilovaning o'zi rejalashtiradi ("Har kuni 20:00 da suv ichishni eslatish")
  • Remote (push) — serverdan keladi ("Buyurtmangiz yo'lda")

O'rnatish

npx expo install expo-notifications expo-device expo-constants
"plugins": [
  ["expo-notifications", { "icon": "./assets/images/notification-icon.png", "color": "#0284c7" }]
]

Android bildirishnoma ikonkasi — oq rangli, shaffof fonli PNG (tizim uni o'zi bo'yaydi).

[!WARNING] Remote push bildirishnomalar Expo Go da Android uchun ishlamaydi (SDK 53 dan beri). Sinash uchun development build kerak (48-dars). Lokal bildirishnomalar Expo Go da ishlaydi. Push — faqat haqiqiy qurilmada, emulyatorda emas.


Ilova ochiq bo'lganda xatti-harakat

Standart holatda ilova ochiq paytda kelgan bildirishnoma ko'rsatilmaydi. Ko'rsatish uchun — modul darajasida, bir marta:

// app/_layout.tsx (komponentdan tashqarida)
import * as Notifications from 'expo-notifications';

Notifications.setNotificationHandler({
  handleNotification: async () => ({
    shouldShowBanner: true,
    shouldShowList: true,
    shouldPlaySound: true,
    shouldSetBadge: false,
  }),
});

Lokal bildirishnoma

async function eslatmaQosh() {
  const { status } = await Notifications.requestPermissionsAsync();
  if (status !== 'granted') return;

  // 5 soniyadan keyin
  await Notifications.scheduleNotificationAsync({
    content: {
      title: '💧 Suv ichish vaqti',
      body: "Bugun hali 3 stakan qoldi",
      data: { url: '/suv' },
    },
    trigger: { type: Notifications.SchedulableTriggerInputTypes.TIME_INTERVAL, seconds: 5 },
  });

  // Har kuni 20:00 da
  await Notifications.scheduleNotificationAsync({
    content: { title: 'Kunlik hisobot', body: 'Bugungi natijalaringizni ko\'ring' },
    trigger: { type: Notifications.SchedulableTriggerInputTypes.DAILY, hour: 20, minute: 0 },
  });
}

await Notifications.cancelAllScheduledNotificationsAsync();   // hammasini bekor qilish

Server kerak emas — eslatmalar, taymerlar, odat kuzatuvchilar uchun.


Remote push — umumiy sxema

┌──────────┐ 1. token so'rash   ┌──────────────┐
│  Ilova   │ ─────────────────→ │ Expo / FCM / │
│          │ ←───────────────── │ APNs         │
└────┬─────┘ 2. ExponentPushToken[...]  └──────┬───────┘
     │ 3. tokenni serverga yuborish             │
     ▼                                          │ 5. telefonga yetkazish
┌──────────┐ 4. "shu tokenga xabar yubor"       │
│  Server  │ ──────────────────────────────────→┘
└──────────┘

Apple (APNs) va Google (FCM) ning push tizimlari murakkab. Expo Push Service ular ustidagi yagona qatlam: siz bitta API va bitta token turi bilan ishlaysiz.

1-2. Token olish

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

export async function pushTokenOl(): Promise<string | null> {
  if (!Device.isDevice) return null;                       // emulyatorda ishlamaydi

  if (Platform.OS === 'android') {
    await Notifications.setNotificationChannelAsync('buyurtmalar', {
      name: 'Buyurtmalar holati',
      importance: Notifications.AndroidImportance.HIGH,
    });
  }

  const { status: mavjud } = await Notifications.getPermissionsAsync();
  let status = mavjud;
  if (mavjud !== 'granted') {
    status = (await Notifications.requestPermissionsAsync()).status;
  }
  if (status !== 'granted') return null;

  const projectId = Constants.expoConfig?.extra?.eas?.projectId ?? Constants.easConfig?.projectId;
  const { data } = await Notifications.getExpoPushTokenAsync({ projectId });
  return data;                                              // "ExponentPushToken[xxxxxx]"
}
  • Android kanallari — foydalanuvchi har bir turni sozlamalarda alohida o'chira oladi ("Buyurtmalar" — ha, "Aksiyalar" — yo'q). Android 8+ da majburiy
  • projectId — eas init dan keyin app.json ga yoziladi (48-dars)

3. Serverga yuborish

// Kirgandan keyin
const token = await pushTokenOl();
if (token) await apiPost('/qurilmalar', { token, platforma: Platform.OS });

Server tokenni foydalanuvchiga bog'lab saqlaydi. Bitta foydalanuvchida bir nechta qurilma bo'lishi mumkin — har biri o'z tokeni bilan. Chiqishda — tokenni serverdan o'chiring.

4. Serverdan yuborish

curl -X POST https://exp.host/--/api/v2/push/send \
  -H "Content-Type: application/json" \
  -d '{
    "to": "ExponentPushToken[xxxxxx]",
    "title": "Buyurtmangiz yo'\''lda 🚚",
    "body": "Kuryer 20-30 daqiqada yetib keladi",
    "data": { "url": "/buyurtma/1043" },
    "channelId": "buyurtmalar"
  }'

Node.js da — expo-server-sdk paketi (100 tadan to'plab yuborish, xatolarni qayta ishlash). Sinash uchun — https://expo.dev/notifications dagi vosita.

Javobdagi DeviceNotRegistered xatosi — foydalanuvchi ilovani o'chirgan. Bunday tokenni bazadan o'chiring.


Bildirishnoma bosilganda — kerakli ekranga

// app/_layout.tsx
import { router } from 'expo-router';

function useBildirishnomaNavigatsiya() {
  useEffect(() => {
    function ochish(n: Notifications.Notification) {
      const url = n.request.content.data?.url;
      if (typeof url === 'string' && url.startsWith('/')) router.push(url);
    }

    // Ilova YOPIQ bo'lganda bosilgan bildirishnoma (ilova shu bilan ochildi)
    const oxirgi = Notifications.getLastNotificationResponse();
    if (oxirgi) ochish(oxirgi.notification);

    // Ilova ochiq / fonda bo'lganda bosilgan
    const sub = Notifications.addNotificationResponseReceivedListener((r) => ochish(r.notification));
    return () => sub.remove();
  }, []);
}

data.url — Expo Router yo'li (29-dars). startsWith('/') — faqat ichki yo'llar; serverdan kelgan ma'lumotga ham ko'r-ko'rona ishonmang.

Ilova ochiq paytda kelgan bildirishnomaga darhol javob (masalan, buyurtmalar ro'yxatini yangilash):

Notifications.addNotificationReceivedListener(() => {
  queryClient.invalidateQueries({ queryKey: ['buyurtmalar'] });
});

Yaxshi amaliyot

  • Ruxsatni kontekstda so'rang (38-dars): birinchi buyurtmadan keyin "Buyurtma holatini bildirishnoma orqali kuzatasizmi?" — ochilishi bilan emas
  • Foydali bo'lsin: buyurtma holati, xabar, eslatma. Har kuni reklama — foydalanuvchi bildirishnomalarni o'chiradi yoki ilovani o'chirib tashlaydi
  • Kanallar bilan turlarni ajrating — foydalanuvchi reklamani o'chirsa ham, buyurtma xabarlari qolsin
  • Matn — mustaqil: "Yangi xabar" emas, "Malika: Buyurtma qachon keladi?"
  • Har bir bildirishnoma — aniq ekranga olib borsin

Nima uchun shunday?

Nima uchun ilova o'zi serverga doimiy ulanib, xabarlarni kutib turmaydi — push xizmati nima uchun kerak?

Batareya. Agar telefondagi har bir ilova o'z serveriga doimiy ulanishni ushlab tursa — 50 ta ilova, 50 ta ulanish, telefon bir necha soatda o'chadi. Shuning uchun iOS va Android fon ilovalarini qattiq cheklaydi. Push tizimi — operatsion tizimning yagona doimiy ulanishi: Apple yoki Google serveriga bitta ulanish orqali barcha ilovalar uchun xabarlar keladi. Ilova esa faqat xabar kelganda "uyg'otiladi".


Xulosa

Eslab qoling:

  • Lokal — scheduleNotificationAsync (eslatmalar); remote — serverdan
  • setNotificationHandler — ilova ochiq paytda ko'rsatish
  • Token: ruxsat → getExpoPushTokenAsync({ projectId }) → serverga
  • Android — kanallar majburiy; ikonka — oq va shaffof
  • Server: exp.host/--/api/v2/push/send; DeviceNotRegistered — tokenni o'chirish
  • data.url + addNotificationResponseReceivedListener + getLastNotificationResponse — bosilganda ekranga
  • Remote push — development build va haqiqiy qurilma
  • Kam, foydali, aniq; ruxsatni kontekstda so'rang

Keyingi dars

43-dars: Haptics, Share, Clipboard, Linking