Wait for iOS APNs token before requesting FCM token

On iOS, FirebaseMessaging.getToken() throws apns-token-not-set when called
before Apple has asynchronously delivered the APNs token, which happens after
the permission prompt. This surfaced as "Failed to update push settings" with
the toggle stuck off. Poll getAPNSToken() with a short bounded wait first, and
return false (toggle stays off) rather than throwing if it never arrives.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
rzuasti
2026-06-09 11:19:46 -04:00
co-authored by Claude Opus 4.8
parent 5fbee67a24
commit 7d7f0197cd
+21
View File
@@ -76,6 +76,16 @@ class FirebasePushService implements PushService {
return false;
}
// On iOS, FCM can only mint a token once Apple has delivered the APNs token
// to the app, which happens asynchronously after permission is granted.
// Calling getToken() before then throws `apns-token-not-set`, so wait for
// the APNs token first. Returns false (rather than throwing) if it never
// arrives, so the toggle simply stays off instead of erroring.
if (defaultTargetPlatform == TargetPlatform.iOS &&
!await _awaitApnsToken()) {
return false;
}
final token = await FirebaseMessaging.instance.getToken();
if (token == null) return false;
@@ -102,6 +112,17 @@ class FirebasePushService implements PushService {
await FirebaseMessaging.instance.deleteToken();
}
// Polls for the iOS APNs token, which Apple delivers asynchronously after the
// user grants permission. Returns true once it is available, or false if it
// has not arrived after a short bounded wait (e.g. no network on first run).
Future<bool> _awaitApnsToken() async {
for (var attempt = 0; attempt < 10; attempt++) {
if (await FirebaseMessaging.instance.getAPNSToken() != null) return true;
await Future<void>.delayed(const Duration(milliseconds: 500));
}
return false;
}
// Configure the local-notifications plugin and render foreground messages
// ourselves (the OS displays them directly when the app is backgrounded or
// terminated). Taps just open the app, so no tap handler is wired.