iOS-da FCM token nega null qaytaradi — va besh qatorli yechim

APNS va FCM o'rtasidagi race condition haqida hujjatlar sizga aytmaydi


Push-notifikatsiyali Flutter ilova chiqardingiz. Android'da hammasi joyida. Lekin iOS foydalanuvchilarining yarmi notifikatsiya olmayapti, analytics esa backend'da bir to'da foydalanuvchining FCM tokeni null ekanini ko'rsatadi.

Sababchi deyarli har doim shu bitta qator bo'ladi:

final token = await FirebaseMessaging.instance.getToken();

Beozor ko'rinadi. iOS'da esa bu — race condition.


Aslida nima sodir bo'ladi

iOS'da Firebase Cloud Messaging sizga FCM tokeni berolmaydi, agar APNS iOS'ga oldin APNS tokenini bermagan bo'lsa. APNS ro'yxatdan o'tishi asinxron: ilova requestPermission() chaqirganda boshlanadi, va device token keyinroq AppDelegate'dagi didRegisterForRemoteNotificationsWithDeviceToken callback orqali keladi.

Agar siz getToken()'ni shu callback ishlamasdan oldin chaqirsangiz, uch narsa sodir bo'lishi mumkin:

  1. null qaytariladi.
  2. "apns-token-not-set" exception otiladi.
  3. Chaqiruv ovozsiz qotib qoladi va splash sahifa abadiy turaveradi.

Flutter plagini buni qisman hal qilishga harakat qiladi, lekin race haqiqiy. Sekin internetda, sovuq startda, qayta o'rnatilgan ilovada, sust APNS gateway bilan operatorda — siz yutqazasiz.


Productionda yashamaydi soxta yechimlar

// ❌ Beqaror. Ba'zan 1 sekund yetadi, ba'zan 8 sekund ham yetmaydi.
await Future.delayed(const Duration(seconds: 3));
final token = await messaging.getToken();
// ❌ Batareyani isrof qiladi. Ba'zi foydalanuvchilarda umuman APNS ro'yxatdan o'tmaydi (internet yo'q).
while (true) {
  final t = await messaging.getToken();
  if (t != null) return t;
}

Ikkalasi ham asl signalni — APNS tokenining o'zini — e'tiborsiz qoldiradi.


Besh qatorli yechim

APNS tokenini aniq kutib oling, chegaralangan retry bilan:

import 'dart:io';
import 'package:firebase_messaging/firebase_messaging.dart';

Future<String?> getFcmTokenSafely() async {
  final messaging = FirebaseMessaging.instance;

  final settings = await messaging.requestPermission();
  if (settings.authorizationStatus == AuthorizationStatus.denied) {
    return null;
  }

  if (Platform.isIOS) {
    String? apnsToken;
    for (var i = 0; i < 10; i++) {
      apnsToken = await messaging.getAPNSToken();
      if (apnsToken != null) break;
      await Future.delayed(const Duration(milliseconds: 500));
    }
    if (apnsToken == null) return null; // haqiqatan ham muvaffaqiyatsiz
  }

  return messaging.getToken();
}

10 ta retry × 500 ms = eng yomon holatda 5 sekund. Amalda tsikl 1- yoki 2-iteratsiyada chiqadi. Funksiya null qaytaradigan yagona holatlar — ruxsat berilmagan yoki APNS rostdan ham ro'yxatdan o'tolmagan. Ikkalasi ham siz backend'ga bildirishingiz kerak bo'lgan haqiqiy xatolar — ularni cheksiz retry qilmasligingiz kerak.


Token yangilanishiga albatta obuna bo'ling

Token istalgan vaqtda o'zgarishi mumkin — ilova qayta o'rnatildi, backup tiklandi, FCM rotation qildi. Startup'da bir marta obuna bo'ling:

messaging.onTokenRefresh.listen((newToken) {
  // PATCH /me/push-token { token: newToken }
});

Backend'ni sinxron tutishning birdan-bir ishonchli yo'li shu. getToken() qaytargan token — bu snapshot; onTokenRefresh esa — haqiqat manbai.


Production-checklist 4 ta nuqta

Kodni ayblashdan oldin iOS tomonini tekshiring:

  1. Push Notifications capability Xcode → Signing & Capabilities'da yoqilgan.
  2. Background ModesRemote notifications belgilangan.
  3. aps-environment entitlementi provisioning profilingizda bor (development yoki production).
  4. iOS Simulator'da iOS 16'dan past versiyada test qilmayapsiz — u yerda APNS umuman ishlamaydi. iOS 16+ simulator Apple Silicon Mac bilan juftlangan bo'lsa, push qo'llab-quvvatlanadi.

"iOS'da FCM ishlamayapti" deb keladigan bug-report'larning 90% — yuqoridagi 4 nuqtadan biri. Token race esa qolgan 10% — va aynan kodingiz tuzata oladigani.


Qisqacha

To'liq helper'ni va har refresh'da tokenni backend'ga jo'natadigan PushTokenSyncService'ni flux_advanced loyihamda — o'zimning open-source Flutter Clean Architecture namunamda — ko'rishingiz mumkin.

Agar bu maqola sizga print(token) bilan o'tkaziladigan bir kechni saqlab qolgan bo'lsa, repository'ga ⭐ qo'yish — yagona iltimosim.