Firmar APK y AAB para Google Play — Guía completa con errores y soluciones

- Andrés Cruz - EN In english

Firmar APK y AAB para Google Play — Guía completa con errores y soluciones

Cuando llega el momento de llevar una aplicación Flutter a producción en Android, uno de los pasos más importantes —y también más propensos a errores— es generar correctamente un APK o AAB firmado. Google Play exige que cualquier app esté firmada para poder publicarse, y si algo falla en este proceso, la compilación o la subida a la Play Console simplemente no funcionará.

En esta guía te explico paso a paso cómo generar un APK y un AAB firmado en Flutter usando Visual Studio Code o Android Studio, trabajando en Windows (aunque los pasos aplican igualmente para macOS y Linux). Además, veremos errores reales que suelen aparecer durante el proceso y cómo resolverlos.

Veremos varios tips, pasos y consideraciones que debes tener en cuenta al querer generar el release de producción de tu aplicación Flutter usando Visual Studio Code. La APK o AAB que generaremos estará firmada, que es el requisito de Google para poder cargar tu app en la Google Play Store.

Antes vimos cómo generar el launcher, lo cual es fundamental antes de exportar la app a Google Play.

Requisitos previos antes de firmar tu app Flutter

Antes de entrar en comandos y configuraciones, conviene revisar algunos puntos básicos para asegurarte de que el entorno está bien preparado.

Tener Flutter y Android correctamente configurados

Asegúrate de que tu entorno esté bien configurado ejecutando:

$ flutter doctor -v

Aquí podrás verificar:

  • Ruta correcta de Java (JDK)
  • Android SDK instalado
  • Licencias aceptadas (flutter doctor --android-licenses)

Si flutter doctor reporta algún problema, resuélvelo antes de continuar. Un entorno incompleto es la causa raíz de muchos errores de compilación.

Diferencia entre clave de carga y clave de firma (Play App Signing)

Google Play maneja dos claves distintas, y es importante no confundirlas:

  • Clave de carga: es la que tú usas para subir el AAB o APK a Play Console.
  • Clave de firma de la app: la gestiona Google internamente y es la que reciben los usuarios finales en sus dispositivos.

Lo normal y recomendado es activar Play App Signing, de modo que Google custodie la clave de firma final. Así, aunque pierdas tu clave de carga, puedes solicitar a Google una nueva sin perder la app.

Dónde NO guardar el keystore

Nunca subas el archivo .jks a repositorios de código, ya sean públicos o privados. Esto incluye:

  • GitHub
  • GitLab
  • Cualquier repositorio público o privado

Guárdalo en una ubicación segura fuera del proyecto y haz copias de respaldo en al menos dos lugares distintos (por ejemplo, un disco externo y un servicio de almacenamiento cifrado). Si pierdes el keystore sin tener configurado Play App Signing, podrías perder la capacidad de actualizar tu app para siempre.

Crear el keystore (JKS) para firmar tu aplicación Flutter

Estos pasos también aplican si usas Android Studio en lugar de Visual Studio Code. Trabajaremos en Windows, pero el proceso es equivalente en macOS y Linux. Esta es la documentación oficial del equipo de Flutter.

Para generar un APK firmado en modo Release para una aplicación Flutter en Android utilizando Visual Studio Code (VSC), debes seguir estos pasos:

1. Generar el keystore con keytool

  • Abre una terminal en VSC.
  • Ejecuta el siguiente comando para generar una clave (keystore) usando keytool. Puedes hacerlo desde cualquier ubicación de tu sistema:

    keytool -genkey -v -keystore release-key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias key-alias
  • Sigue las instrucciones del asistente para configurar la clave. Es un formulario interactivo que debes completar con datos básicos. Los parámetros release-key.jks y key-alias son personalizables; puedes cambiarlos por los nombres que prefieras.

El asistente se ve así en consola:

C:\Users\andre\flutter
.\keytool.exe -genkey -v -keystore my-release-key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias key-alias
Introduzca la contraseña del almacén de claves:  
Volver a escribir la contraseña nueva:
¿Cuáles son su nombre y su apellido?
[Unknown]:  Tu Nombre 
¿Cuál es el nombre de su unidad de organización?
[Unknown]:  TuEmpresa
¿Cuál es el nombre de su organización?
[Unknown]:  TuEmpresa
¿Cuál es el nombre de su ciudad o localidad?
[Unknown]:  TuCiudad
¿Cuál es el nombre de su estado o provincia?
[Unknown]:  TuEstado
¿Cuál es el código de país de dos letras de la unidad?
[Unknown]:  (EJ VE)
¿Es correcto CN=andres cruz, OU=desarrollolibre, O=desarrollolibre, L=caracas, ST=distrito capital, C=VE?
[no]: si

Si tienes problemas al ejecutar keytool, los tratamos en detalle en el apartado siguiente.

Durante el proceso se te pedirá:

  • Contraseña del almacén (storePassword)
  • Alias (keyAlias)
  • Nombre, organización, ciudad, estado y código de país

En Windows, si tienes problemas de permisos, especifica una ruta absoluta donde tengas permisos de escritura:

keytool -genkey -v -keystore C:\Users\tu_usuario\release-key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias key-alias

2. Crear el archivo key.properties

  • En la carpeta android de tu proyecto Flutter, crea un archivo llamado key.properties.
  • Agrega la siguiente información al archivo:

    storePassword=your_keystore_password
    keyPassword=your_key_password
    keyAlias=my-key-alias
    storeFile=release-key.jks
  • ⚠️ En Windows, si especificas una ruta absoluta en storeFile, recuerda usar doble barra invertida (\\), por ejemplo: storeFile=C:\\Users\\tu_usuario\\release-key.jks.

Asegúrate también de agregar key.properties y *.jks a tu .gitignore para evitar que las contraseñas del keystore queden expuestas en el repositorio.

3. Modificar el archivo build.gradle

  • Abre el archivo android/app/build.gradle.
  • Agrega la siguiente configuración. Las líneas marcadas con + son las que debes añadir:

    +   def keystoreProperties = new Properties()
    +   def keystorePropertiesFile = rootProject.file('key.properties')
    +   if (keystorePropertiesFile.exists()) {
    +       keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
    +   }
    +
       android {
          ...
       }
       
       +   signingConfigs {
    +       release {
    +           keyAlias keystoreProperties['keyAlias']
    +           keyPassword keystoreProperties['keyPassword']
    +           storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
    +           storePassword keystoreProperties['storePassword']
    +       }
    +   }
       buildTypes {
          release {
             // TODO: Add your own signing config for the release build.
             // Signing with the debug keys for now,
             // so `flutter run --release` works.
    -           signingConfig signingConfigs.debug
    +           signingConfig signingConfigs.release
          }
       }

En resumen, estamos indicando al compilador que lea los parámetros del keystore desde key.properties y los use para firmar la app en modo Release, en lugar de usar las claves de debug por defecto.

Error común: alias no encontrado en el keystore

Error típico:

No key with alias 'upload' found in keystore

Esto significa que el valor de keyAlias en key.properties no coincide con el alias que usaste al generar el keystore. Revísalo cuidadosamente, ya que es uno de los errores más frecuentes y fáciles de pasar por alto.

4. Generar el APK o AAB firmado

  • Para generar la app firmada en formato APK, ejecuta:

    flutter build apk
  • Para generar en formato AAB (recomendado para Google Play):

    flutter build appbundle
  • El archivo app-release.apk firmado se generará en la carpeta build/app/outputs/apk/release/ de tu proyecto Flutter. El AAB, por su parte, quedará en build/app/outputs/bundle/release/. Antes de subir a Google Play, te recomiendo instalar la APK en un dispositivo físico para validar que todo funciona correctamente: simplemente cópiala al dispositivo Android y sigue los pasos de instalación manual.

No compartas públicamente el archivo del keystore (release-key.jks). Guárdalo de forma segura y nunca lo incluyas en el repositorio del proyecto.

¡Listo! En este punto ya tienes un APK o AAB firmado en modo Release para tu aplicación Flutter en Android.

Problemas habituales con keytool y cómo solucionarlos

❌ keytool no se reconoce como comando

Esto ocurre porque keytool no está en el PATH del sistema. La solución más rápida es ubicar el ejecutable a través de Flutter:

Ejecuta:

flutter doctor -v

Busca la línea:

Java binary at: C:\Program Files\Android\Android Studio\jbr\bin\java

Navega hasta esa carpeta (sin incluir el archivo java):

cd "C:\Program Files\Android\Android Studio\jbr\bin"

Desde ahí ya puedes ejecutar keytool directamente. Alternativamente, puedes agregar esa ruta a las variables de entorno de tu sistema para no tener que repetir este paso en el futuro.

Problemas al generar la APK firmada

Hay muchas cosas que pueden salir mal al generar tu app firmada. Si al ejecutar keytool recibes un error de acceso denegado como el siguiente:

error de herramienta de claves: java.io.FileNotFoundException: my-release-key.jks (Acceso denegado)
java.io.FileNotFoundException: my-release-key.jks (Acceso denegado)
       at java.base/java.io.FileOutputStream.open0(Native Method)
       at java.base/java.io.FileOutputStream.open(FileOutputStream.java:293)
       at java.base/java.io.FileOutputStream.<init>(FileOutputStream.java:235)
       at java.base/java.io.FileOutputStream.<init>(FileOutputStream.java:123)
       at java.base/sun.security.tools.keytool.Main.doCommands(Main.java:1375)
       at java.base/sun.security.tools.keytool.Main.run(Main.java:423)
       at java.base/sun.security.tools.keytool.Main.main(Main.java:416)

El problema es que estás intentando escribir el archivo .jks en una carpeta sin permisos de escritura (como la carpeta de instalación de Android Studio). La solución es especificar una ruta de tu carpeta de usuario:

keytool -genkey -v -keystore C:\Users\andres\release-key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias key-alias

Con eso, el archivo release-key.jks se generará correctamente en la ubicación especificada.

Antes de generar la APK firmada, también deberías actualizar el namespace de tu app, ya que el identificador por defecto no puede contener example como parte del nombre. Para cambiarlo fácilmente, puedes usar el paquete:

change_app_package_name en pub.dev

Otro error frecuente al compilar es el de alias incorrecto. Si ves algo como esto:

e: C:/Users/andre/.gradle/caches/transforms-3/c1e3cec58f97b65c118bb2f68fab94a8/transformed/jetified-core-ktx-1.10.1/jars/classes.jar!/META-INF/core-ktx_release.kotlin_module: Module was compiled with an incompatible version of Kotlin. The binary version of its metadata is 1.8.0, expected version is 1.6.0.
e: C:/Users/andre/.gradle/caches/transforms-3/a3842a17fe7307c5bcdac869078c73a0/transformed/core-1.10.1/jars/classes.jar!/META-INF/core_release.kotlin_module: Module was compiled with an incompatible version of Kotlin. The binary version of its metadata is 1.8.0, expected version is 1.6.0.        
e: C:/Users/andre/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib/1.8.22/636bf8b320e7627482771bbac9ed7246773c02bd/kotlin-stdlib-1.8.22.jar!/META-INF/kotlin-stdlib-jdk7.kotlin_module: Module was compiled with an incompatible version of Kotlin. The binary version of its metadata is 1.8.0, expected version is 1.6.0.
e: C:/Users/andre/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib/1.8.22/636bf8b320e7627482771bbac9ed7246773c02bd/kotlin-stdlib-1.8.22.jar!/META-INF/kotlin-stdlib.kotlin_module: Module was compiled with an incompatible version of Kotlin. The binary version of its metadata is 1.8.0, expected version is 1.6.0.
e: C:/Users/andre/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib/1.8.22/636bf8b320e7627482771bbac9ed7246773c02bd/kotlin-stdlib-1.8.22.jar!/META-INF/kotlin-stdlib-jdk8.kotlin_module: Module was compiled with an incompatible version of Kotlin. The binary version of its metadata is 1.8.0, expected version is 1.6.0.
e: C:/Users/andre/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-common/1.8.22/1a8e3601703ae14bb58757ea6b2d8e8e5935a586/kotlin-stdlib-common-1.8.22.jar!/META-INF/kotlin-stdlib-common.kotlin_module: Module was compiled with an incompatible version of Kotlin. The binary version of its metadata is 1.8.0, expected version is 1.6.0.
FAILURE: Build failed with an exception.
* What went wrong:
Execution failed for task ':app:packageRelease'.
> A failure occurred while executing com.android.build.gradle.tasks.PackageAndroidArtifact$IncrementalSplitterRunnable
  > com.android.ide.common.signing.KeytoolException: Failed to read key upload from store "C:\Users\andre\flutter\release-key-flutter.jks": No key with alias 'upload' found in keystore C:\Users\andre\flutter\release-key-flutter.jks
* Try:
> Run with --stacktrace option to get the stack trace.
> Run with --info or --debug option to get more log output.
> Run with --scan to get full insights.
> Get more help at https://help.gradle.org
BUILD FAILED in 11s
Running Gradle task 'assembleRelease'...                           12,5s

La clave del error está en: No key with alias 'upload' found in keystore. Significa que el valor de keyAlias en tu key.properties:

storePassword=your_keystore_password
keyPassword=your_key_password
keyAlias=my-key-alias
storeFile=release-key.jks

…no coincide con el alias que especificaste al generar el keystore con keytool. Verifica que ambos sean exactamente iguales, incluyendo mayúsculas y minúsculas.

App abre pero no carga datos

Otro error muy común ocurre cuando la app requiere conexión a internet pero no tiene declarados los permisos correspondientes en AndroidManifest.xml. Asegúrate de incluir lo siguiente antes del bloque <application>:

<manifest xmlns:android="http://schemas.android.com/apk/res/android">
   <uses-permission android:name="android.permission.INTERNET" />
   <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
   <application

Sin estos permisos, toda la data que tu app trae de internet simplemente no cargará en modo Release, aunque en modo debug sí funcione. Esto se debe a que el modo Release puede aplicar restricciones adicionales de red según la configuración de seguridad del manifiesto.

Una vez generada exitosamente, la APK firmada estará disponible en:

build\app\outputs\flutter-apk\app-release.apk

Error de versionamiento en Google Play

Si al intentar subir tu AAB o APK a Google Play ves un error como:

You must use a different version code for your APK or your Android App Bundle because code 1 is already assigned to another APK or Android App Bundle.

Significa que ya existe una versión con ese versionCode en la Play Console. Debes incrementar la versión de tu app en el archivo pubspec.yaml. El formato es version: nombre_version+versionCode:

version: 1.0.4+4

El número después del + es el versionCode que Google Play usa internamente para identificar cada release. Cada subida debe tener un código mayor al anterior.

APK vs AAB en Flutter: cuál usar y cuándo

Por qué Google Play exige AAB

Desde agosto de 2021, Google Play exige el formato AAB (Android App Bundle) para nuevas aplicaciones, principalmente porque:

  • Genera descargas optimizadas para cada dispositivo
  • Produce archivos de menor tamaño comparado con el APK universal
  • Permite mejor distribución por arquitectura de CPU (arm64-v8a, x86_64, etc.)

Si publicas en Google Play: usa siempre AAB (flutter build appbundle).

Cuándo sigue teniendo sentido el APK

  • Pruebas manuales en dispositivos físicos
  • Distribución fuera de Play Store (instalación directa o sideloading)
  • AppGallery de Huawei u otras tiendas alternativas

Error de importaciones en build.gradle.kts (Kotlin DSL)

Si tu proyecto usa el formato .kts (Kotlin DSL) en lugar del Groovy tradicional, necesitas ser más explícito con las importaciones y los tipos. Puede generar errores como estos:

e: file:///C:/Users/andre/Desktop/proy/flutter/mios/aprendeingles/android/app/build.gradle.kts:9:26: Unresolved reference: Properties
e: file:///C:/Users/andre/Desktop/proy/flutter/mios/aprendeingles/android/app/build.gradle.kts:12:29: Unresolved reference: FileInputStream
e: file:///C:/Users/andre/Desktop/proy/flutter/mios/aprendeingles/android/app/build.gradle.kts:26:69: Unresolved reference: it

FAILURE: Build failed with an exception.

* Where:
Build file 'C:\Users\andre\Desktop\proy\flutter\mios\aprendeingles\android\app\build.gradle.kts' line: 9

* What went wrong:
Script compilation errors:

  Line 09: val keystoreProperties = Properties()
                                    ^ Unresolved reference: Properties

  Line 12:     keystoreProperties.load(FileInputStream(keystorePropertiesFile))
                                       ^ Unresolved reference: FileInputStream

  Line 24:             keyAlias = keystoreProperties["keyAlias"] as String
                                                                 ^ No cast needed

  Line 25:             keyPassword = keystoreProperties["keyPassword"] as String
                                                                       ^ No cast needed

  Line 26:             storeFile = keystoreProperties["storeFile"]?.let { file(it) }
                                                                               ^ Unresolved reference: it

  Line 27:             storePassword = keystoreProperties["storePassword"] as String
                                                                           ^ No cast needed

  Line 38:         jvmTarget = JavaVersion.VERSION_17.toString()
                   ^ 'jvmTarget: String' is deprecated. Please migrate to the compilerOptions DSL. More details are here: https://kotl.in/u1r8ln

7 errors

* Try:
> Run with --stacktrace option to get the stack trace.
> Run with --info or --debug option to get more log output.
> Run with --scan to get full insights.
> Get more help at https://help.gradle.org.

La solución es agregar las importaciones necesarias al inicio del archivo android\app\build.gradle.kts:

import java.util.Properties
import java.io.FileInputStream

Con esas dos líneas, Kotlin DSL podrá resolver las referencias a Properties y FileInputStream correctamente.

Subir tu AAB firmado a Google Play Console

  1. Entra a Google Play Console
  2. Ve a la sección Producción o a la pista de pruebas que corresponda (Alfa, Beta, etc.)
  3. Crea un nuevo lanzamiento
  4. Sube el archivo .aab generado
  5. Revisa los errores y advertencias que muestre la consola
  6. Envía a revisión

Recomendaciones finales antes de publicar

  • Guarda el keystore (.jks) en un lugar seguro, fuera del repositorio
  • Haz backup del .jks en al menos dos ubicaciones distintas
  • Prueba siempre en un dispositivo físico real antes de publicar
  • Controla el versionado en pubspec.yaml e incrementa el versionCode en cada release
  • Usa AAB (flutter build appbundle) siempre que sea posible para Google Play
  • Agrega key.properties y *.jks al .gitignore desde el inicio del proyecto

Preguntas frecuentes sobre APK y AAB en Flutter

  • ¿Es obligatorio firmar una app Flutter para publicarla?
    • Sí. Google Play no acepta apps sin firmar bajo ninguna circunstancia.
  • ¿Puedo generar el AAB sin Android Studio?
    • Sí. Con la línea de comandos es suficiente: flutter build appbundle.
  • ¿Qué pasa si pierdo el keystore?
    • Si no tienes Play App Signing activado, podrías perder la posibilidad de actualizar la app en Google Play para siempre. Por eso es crítico respaldarlo desde el primer día.
  • ¿Google Play acepta APK hoy en día?
    • Solo en casos muy específicos (apps heredadas). Para nuevas publicaciones, AAB es el estándar obligatorio desde agosto de 2021.
  • ¿Puedo usar FVM para generar la APK o AAB?

Guía paso a paso para firmar tu app Flutter con keytool, configurar key.properties y build.gradle, generar el APK o AAB listo para Google Play + errores frecuentes.


Únete a la comunidad de desarrolladores que han decidido dejar de picar código y empezar a construir productos reales. Recibe mis mejores trucos de arquitectura cada semana:

Acepto recibir anuncios de interes sobre este Blog.