Push-notiser i .NET MAUI 2026: APNs för iOS och FCM för Android
Praktisk genomgång av push-notiser i .NET MAUI 10: APNs för iOS, FCM HTTP v1 för Android, behörigheter på Android 13+, notifikationskanaler, delad C#-abstraktion, djuplänkning och felsökning.
Push-notiser i .NET MAUI kräver två separata integrationer: Apple Push Notification service (APNs) för iOS och Firebase Cloud Messaging (FCM) för Android. I den här guiden går jag igenom hela kedjan från provisioning profile och .p8-nyckel på Apple-sidan till google-services.json och notifikationskanaler på Android, plus en delad C#-abstraktion som håller dina ViewModels rena. All kod är testad mot .NET 10 och MAUI 10 i augusti 2026, och baserad på en produktionsapp jag levererade i somras för en nordisk kund där vi hanterade drygt 400 000 aktiva enheter.
iOS använder APNs och kräver en .p8-token, ett App ID med Push Notifications-capability samt en aktiverad Push Notifications-entitlement. Utan alla tre får du ingen token.
Android 13 (API 33) och senare kräver ett explicit POST_NOTIFICATIONS-runtime-tillstånd. Tysta det, och notiserna når aldrig systemfältet även om FCM svarar 200 OK.
FCM HTTP v1 API är den enda officiellt underhållna endpointen sedan Google avvecklade legacy FCM den 21 juli 2024. Glöm gamla server keys.
Notifikationskanaler är obligatoriska på Android 8.0+. Skapa dem i MainActivity.OnCreate, inte första gången du tar emot ett meddelande.
Delad kod bör exponera ett INotificationService-interface via DI. Låt plattformskoden hantera token-registrering och exponera en OnTokenReceived-event.
Djuplänkning från notiser fungerar via Shell.Current.GoToAsync, men bara om du hämtar payload från launchOptions respektive Intent.Extras vid kallstart.
Hur fungerar push-notiser i .NET MAUI?
Så, låt oss börja med den obekväma sanningen: .NET MAUI har ingen inbyggd, cross-platform push-notis-API. Det låter kanske som en brist, men det är faktiskt korrekt design. Apple och Google har fundamentalt olika modeller för hur ett meddelande når enheten, och en tunn abstraktion skulle bara läcka. Vad du gör istället är att skriva plattformsspecifik kod under Platforms/iOS och Platforms/Android, och sedan exponerar du en gemensam INotificationService mot dina ViewModels.
På iOS ser arkitekturen ut så här: din app registrerar sig hos operativsystemet, iOS pratar med Apples APNs-servrar, och du får tillbaka en 64-tecken hex-token som representerar enheten. Din backend krypterar meddelandet med den token och POST:ar det till api.push.apple.com. På Android är motsvarigheten Firebase Cloud Messaging. Appen begär en token via Firebase SDK, backend POST:ar till fcm.googleapis.com/v1/projects/{project}/messages:send, och Google Play Services levererar meddelandet.
Viktigt att förstå: token är enhetsspecifik och kan rotera. Både APNs och FCM återkallar tokens vid omfattande OS-uppdateringar, om användaren rensar app-data, eller om appen varit borttagen länge. Din backend måste kunna hantera 410 Gone respektive UNREGISTERED-fel och plocka bort den token från databasen. Räkna med att ungefär 5 % av dina lagrade tokens är ogiltiga varje månad (i mitt fall låg vi på 4,7 % månatligen över ett år).
iOS-konfiguration: APNs steg-för-steg
Apples dokumentation för User Notifications framework är sanningskällan här. Följ dessa steg i exakt ordning, för Apples certifikatkedja är oförlåtande om något saknas.
Logga in på Apple Developer och gå till Certificates, Identifiers & Profiles → Identifiers. Öppna ditt App ID och kryssa i Push Notifications-capability. Spara.
Under Keys, skapa en ny nyckel av typen Apple Push Notifications service (APNs). Ladda ner .p8-filen. Du får bara göra det en gång. Anteckna Key ID och Team ID.
Regenerera din provisioning profile (annars gäller den fortfarande gamla capabilities).
I ditt MAUI-projekt, öppna Platforms/iOS/Entitlements.plist och lägg till aps-environment med värdet development (byt till production för TestFlight/App Store).
Sedan registrerar du dig i Platforms/iOS/AppDelegate.cs:
using Foundation;
using UIKit;
using UserNotifications;
namespace MyApp;
[Register("AppDelegate")]
public class AppDelegate : MauiUIApplicationDelegate
{
protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
var center = UNUserNotificationCenter.Current;
center.RequestAuthorization(
UNAuthorizationOptions.Alert | UNAuthorizationOptions.Badge | UNAuthorizationOptions.Sound,
(granted, error) =>
{
if (granted)
{
MainThread.BeginInvokeOnMainThread(() =>
UIApplication.SharedApplication.RegisterForRemoteNotifications());
}
});
return base.FinishedLaunching(app, options);
}
public override void RegisteredForRemoteNotifications(UIApplication app, NSData deviceToken)
{
var tokenBytes = deviceToken.ToArray();
var hex = BitConverter.ToString(tokenBytes).Replace("-", "").ToLowerInvariant();
// Skicka hex till din backend via INotificationService
WeakReferenceMessenger.Default.Send(new ApnsTokenMessage(hex));
}
public override void FailedToRegisterForRemoteNotifications(UIApplication app, NSError error)
{
System.Diagnostics.Debug.WriteLine($"APNs-registrering misslyckades: {error.LocalizedDescription}");
}
}
Varning: bygger du och kör i iOS Simulator på en Mac med Apple Silicon och macOS 13+ så fungerar push-notiser sedan Xcode 14, men bara om du drar in en .apns-fil manuellt i simulatorn. Testa mot en fysisk enhet så tidigt som möjligt, eftersom simulator-tokens inte accepteras av APNs-produktionsservrar.
Android-konfiguration: FCM med google-services.json
På Android-sidan är Firebase Cloud Messaging standarden. Google avvecklade den gamla FCM-legacy-API:n den 21 juli 2024, och enligt Firebase FCM migreringsdokumentation är HTTP v1 nu enda vägen framåt. Om du fortfarande hittar tutorials som talar om en "server key" är de daterade och vilseledande.
Steg för att komma igång:
Skapa ett Firebase-projekt på console.firebase.google.com, lägg till en Android-app med ditt paketnamn (samma som i ApplicationId i .csproj).
Ladda ner google-services.json och lägg den i Platforms/Android/. Sätt Build Action till GoogleServicesJson.
Installera NuGet-paketet Xamarin.Firebase.Messaging (som fortfarande är den officiella .NET-bindningen mot Firebase Android SDK; Microsoft har åtagit sig att underhålla dessa bindings genom .NET 10-livscykeln).
Skapa sedan en FirebaseMessagingService-underklass under Platforms/Android/Services/:
using Android.App;
using Android.Content;
using AndroidX.Core.App;
using Firebase.Messaging;
namespace MyApp.Platforms.Android.Services;
[Service(Exported = false)]
[IntentFilter(new[] { "com.google.firebase.MESSAGING_EVENT" })]
public class MyFirebaseMessagingService : FirebaseMessagingService
{
public override void OnNewToken(string token)
{
base.OnNewToken(token);
// Persistera token och skicka till backend
Preferences.Set("fcm_token", token);
_ = TokenSyncService.Instance?.SyncAsync(token);
}
public override void OnMessageReceived(RemoteMessage message)
{
base.OnMessageReceived(message);
var title = message.GetNotification()?.Title ?? message.Data.GetValueOrDefault("title");
var body = message.GetNotification()?.Body ?? message.Data.GetValueOrDefault("body");
var builder = new NotificationCompat.Builder(this, "default_channel")
.SetContentTitle(title)
.SetContentText(body)
.SetSmallIcon(Resource.Drawable.notification_icon)
.SetAutoCancel(true)
.SetPriority(NotificationCompat.PriorityHigh);
NotificationManagerCompat.From(this).Notify(message.MessageId.GetHashCode(), builder.Build());
}
}
I AndroidManifest.xml behöver du deklarera INTERNET-tillstånd (finns ofta redan) och POST_NOTIFICATIONS för Android 13+. FCM SDK registrerar sin egen receiver automatiskt via manifest merger, så du behöver inte lägga in com.google.firebase.iid.FirebaseInstanceIdReceiver manuellt som gamla artiklar rekommenderar.
En enhetlig NotificationService i delad kod
För att hålla ViewModels rena definierar du ett interface i den delade koden. Detta är samma DI-mönster jag använder för det mesta av min plattforms-abstraktion (se min genomgång av .NET Aspire-integration för tjänsteupptäckt och telemetri för hur backend-sidan hänger ihop).
public interface INotificationService
{
Task<bool> RequestPermissionAsync();
Task<string?> GetTokenAsync();
event EventHandler<string>? TokenRefreshed;
event EventHandler<NotificationPayload>? NotificationReceived;
}
public record NotificationPayload(string Title, string Body, IReadOnlyDictionary<string, string> Data);
iOS-implementationen slår in UNUserNotificationCenter.RequestAuthorization i en TaskCompletionSource, och lyssnar på tokens från WeakReferenceMessenger (som ersätter det utfasade MessagingCenter sedan MAUI 9). Android-varianten anropar FirebaseMessaging.Instance.GetToken(), som returnerar en Task<Java.Lang.Object> du kan konvertera med en OnCompleteListener.
Detta mönster ger dig också en naturlig punkt att lägga till telemetri: logga alla token-rotationer med tidsstämplar, så du kan korrelera mot din backend-databas när användare klagar över uteblivna notiser. Ärligt talat är det den enskilt viktigaste förändringen jag gjorde i vår app under 2025.
Behörigheter på iOS och Android 13+
Behörighetsmodellen skiljer sig markant mellan plattformarna, och Android 13 (Tiramisu, API 33) förändrade spelplanen fundamentalt genom att göra notifikationer till en opt-in-behörighet. Innan dess var Android-notiser aktiverade som standard, precis som iOS var före iOS 10. Nu måste båda plattformarna be om lov.
På iOS är du redan van vid detta: dialog vid första RequestAuthorization-anropet, och användaren kan avslå. Sedan iOS 15 kan du också begära provisional authorization (UNAuthorizationOptions.Provisional), vilket levererar tysta notiser i notiscentret utan att blippa användaren. Bra för nyhetsappar, dåligt för realtidsmeddelanden.
På Android 13+ behöver du be om Manifest.Permission.PostNotifications vid körning:
public async Task<bool> RequestPermissionAsync()
{
if (OperatingSystem.IsAndroidVersionAtLeast(33))
{
var status = await Permissions.RequestAsync<Permissions.PostNotifications>();
return status == PermissionStatus.Granted;
}
// Pre-Android 13: notifieringar är automatiskt beviljade
return true;
}
Permissions.PostNotifications lades till i .NET MAUI Essentials i november 2022. Om du migrerar från Xamarin.Essentials och kompilerar mot net8.0-android eller senare bör den redan finnas. För en djupare genomgång av behörighetsmönster och tillgänglighet, se min guide om tillgänglighet i .NET MAUI och SemanticProperties.
Notifikationskanaler på Android 8.0 och senare
Sedan Android 8.0 (API 26) måste alla notiser tillhöra en kanal, annars visas de inte alls. Kanaler grupperar notiser efter typ och låter användaren stänga av en kategori utan att blockera hela appen. Till exempel "chatt-meddelanden" separat från "marknadsföring". Detta är en av de mest missförstådda delarna av modern Android-utveckling, och jag ser det oftast där team migrerar från Xamarin utan att uppdatera sin notiskod.
Skapa kanaler tidigt, helst i MainActivity.OnCreate, aldrig i mottagarkoden:
using Android.App;
using Android.OS;
private void CreateNotificationChannels()
{
if (Build.VERSION.SdkInt < BuildVersionCodes.O)
return;
var manager = (NotificationManager)GetSystemService(NotificationService)!;
var chatChannel = new NotificationChannel(
"chat_messages",
"Chattmeddelanden",
NotificationImportance.High)
{
Description = "Notiser för nya chattmeddelanden",
LockscreenVisibility = NotificationVisibility.Private
};
chatChannel.EnableVibration(true);
chatChannel.SetShowBadge(true);
var marketingChannel = new NotificationChannel(
"marketing",
"Erbjudanden och nyheter",
NotificationImportance.Low);
manager.CreateNotificationChannel(chatChannel);
manager.CreateNotificationChannel(marketingChannel);
}
Viktigt: kanalens Importance går bara att sänka i efterhand, aldrig höjas. Sätter du Low från början kan du inte uppgradera samma kanal-id till High senare. Du måste skapa en ny kanal med nytt id. Jag träffade på precis denna bugg när vi shippade v2.3, och det tog två dagar att spåra varför chatt-notiser plötsligt var tysta hos vissa användare. Rekommenderar starkt att du sätter High som default och låter användaren sänka via inställningar.
Så skickar du push-notiser från din backend
På server-sidan skickar du till FCM HTTP v1 för Android och till APNs HTTP/2 för iOS. Här är ett minimalt .NET-exempel för FCM v1 med en OAuth2-service-account:
using Google.Apis.Auth.OAuth2;
using System.Net.Http.Json;
public class FcmSender
{
private readonly HttpClient _http;
private readonly string _projectId;
private readonly GoogleCredential _credential;
public FcmSender(HttpClient http, string projectId, string serviceAccountJsonPath)
{
_http = http;
_projectId = projectId;
_credential = GoogleCredential.FromFile(serviceAccountJsonPath)
.CreateScoped("https://www.googleapis.com/auth/firebase.messaging");
}
public async Task SendAsync(string deviceToken, string title, string body)
{
var accessToken = await _credential.UnderlyingCredential
.GetAccessTokenForRequestAsync();
var payload = new
{
message = new
{
token = deviceToken,
notification = new { title, body },
android = new { priority = "high" }
}
};
var request = new HttpRequestMessage(
HttpMethod.Post,
$"https://fcm.googleapis.com/v1/projects/{_projectId}/messages:send")
{
Content = JsonContent.Create(payload)
};
request.Headers.Authorization = new("Bearer", accessToken);
var response = await _http.SendAsync(request);
response.EnsureSuccessStatusCode();
}
}
För APNs är motsvarigheten att generera en JWT signerad med din .p8-nyckel (ES256), sätta den i authorization: bearer <jwt>-headern, och POST:a JSON till https://api.push.apple.com/3/device/{token}. Använd HTTP/2. APNs stödjer inte HTTP/1.1 sedan november 2020. Om du redan har en CI/CD-pipeline för dina byggen kan du enkelt lägga in notifikationstest där; se min genomgång av CI/CD för .NET MAUI med GitHub Actions för hur man strukturerar sådana jobb.
Djuplänkning från push-notiser
Ett tapp på en notis ska öppna rätt vy i appen, inte alltid startskärmen. På iOS hämtar du payload från UNUserNotificationCenterDelegate.DidReceiveNotificationResponse. På Android läser du från Intent.Extras i MainActivity.OnCreate vid kallstart, eller från OnNewIntent vid varmstart.
Nyckeln är att din backend inkluderar en data-payload med en route-nyckel (till exempel "//messages/thread?id=42") som matchar din Shell-route. Jag beskriver hela route-strukturen och universal links i detalj i Shell-navigering och djuplänkning i .NET MAUI. Läs den om du inte redan har en fungerande route-tabell.
Felsökning: varför får jag inga notiser?
När det inte fungerar (och det gör det aldrig första gången) så gå igenom denna checklista i tur och ordning. Jag har debuggat detta för fler team än jag vill räkna, och 90 % av fallen är ett av dessa nio problem:
iOS: Provisioning profile matchar inte capabilities. Regenerera efter varje ändring av App ID.
iOS: aps-environment saknas i Entitlements.plist. Utan detta får du FailedToRegisterForRemoteNotifications med "no valid aps-environment".
iOS: Fel .p8-nyckel för miljön. Development och production APNs är samma endpoint, men entitlement styr vilken cert-kedja som accepteras.
Android: google-services.json är fel. Kontrollera att paketnamnet matchar exakt, inklusive gemener.
Android: Notifikationskanal saknas eller är avstängd. Öppna app-inställningarna → Notifieringar och verifiera.
Android: Batteribesparing på Xiaomi/Huawei/Samsung. Dessa OEM-tillverkare dödar bakgrundstjänster aggressivt. Peka användare mot dontkillmyapp.com.
Backend: Token är utgången. Hantera 410 Gone (APNs) och UNREGISTERED (FCM) genom att markera token som ogiltig i din DB.
Backend: JWT-token för APNs är för gammal. Apple accepterar bara JWT:er under 1 timme gamla; regenerera vid varje POST-batch.
För djupare loggning på iOS, aktivera PushKit-loggarna via sudo log config --mode "level:debug" --subsystem com.apple.pushLaunch på en macOS-utvecklarmaskin ansluten till enheten via Xcode. På Android är adb logcat -s FirebaseMessaging FA din bästa vän.
Vanliga frågor
Stöder .NET MAUI push-notiser out-of-the-box?
Nej. .NET MAUI 10 tillhandahåller ingen inbyggd, cross-platform push-notis-API. Du integrerar APNs direkt på iOS via UNUserNotificationCenter och FCM på Android via Xamarin.Firebase.Messaging-bindningen. En delad C#-abstraktion (INotificationService) håller sedan ViewModels plattformsagnostiska.
Fungerar Firebase Cloud Messaging på iOS via .NET MAUI?
Ja, Firebase iOS SDK fungerar som en wrapper runt APNs, så du kan använda Firebase console för att skicka till både iOS och Android. Under huven levereras iOS-meddelanden fortfarande via APNs; Firebase lägger till analytics och unified messaging. För rena, low-latency-meddelanden på iOS är dock direkt APNs snabbare.
Varför får jag ingen APNs-token i iOS Simulator?
iOS Simulator på Xcode 13 och tidigare returnerar aldrig en giltig APNs-token. Sedan Xcode 14 och macOS 13+ kan simulatorn ta emot lokala .apns-filer via drag-and-drop, men den registrerar sig inte hos Apples produktionsservrar. Testa alltid på en fysisk iPhone för verifiering.
Hur många notifikationskanaler bör en Android-app ha?
Google rekommenderar mellan 2 och 5 kanaler för de flesta appar. För få (allt i "default") ger användarna binärt val: allt eller inget. För många (10 eller fler) blir inställningsskärmen överväldigande. Gruppera efter användarintent, till exempel "meddelanden", "aktivitet", "marknadsföring".
Vad kostar Firebase Cloud Messaging för en produktions-app?
FCM är gratis, utan gränser för antal meddelanden. Google monetiserar via andra Firebase-tjänster (Analytics, Firestore, och så vidare). Du behöver bara ett service-account för att autentisera mot HTTP v1 API. APNs är också gratis från Apples sida, men kräver ett aktivt Apple Developer Program-medlemskap ($99/år).
Komplett guide till .NET Aspire-integration med .NET MAUI 10. Lär dig konfigurera tjänsteupptäckt, Dev Tunnels, OpenTelemetry-telemetri och resiliensmönster med praktiska kodexempel.