# Davidortinau Maui Skills Maui Push Notifications

> Push Notifications — Gotchas & Best Practices

- Skill: `tomevault-io/davidortinau-maui-skills-maui-push-notifications` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add tomevault-io/davidortinau-maui-skills-maui-push-notifications`
- Raw SKILL.md: https://api.skillmd.com/api/skills/tomevault-io/davidortinau-maui-skills-maui-push-notifications/raw
- Safety review: pending (external: skill-scanner PASS, skillspector PASS)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: tomevault-io (https://skillmd.com/u/tomevault-io)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/tomevault-io/davidortinau-maui-skills-maui-push-notifications

---


# 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.

```csharp
// ❌ 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.

```csharp
// ❌ 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:

```csharp
// ✅ 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.json` in `Platforms/Android/` with `<GoogleServicesJson>` in `.csproj`
- [ ] `Xamarin.Firebase.Messaging` NuGet package added (Android-only condition)
- [ ] `POST_NOTIFICATIONS` permission requested on API 33+
- [ ] Notification channel created before first notification (API 26+)
- [ ] `OnNewToken` always calls `RegisterAsync`
- [ ] `FirebaseMessagingService` registered in manifest with `MESSAGING_EVENT` intent filter

### iOS
- [ ] Push Notifications capability enabled in Apple Developer portal
- [ ] APNs key (`.p8`) uploaded to Azure Notification Hub
- [ ] `Entitlements.plist` with `aps-environment` set to `development` or `production`
- [ ] `CodesignEntitlements` set in `.csproj`
- [ ] `RegisterForRemoteNotifications` called 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
- [ ] `BaseAddress` ends 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.

---
> Converted and distributed by [TomeVault](https://tomevault.io/claim/davidortinau) — claim your Tome and manage your conversions.
<!-- tomevault:4.0:skill_md:2026-04-11 -->

