maui-push-notifications
Installation
SKILL.md
Push Notifications — Gotchas & Best Practices
Troubleshooting Table
| Issue | Cause | Fix |
|---|---|---|
| Token changes every debug run (Android) | Debug builds regenerate Firebase tokens | Always re-register in OnNewToken |
HttpClient requests fail silently |
BaseAddress missing trailing / |
Ensure endpoint ends with / |
| iOS push won't arrive on simulator | Simulators don't support APNS | Use a physical iOS device |
| No notifications on Android 13+ | POST_NOTIFICATIONS required (API 33+) |
Call RequestPermissions in OnCreate |
| Notification channel missing (API 26+) | Android requires explicit channel creation | Create channel before sending |
SendNotificationAsync throws for >20 tags |
Azure NH tag expression limit | Batch tags in groups of 20 |
422 on device registration |
Platform string mismatch | Use "fcmv1" (not "gcm") and "apns" |
Token empty at RegisterAsync |
Race condition on cold start | Guard with IsNullOrWhiteSpace check |
Critical Gotchas
⚠️ BaseAddress trailing slash
HttpClient.BaseAddress must end with /. Without it, relative URI resolution silently breaks.
// ❌ Relative URIs resolve incorrectly
_http = new HttpClient { BaseAddress = new Uri("https://example.com") };
// ✅ Trailing slash required
_http = new HttpClient { BaseAddress = new Uri("https://example.com/") };
⚠️ Android: Always re-register on token refresh
Firebase tokens regenerate frequently in debug builds. If you skip OnNewToken, the backend holds a stale token and pushes silently fail.
// ❌ Only registering once at startup
// (token may change later without re-registration)
// ✅ Always re-register in OnNewToken
public override void OnNewToken(string token)
{
var svc = IPlatformApplication.Current?.Services.GetService<IPushNotificationService>();
if (svc is null) return;
svc.Token = token;
_ = svc.RegisterAsync();
}
⚠️ Android 13+ requires POST_NOTIFICATIONS permission
Without explicitly requesting POST_NOTIFICATIONS on API 33+, notifications are silently dropped.
⚠️ Azure NH tag expression limit: 20 tags
SendNotificationAsync throws if a tag expression references more than 20 tags. Batch into chunks:
// ✅ Batch tags to stay under the limit
var batches = request.Tags.Chunk(20);
foreach (var batch in batches)
{
var tagExpr = string.Join(" || ", batch);
await _hub.SendFcmV1NativeNotificationAsync(payload, tagExpr, ct);
}
⚠️ Use "fcmv1" not "gcm"
The legacy "gcm" platform string causes 422 errors on device registration. Always use "fcmv1" for Android and "apns" for iOS.
⚠️ iOS simulators cannot receive push notifications
APNS only works on physical iOS devices. Simulators silently ignore push registrations.
Platform Checklist
Android
-
google-services.jsoninPlatforms/Android/with<GoogleServicesJson>in.csproj -
Xamarin.Firebase.MessagingNuGet package added (Android-only condition) -
POST_NOTIFICATIONSpermission requested on API 33+ - Notification channel created before first notification (API 26+)
-
OnNewTokenalways callsRegisterAsync -
FirebaseMessagingServiceregistered in manifest withMESSAGING_EVENTintent filter
iOS
- Push Notifications capability enabled in Apple Developer portal
- APNs key (
.p8) uploaded to Azure Notification Hub -
Entitlements.plistwithaps-environmentset todevelopmentorproduction -
CodesignEntitlementsset in.csproj -
RegisterForRemoteNotificationscalled on main thread after authorization grant - Device token converted to lowercase hex string (no dashes)
Backend
- Azure Notification Hub configured with FCM V1 JSON and APNS key
-
BaseAddressends with trailing/ - Tag expressions batched in groups of ≤20
- Platform strings:
"fcmv1"and"apns"(not legacy values)
Architecture Overview
MAUI app → ASP.NET Core backend → Azure Notification Hub → FCM / APNS → device
See references/push-notifications-api.md for full implementation templates.
Related skills