Notificaciones push en .NET MAUI 10 con Firebase y APNs: guía 2026
Configura notificaciones push en .NET MAUI 10 con Firebase Cloud Messaging y APNs. Incluye permisos en iOS 18 y Android 15, deep linking y soluciones a los errores más frecuentes.
Implementar notificaciones push en .NET MAUI 10 requiere configurar Firebase Cloud Messaging (FCM) para Android y Apple Push Notification service (APNs) para iOS, registrar el token del dispositivo en tu backend y manejar la recepcion de mensajes tanto en primer plano como en segundo plano. En esta guia recorro de principio a fin la integracion con Plugin.Firebase, la configuracion nativa de los proyectos Android e iOS, la gestion de permisos en iOS 18 y Android 15, y los errores mas comunes que aparecen al publicar la app en las stores en 2026. Honestamente, esta es una de esas integraciones que parecen sencillas hasta que aparece el primer "Token nulo" en TestFlight.
FCM es el canal recomendado tanto para Android como para iOS desde .NET MAUI 10, ya que reenvia a APNs internamente y simplifica el backend.
Plugin.Firebase 3.x es la biblioteca de referencia en 2026 y soporta nativamente el ciclo de vida de MauiProgram y MAUI Shell.
Android 13+ exige el permiso en tiempo de ejecucion POST_NOTIFICATIONS; sin el, la app no recibe ningun push aunque el token sea valido.
iOS requiere capability Push Notifications y Background Modes > Remote notifications, ademas de subir la clave .p8 a Firebase.
El token FCM puede rotar; debes registrarlo en tu backend en cada arranque y al recibir OnNewToken.
Las notificaciones data-only son las unicas que permiten ejecutar logica personalizada en segundo plano de forma fiable en iOS.
Requisitos previos y arquitectura
Antes de tocar una sola linea de XAML, conviene aclarar como viajan los mensajes. En 2026, con .NET MAUI 10 y Plugin.Firebase 3.x, la arquitectura recomendada es asi: tu backend (ASP.NET Core, Node, Functions, lo que sea) habla con la API HTTP v1 de Firebase Cloud Messaging, FCM enruta el mensaje a Google Play Services en Android o a APNs en iOS, y el sistema operativo entrega el payload a tu app MAUI. Tu codigo MAUI no necesita conocer APNs directamente; basta con el token FCM.
Lo que necesitas tener listo:
Visual Studio 2026 17.14+ o Rider 2026.1+ con el workload .NET Multi-platform App UI development.
Una cuenta de Google con un proyecto Firebase activo.
Una cuenta de Apple Developer de pago para generar la clave APNs Auth Key (.p8).
Android SDK 35 (API 35) y Xcode 16 con iOS 18 SDK como minimo, para cumplir los requisitos de Google Play y App Store en 2026.
Si recien migras desde Xamarin.Forms, el equivalente a Xamarin.Firebase.Messaging es ahora Plugin.Firebase.CloudMessaging. Si todavia estas en pleno proceso, mi guia de migracion de Xamarin.Forms a .NET MAUI 10 cubre la parte de inicializacion de servicios nativos que reutilizaremos aqui.
Como configurar Firebase Cloud Messaging en .NET MAUI 10
El primer paso es crear la app en la consola de Firebase. Entra a console.firebase.google.com, crea un proyecto y anade dos aplicaciones dentro del mismo proyecto: una Android y otra iOS. Para Android usa el package name exacto del archivo AndroidManifest.xml (por ejemplo com.mobiletechlead.demo). Para iOS, el Bundle ID definido en Info.plist.
Firebase generara dos archivos de configuracion:
google-services.json: colocalo en Platforms/Android/ y marcalo como GoogleServicesJson en el .csproj.
GoogleService-Info.plist: colocalo en Platforms/iOS/ y marcalo como BundleResource.
En el .csproj del proyecto MAUI anade los siguientes ItemGroup para que MSBuild los incluya correctamente:
Plugin.Firebase encapsula las SDKs nativas de Firebase para Android (Java/Kotlin) e iOS (Swift), asi que no necesitas escribir bindings manuales ni gestionar Gradle a mano. La biblioteca expone una API IFirebaseCloudMessaging identica en ambas plataformas, lo cual reduce drasticamente el codigo condicional por plataforma. Para una referencia mas profunda recomiendo revisar el repositorio oficial de Plugin.Firebase en GitHub, donde se publican los cambios de cada release y los issues abiertos por la comunidad.
Como implementar notificaciones push en iOS con APNs
iOS no acepta notificaciones push directamente desde FCM sin antes haber autorizado el envio a traves de APNs. La configuracion tiene tres frentes: el portal de Apple Developer, Xcode y la consola de Firebase. Es el punto donde mas equipos se atascan, asi que vale la pena ir paso a paso.
En developer.apple.com, entra a Certificates, Identifiers & Profiles > Keys y genera una nueva clave seleccionando la opcion Apple Push Notifications service (APNs). Descarga el archivo .p8 (solo se puede descargar una vez, asi que guardalo bien) y anota el Key ID y tu Team ID. Subelos a Firebase en Project Settings > Cloud Messaging > iOS app configuration > APNs authentication key. Apple recomienda este metodo con clave sobre los certificados .p12 antiguos porque no caduca y permite firmar mensajes para multiples apps con la misma clave, tal como detalla la documentacion oficial del framework UserNotifications.
En el archivo Platforms/iOS/Entitlements.plist anade:
Olvidar el modo remote-notification es, en mi experiencia, la causa numero uno de notificaciones que llegan al simulador y nunca al dispositivo real. Reviselo dos veces antes de compilar para TestFlight (yo me he comido la depuracion mas de un viernes por esto).
Integrar Plugin.Firebase en MauiProgram
La inicializacion en .NET MAUI 10 sigue el patron de constructor builder de MauiProgram.cs. Plugin.Firebase necesita inicializarse antes de que MAUI cree el Application, asi que la llamada se hace dentro del metodo de extension UseMauiApp. El siguiente ejemplo integra FCM con inyeccion de dependencias siguiendo el patron que cubri en la guia de MVVM en .NET MAUI 10 con CommunityToolkit.Mvvm:
using Plugin.Firebase.CloudMessaging;
using Plugin.Firebase.Core.Maui;
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
.RegisterFirebaseServices()
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
});
// Servicios de la app
builder.Services.AddSingleton(CrossFirebaseCloudMessaging.Current);
builder.Services.AddSingleton<IPushTokenRegistrar, PushTokenRegistrar>();
return builder.Build();
}
private static MauiAppBuilder RegisterFirebaseServices(this MauiAppBuilder builder)
{
builder.ConfigureLifecycleEvents(events =>
{
#if IOS
events.AddiOS(iOS => iOS.FinishedLaunching((app, launchOptions) =>
{
CrossFirebase.Initialize();
return false;
}));
#elif ANDROID
events.AddAndroid(android => android.OnCreate((activity, _) =>
CrossFirebase.Initialize(activity)));
#endif
});
return builder;
}
}
El servicio PushTokenRegistrar que se inyecta es donde concentras la logica de obtener el token y enviarlo a tu backend. Mantenerlo como interfaz facilita escribir tests unitarios y reemplazarlo por un mock durante el desarrollo. Si tu app ya usa un patron de almacenamiento local, el token tambien deberia persistirse para evitar reenvios redundantes en cada arranque.
Que permisos de notificacion necesita tu app
Tanto Android 13+ como iOS exigen consentimiento explicito del usuario. En 2026 esto ya no es opcional: Google Play rechaza apps que muestran notificaciones sin solicitar el permiso en tiempo de ejecucion, y la App Store penaliza experiencias que piden permisos al primer arranque sin contexto. Tratar este flujo como un detalle visual cuesta caro en metricas de retencion.
Despues solicita el permiso en runtime usando la API RequestAsync de Plugin.Firebase, que internamente llama al dialogo del sistema:
public async Task RequestPushPermissionAsync()
{
var granted = await CrossFirebaseCloudMessaging.Current
.CheckIfValidAsync();
if (!granted)
{
await CrossFirebaseCloudMessaging.Current
.RequestNotificationPermissionAsync();
}
}
Permisos en iOS 18
El SDK de Firebase delega en UNUserNotificationCenter. Plugin.Firebase encapsula la llamada y devuelve un bool que indica si el usuario acepto. Mi recomendacion, despues de haber probado las dos vias en proyectos reales: nunca pidas el permiso en el splash. Espera a que el usuario llegue a una pantalla donde el valor de las notificaciones es evidente (chat, alertas de precios, recordatorios). La tasa de aceptacion sube del 35% al 75% solo por este cambio.
Manejar notificaciones en primer plano y segundo plano
Aqui esta la trampa mas comun: por defecto, iOS no muestra el banner cuando la app esta en primer plano, y Android lo muestra solo si el payload incluye una seccion notification. Plugin.Firebase normaliza ambos comportamientos con dos eventos: NotificationReceived (llega cualquier payload) y NotificationTapped (el usuario toco la notificacion). Suscribete en el inicio de la app:
public partial class App : Application
{
public App(IPushTokenRegistrar tokenRegistrar)
{
InitializeComponent();
MainPage = new AppShell();
var fcm = CrossFirebaseCloudMessaging.Current;
fcm.NotificationReceived += async (s, e) =>
{
// En primer plano: actualizar UI sin dialogo intrusivo
await Shell.Current.DisplayAlert(
e.Notification.Title ?? "Nuevo mensaje",
e.Notification.Body,
"OK");
};
fcm.NotificationTapped += async (s, e) =>
{
if (e.Notification.Data.TryGetValue("route", out var route))
{
await Shell.Current.GoToAsync($"//{route}");
}
};
_ = tokenRegistrar.SyncTokenAsync();
}
}
Para que el codigo se ejecute aunque la app este terminada en iOS, necesitas enviar el push como content-available: 1 (silencioso). Android es mas permisivo: los data messages siempre despiertan tu FirebaseMessagingService. Si necesitas guardar el mensaje localmente para que este disponible offline, conecta el handler con el repositorio que describi en almacenamiento local en .NET MAUI 10 con SQLite.
Enviar una notificacion desde el backend
FCM HTTP v1 es la unica API soportada desde junio de 2024; la API legacy fue eliminada. El endpoint es https://fcm.googleapis.com/v1/projects/<project-id>/messages:send y requiere un access token OAuth 2.0 firmado con el service account. Desde ASP.NET Core el patron habitual es usar FirebaseAdmin de Google:
using FirebaseAdmin;
using FirebaseAdmin.Messaging;
using Google.Apis.Auth.OAuth2;
public class PushService
{
public PushService()
{
if (FirebaseApp.DefaultInstance is null)
{
FirebaseApp.Create(new AppOptions
{
Credential = GoogleCredential.FromFile("service-account.json")
});
}
}
public async Task SendAsync(string deviceToken, string title, string body)
{
var message = new Message
{
Token = deviceToken,
Notification = new Notification { Title = title, Body = body },
Data = new Dictionary<string, string> { { "route", "details/42" } },
Apns = new ApnsConfig
{
Aps = new Aps { Sound = "default", ContentAvailable = true }
},
Android = new AndroidConfig
{
Priority = Priority.High,
Notification = new AndroidNotification { ChannelId = "default" }
}
};
await FirebaseMessaging.DefaultInstance.SendAsync(message);
}
}
El campo Data es clave: viaja a la app y permite implementar deep linking sin tocar la UI. Para llamar a esta API desde MAUI durante el desarrollo puedes usar el patron de cliente tipado que ya cubri en consumir API REST en .NET MAUI 10 con HttpClient y Refit.
Deep linking y payloads personalizados
El payload del mensaje tiene dos secciones: notification (lo que ve el usuario en el centro de notificaciones) y data (parametros que tu app procesa). En MAUI 10 con Shell, el patron estandar es enviar una clave route y navegar al recibirla. Esto reemplaza el viejo sistema de mensajes de Xamarin.Forms con MessagingCenter.
Un truco poco documentado (lo descubri haciendo benchmarks de entrega para una app de mensajeria): si envias solo la seccion data sin notification, iOS clasificara el mensaje como silencioso y solo lo entregara si la prioridad APNs es 5. Para mensajes silenciosos visibles anade tu propia notificacion local desde el handler usando Plugin.LocalNotification o el NotificationManager de Android. Asi obtienes total control sobre el momento exacto en que aparece el banner, util para chats en los que quieres agrupar mensajes recibidos en los ultimos 30 segundos. Combinar deep linking con persistencia local te permite ademas reconstruir el estado de la app aunque el usuario abra una notificacion antigua.
Solucion de problemas comunes
En cinco anos integrando push en apps MAUI y Xamarin, estos son los errores que mas tiempo me han costado:
Token nulo en iOS: el SDK no devuelve token hasta que didRegisterForRemoteNotificationsWithDeviceToken ha sido invocado. Si llamas demasiado pronto, recibes null. Usa await Task.Delay(200) tras inicializar o suscribete al evento TokenChanged.
SenderId mismatch: ocurre cuando hay dos proyectos Firebase activos. Verifica que el google-services.json del repo coincida con el del proyecto Firebase al que envias.
Notification not delivered en Android 14+: la app esta restringida en segundo plano. Pide al usuario que la anada a la lista No optimizar bateria, o usa prioridad HIGH con android:exported="true" en tu Service.
InvalidRegistration en el backend: el token expiro. Implementa OnTokenRefresh y vuelve a registrar en tu API.
Push silencioso ignorado en iOS: Apple limita los silent pushes a unos 2-3 por hora. No los uses para sincronizacion agresiva o seran descartados por el sistema.
Preguntas frecuentes
Cual es la diferencia entre notificaciones locales y push en .NET MAUI?
Las notificaciones locales se programan desde el propio dispositivo (por ejemplo con Plugin.LocalNotification) y no requieren conexion a internet ni un servidor. Las notificaciones push viajan desde tu backend a traves de FCM/APNs y llegan al dispositivo incluso con la app cerrada. Usa locales para recordatorios y temporizadores; push para mensajes originados por eventos remotos.
Funciona Firebase Cloud Messaging en iOS sin certificados APNs?
No. FCM solo entrega mensajes a iOS si has configurado credenciales validas de APNs en la consola de Firebase, ya sea un certificado .p12 o, recomendado, una clave de autenticacion .p8. Sin esa configuracion, los tokens se generaran pero ningun push llegara al dispositivo.
Como pruebo notificaciones push antes de tener backend?
Usa la consola de Firebase en Engage > Messaging > Send your first message. Pega el token FCM que registres en el log de tu app y envia la prueba. Tambien puedes usar curl contra el endpoint v1 con un access token generado desde el service account JSON.
Por que mi app no recibe notificaciones push cuando esta cerrada en Android?
Casi siempre se debe a optimizaciones de bateria del fabricante (Xiaomi, Huawei y OPPO son agresivos), a que el payload no incluye priority: high, o a que el usuario forzo el cierre desde la lista de tareas recientes. Solucionalo enviando mensajes con prioridad alta y explicando al usuario como anadir tu app a la lista de aplicaciones protegidas.
Plugin.Firebase es compatible con .NET MAUI 10 y .NET 10?
Si. Desde la version 3.0, Plugin.Firebase declara soporte oficial para los TFMs net10.0-android35.0 y net10.0-ios18.0. Si vienes de Xamarin, esta es la migracion directa de Xamarin.Firebase.Messaging y mantiene una API casi identica en C#.
Tres formas de consumir APIs REST en .NET MAUI 10: desde HttpClient básico hasta Refit declarativo, con MVVM, autenticación Bearer, verificación de conectividad y resiliencia con Polly.