Wilson Vargas

Target API 36 en Google Play: cómo actualizar tu app .NET MAUI (qué cambiar en el .csproj y qué permisos revisar)

Google Play ya exige Android 16 (API 36). Qué versión de .NET necesitas, qué cambiar en el .csproj y el manifiesto, y qué permisos y comportamientos revisar.

Portada: Google Play exige target API 36 para apps .NET MAUI

¿Abriste Play Console estas semanas y te encontraste con un aviso rojo de "Target API level" sobre tu app .NET MAUI? Si es así, no estás solo: el 31 de agosto de 2026 Google Play empezó a exigir Android 16 (API 36) para apps nuevas y actualizaciones, y la prórroga que se podía pedir terminó el 1 de noviembre. En otras palabras: si todavía no actualizaste, hoy no puedes subir una nueva versión.

Esto no es nuevo. Desde que empecé con Xamarin en 2017, cada año ha tocado subir el targetSdkVersion. Lo que sí cambió es cómo se hace en .NET: ya no es un desplegable en las propiedades del proyecto, sino que depende de tu TargetFramework. Y hay una trampa en el manifiesto que vale la pena conocer.

Hoy vamos a ver qué exige Google Play, qué versión de .NET necesitas, qué cambiar en el .csproj y qué comportamientos y permisos de Android 16 tienes que revisar antes de subir tu AAB.

¿Qué exige Google Play exactamente?

Línea de tiempo del requisito de target API en Google Play

Google tiene dos reglas distintas y es fácil confundirlas:

Regla Qué exige Qué pasa si no cumples
Apps nuevas y actualizaciones Apuntar a API 36 (Android 16) desde el 31/08/2026 No puedes subir la nueva versión
Apps existentes Apuntar al menos a API 35 (Android 15) Tu app deja de aparecer para usuarios nuevos con Android más reciente que tu target

Hay excepciones por tipo de dispositivo: Wear OS y Android Automotive OS piden API 35 para actualizaciones, y Android TV y Android XR piden API 34. Si tu app es solo para teléfonos y tablets, la regla que te aplica es la de API 36.

Ojo: los usuarios que ya tienen tu app instalada no la pierden. El problema es que no puedes publicar correcciones ni funciones nuevas, y eso, en la práctica, es como tener la app congelada.

Google sube este requisito cada año, más o menos un año después de que sale cada versión de Android. A mi parecer, lo más sano es asumir que en 2027 va a tocar API 37, aunque al escribir esto Google todavía no lo ha anunciado.

La clave en .NET: el target API viene de tu TargetFramework

En Xamarin.Android elegías "Compile using Android version" y "Target Android version" por separado. En .NET for Android, el nivel de API sale del TFM (net10.0-android, net11.0-android, etc.). Si no pones número, el SDK usa el último nivel estable que soporta esa versión de .NET:

Qué nivel de API usa cada versión de .NET por defecto
Versión de .NET API por defecto con netX.0-android ¿Cumple Google Play hoy? Soporte de .NET MAUI
.NET 8 34 (Android 14) ❌ Terminó el 14/05/2025
.NET 9 35 (Android 15) ❌ (solo sirve para seguir visible) Terminó el 12/05/2026
.NET 10 36 (Android 16) ✅ Hasta el 11/05/2027
.NET 11 37 (Android 17) ✅ Se lanza en .NET Conf (10–13 de noviembre de 2026)

La conclusión es directa: si tu app sigue en .NET 8 o .NET 9, tienes que pasar al menos a .NET 10. No hay un "parche" para que .NET 9 apunte a API 36: la versión de .NET define qué bindings de Android tienes disponibles.

Importante: .NET MAUI 9 ya no tiene soporte desde mayo de 2026. Aunque el requisito de Google no existiera, quedarte ahí ya era mala idea.

Paso a paso: actualizar el .csproj

1. Cambia el TargetFramework

Este es el cambio principal. En un proyecto .NET MAUI típico:

<PropertyGroup>
    <!-- Antes: net9.0-android → API 35 -->
    <!-- Ahora: net10.0-android → API 36 -->
    <TargetFrameworks>net10.0-android;net10.0-ios;net10.0-maccatalyst</TargetFrameworks>
</PropertyGroup>

Si prefieres que el nivel de API quede explícito y no cambie solo cuando actualices el SDK, puedes ponerlo en el TFM:

<!-- Fija API 36 aunque el SDK traiga una versión más nueva -->
<TargetFrameworks>net10.0-android36.0;net10.0-ios;net10.0-maccatalyst</TargetFrameworks>

Lo que más me gusta de dejarlo sin número es que, al pasar a .NET 11, subes a API 37 sin tocar nada más. Lo que no me gusta es justo eso: que el cambio de comportamiento te llegue "de regalo" sin darte cuenta. Para apps en producción, yo lo dejaría explícito.

2. Actualiza los paquetes NuGet

Con el cambio de TFM, actualiza también Microsoft.Maui.Controls y el resto de paquetes a versiones compatibles con .NET 10 (CommunityToolkit.Maui, SkiaSharp, Syncfusion, etc.). La mayoría de los errores de compilación después del salto vienen de aquí, no del API de Android.

3. Revisa SupportedOSPlatformVersion (es otra cosa)

Mucha gente confunde estas dos propiedades:

Propiedad Qué controla En el manifiesto
TargetFramework (net10.0-android) Contra qué API compilas y a cuál apuntas targetSdkVersion
SupportedOSPlatformVersion Versión mínima de Android donde se instala tu app minSdkVersion

La documentación de .NET for Android lo dice claro: SupportedOSPlatformVersion fija el mínimo y "no representa la política de Google Play". O sea, no tienes que subir el mínimo para cumplir con Play.

Eso sí, si piensas pasar a .NET 11, ten en cuenta que el mínimo sube a API 24 (Android 7.0), porque CoreCLR lo requiere. Es un cambio importante documentado oficialmente:

<!-- Necesario en .NET 11: el mínimo pasó de 21 a 24 -->
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">24.0</SupportedOSPlatformVersion>

Si quieres más contexto sobre ese cambio de runtime, te lo conté en CoreCLR en .NET 11: adiós Mono.

4. La trampa: el targetSdkVersion del AndroidManifest.xml

Aquí está el detalle que más dolores de cabeza da en proyectos migrados desde Xamarin. Abre Platforms/Android/AndroidManifest.xml y busca esto:

<!-- ❌ Si tienes esto, el TFM no sirve de nada -->
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="33" />

Revisando el código de .NET for Android (ManifestDocument.cs), el comportamiento es este: si el manifiesto no trae targetSdkVersion, el build lo agrega con el nivel de tu TFM; pero si ya lo trae, se respeta el valor que tú pusiste. Es decir, puedes compilar con net10.0-android y aun así subir un AAB que apunta a API 33.

La señal de alerta es la advertencia XA1006: "The TargetFrameworkVersion (Android API level 36) is higher than the targetSdkVersion (33)". Si la ves en tu build, no la ignores.

La solución más simple es borrar los atributos y dejar que el TFM mande:

<!-- ✅ Sin uses-sdk: minSdkVersion sale de SupportedOSPlatformVersion
     y targetSdkVersion sale del TargetFramework -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <application android:allowBackup="true" android:icon="@mipmap/appicon"
                 android:roundIcon="@mipmap/appicon_round" android:supportsRtl="true" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.INTERNET" />
</manifest>
El targetSdkVersion del manifiesto gana al TargetFramework

5. Verifica el resultado en el paquete final

No te quedes con lo que dice el .csproj: revisa lo que realmente va a recibir Google. Con las herramientas del SDK de Android:

apkanalyzer manifest target-sdk bin/Release/net10.0-android/com.empresa.miapp-Signed.apk

bundletool dump manifest --bundle bin/Release/net10.0-android/com.empresa.miapp-Signed.aab | findstr targetSdkVersion

También puedes preguntarle a MSBuild qué nivel está usando tu proyecto:

dotnet build -f net10.0-android -getProperty:TargetPlatformVersion

Y, por supuesto, Play Console te dice el target API de cada versión en el explorador de app bundles antes de enviarla a revisión.

Qué cambia en tu app al apuntar a Android 16

Subir el número es la parte fácil. Lo importante es que, al apuntar a API 36, tu app empieza a recibir los cambios de comportamiento de Android 16 en dispositivos con Android 16. Estos son los que, a mi parecer, más afectan a una app .NET MAUI:

Cambio en Android 16 ¿Te afecta en MAUI? Qué hacer
Se desactiva la exclusión de edge-to-edge Sí, si usabas windowOptOutEdgeToEdgeEnforcement Quitarla y manejar el área segura
Predictive back activado por defecto Sí, si sobrescribes OnBackPressed Usar los eventos de ciclo de vida de MAUI
Se ignoran orientación y redimensionado en pantallas grandes Sí, si fijas ScreenOrientation Probar en tablets y plegables
BODY_SENSORS → permisos de salud granulares Solo si lees sensores de salud Cambiar el permiso
elegantTextHeight se ignora Rara vez Revisar textos en árabe, tailandés, etc.

Edge-to-edge sin escapatoria

En Android 15 todavía podías poner windowOptOutEdgeToEdgeEnforcement en tu tema para que el contenido no se dibujara detrás de las barras del sistema. En Android 16, si apuntas a API 36, ese atributo está deprecado y desactivado. Quítalo.

La buena noticia es que .NET MAUI 10 ya trabaja en modo edge-to-edge (MauiAppCompatActivity llama a WindowCompat.SetDecorFitsSystemWindows(Window, false)) y trae la propiedad SafeAreaEdges para controlarlo. Ojo con este cambio importante: en .NET 9 un ContentPage en Android evitaba las barras del sistema por defecto, y en .NET 10 el valor por defecto es None. Si quieres el comportamiento anterior:

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="MiApp.Views.InicioPage"
             SafeAreaEdges="Container">
    <!-- El contenido ya no queda detrás de la barra de estado ni de navegación -->
</ContentPage>
Ojo: si usabas WindowSoftInputModeAdjust.Resize para que el teclado empujara el contenido, la documentación recomienda SafeAreaEdges="All" en esa página.

Predictive back: deja de sobrescribir OnBackPressed

Con API 36, Android activa por defecto las animaciones de "predictive back", deja de llamar a onBackPressed y ya no envía KEYCODE_BACK. Si en tu MainActivity tienes algo como esto, en Android 16 simplemente no se va a ejecutar:

// ❌ En Android 16 con target API 36, esto ya no se llama
public override void OnBackPressed()
{
    // lógica personalizada...
    base.OnBackPressed();
}

En .NET MAUI 10, MauiAppCompatActivity registra un callback de predictive back (desde Android 13) que dispara los mismos eventos de ciclo de vida que antes. Así que el camino correcto es usar esos eventos en MauiProgram.cs:

using Microsoft.Maui.LifecycleEvents;

builder.ConfigureLifecycleEvents(events =>
{
#if ANDROID
    events.AddAndroid(android => android.OnBackPressed(activity =>
    {
        // Devuelve true si ya manejaste el "atrás" y no quieres que se propague
        if (MiServicioDeNavegacion.HayModalAbierto)
        {
            MiServicioDeNavegacion.CerrarModal();
            return true;
        }

        return false; // Deja que MAUI haga la navegación normal
    }));
#endif
});

Y si solo necesitas interceptar el botón en una página, Page.OnBackButtonPressed() sigue siendo la opción más sencilla. Si quieres más detalles sobre cómo poner código específico de Android sin ensuciar todo el proyecto, tengo un post sobre código específico de cada plataforma en .NET MAUI.

Importante: existe una exclusión temporal con android:enableOnBackInvokedCallback="false" en <application>, pero úsala solo como parche mientras migras.

Orientación fija en tablets: se acabó

Si tu MainActivity tiene ScreenOrientation = ScreenOrientation.Portrait, presta atención. En pantallas con ancho mínimo de 600 dp (tablets y plegables abiertos), Android 16 ignora screenOrientation, resizeableActivity, minAspectRatio y maxAspectRatio cuando apuntas a API 36. Las excepciones son los juegos y las pantallas pequeñas.

// En un teléfono se respeta; en una tablet con Android 16, se ignora
[Activity(Theme = "@style/Maui.SplashTheme", MainLauncher = true,
          ScreenOrientation = ScreenOrientation.Portrait,
          ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode |
                                 ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize | ConfigChanges.Density)]
public class MainActivity : MauiAppCompatActivity
{
}

Hay una exclusión temporal con una propiedad en el manifiesto:

<application ...>
    <!-- Exclusión TEMPORAL: deja de funcionar al apuntar a API 37 -->
    <property android:name="android.window.PROPERTY_COMPAT_ALLOW_RESTRICTED_RESIZABILITY"
              android:value="true" />
</application>

Pero fíjate en el detalle: según la documentación de Android, esta exclusión no aplica cuando apuntas a API 37, que es justo el valor por defecto de .NET 11. Así que, si vas a migrar a .NET 11 pronto, mejor invierte el tiempo en que tus pantallas funcionen en horizontal.

Permisos: qué revisar

Aquí no hay un gran cambio masivo, pero sí algunos puntos concretos:

✅ Sensores de salud. Si usas BODY_SENSORS (por ejemplo, para leer el ritmo cardíaco), con API 36 se reemplaza por permisos granulares de salud, los mismos de Health Connect. En primer plano se pide READ_HEART_RATE y en segundo plano READ_HEALTH_DATA_IN_BACKGROUND. Además, la app debe declarar una actividad que muestre la política de privacidad, o el permiso se revoca.

<!-- Antes -->
<uses-permission android:name="android.permission.BODY_SENSORS" />
<!-- Con target API 36 -->
<uses-permission android:name="android.permission.health.READ_HEART_RATE" />

✅ Red local. Android 16 introdujo una protección de red local (sockets, NsdManager, descubrimiento de dispositivos) que se está desplegando en fases. Si tu app habla con impresoras, dispositivos IoT o un servidor en la misma red, pruébala ya:

adb shell am compat enable RESTRICT_LOCAL_NETWORK com.empresa.miapp
adb reboot

Según la documentación, el acceso se recupera concediendo NEARBY_WIFI_DEVICES.

✅ Fotos propias en el selector. Si el usuario da acceso limitado a fotos, las imágenes que creó tu app aparecen preseleccionadas, y si las desmarca pierdes el acceso. Rara vez rompe algo, pero conviene saberlo.

✅ Intents más seguros. Android 16 agrega el atributo intentMatchingFlags (por ahora opcional) para que los componentes solo reciban intents que coincidan con sus filtros. Google dice que en el futuro será el comportamiento por defecto, así que vale la pena probar con enforceIntentFilter.

¿Y las librerías nativas?

Un efecto secundario de actualizar es que, de paso, quedas mejor parado con otro requisito de Google Play: el soporte de páginas de 16 KB. Si vas a tocar el proyecto, revisa también ese punto con mi artículo sobre páginas de 16 KB en Android y .NET MAUI.

Qué hacer según la versión de .NET de tu app

¿.NET 10 o directo a .NET 11?

Con .NET 11 a punto de salir, la pregunta es lógica. Mi recomendación:

✅ Ve a .NET 10 si tienes que publicar algo pronto (una corrección, una función comprometida). Es la versión estable con soporte hasta mayo de 2027 y cumple con API 36.

✅ Planea .NET 11 para los próximos meses: te da API 37 por defecto y CoreCLR como runtime, lo que probablemente te deje listo para el requisito de 2027.

❌ Evita saltar directo a .NET 11 con prisa: cambian el runtime, el mínimo de API (24) y los comportamientos de Android 17 a la vez. Son demasiadas variables juntas para una actualización urgente.

Checklist

✅ Tu app está en .NET 10 o superior (net10.0-android → API 36). ✅ Actualizaste Microsoft.Maui.Controls y los NuGet de terceros. ✅ El AndroidManifest.xml no fija un targetSdkVersion viejo (o lo pusiste en 36). ✅ El build no muestra la advertencia XA1006. ✅ Verificaste el targetSdkVersion en el APK o AAB final. ✅ Quitaste windowOptOutEdgeToEdgeEnforcement y revisaste SafeAreaEdges. ✅ No sobrescribes OnBackPressed en MainActivity; usas eventos de ciclo de vida. ✅ Probaste tu app en una tablet o plegable con Android 16. ✅ Cambiaste BODY_SENSORS por permisos de salud (si aplica). ✅ Probaste el acceso a red local (si aplica).

Conclusión

Ya viste:

Mi recomendación personal: no veas esto como un trámite anual de "subir un número". Cada salto de API trae cambios de comportamiento reales, y la forma más tranquila de vivirlos es actualizar cuando sale cada versión de .NET, no cuando Play Console ya te bloqueó. Y deja el nivel de API explícito en el TFM: te ahorras sorpresas.

¿Ya actualizaste tu app a API 36? ¿Te encontraste con algún cambio de comportamiento que no esperabas? Escríbeme en X (Twitter) y cuéntame.

Nos leemos pronto 😊


Referencias:

Compartir
Wilson Vargas
Escrito por

Wilson Vargas

Programador .NET con 8 años de experiencia, apasionado por la tecnología. Con habilidades en el desarrollo de aplicaciones utilizando tecnologías de la plataforma .NET.

Posts relacionados

Comentarios