Una app Flutter razonablemente contenida ronda los 30–50 MB de descarga. Cuando se dispara a 120 o 150, casi siempre es por lo mismo: fuentes e iconos completos que no usas, imágenes sin comprimir y dependencias nativas que nadie ha auditado. La palanca más rentable no es un truco de compilación: es publicar un Android App Bundle y dejar que Google Play entregue a cada dispositivo solo lo que necesita. Lo demás (tree shaking de iconos, WebP, R8) son capas que suman. Antes de tocar nada, mide con flutter build appbundle --analyze-size, porque a veces la respuesta honesta es que a tu app el tamaño le da igual.

Primero decide si esto te importa

No todas las apps pagan el mismo precio por pesar de más. Si tu app es de consumo, se instala en frío desde una ficha de store y compites por cada descarga, el peso te cuesta dinero. Google Play avisa al usuario de que se pase a Wi-Fi por encima de los 200 MB de descarga, y en mercados con datos caros o dispositivos con poco almacenamiento cada MB de más son instalaciones que no ocurren. Ahí optimizar es trabajo comercial, no un capricho de ingeniería.

Si tu app es B2B, se distribuye por MDM o la usa un equipo interno que ya sabe que la quiere, el tamaño es casi irrelevante. Un binario de 90 MB para 300 comerciales de campo no es un problema, y dedicar dos sprints a bajarlo a 60 sí lo es. Mide el impacto real antes de meter horas, porque la optimización prematura de tamaño sale tan cara como cualquier otra.

Mide antes de tocar nada

El error habitual es empezar por el truco que uno leyó en un hilo. Empieza por el mapa:

flutter build appbundle --analyze-size

El comando genera un desglose que abres en la herramienta App Size de DevTools. Verás cuánto ocupa el código Dart compilado, cuánto los assets, cuánto cada fuente y cuánto arrastra cada paquete nativo. Casi siempre hay dos o tres sorpresas: una fuente de 4 MB que entró de la mano de un paquete, un set de imágenes en PNG a 3x que podrían ser WebP, un SDK de analítica que pesa más que media app. Optimiza contra ese desglose y no contra una lista genérica.

Publica App Bundle, no APK

Es la decisión que más ahorra y la que menos esfuerzo cuesta, porque ya deberías estar en ella: el App Bundle es el formato obligatorio en Google Play desde agosto de 2021. Con Dynamic Delivery, Play genera y firma un APK a medida de cada dispositivo, con solo la arquitectura, la densidad de pantalla y el idioma que ese móvil usa. El usuario descarga bastante menos que el .aab que tú subes.

Esto tiene una consecuencia práctica: si publicas en Play, flutter build apk --split-per-abi no te aporta nada. Ese flag tenía sentido cuando repartías APKs a mano; hoy solo lo necesitas si distribuyes fuera de Play, en una tienda alternativa, por descarga directa o por un canal empresarial. En iOS no hay que hacer nada equivalente: App Store aplica app thinning y sirve una variante por dispositivo, así que lo que baja cada usuario es menor que el .ipa que subes a App Store Connect.

Los tres sospechosos habituales

Iconos y fuentes

Flutter hace tree shaking de iconos en release por defecto: si usas Icons.* con referencias constantes, solo empaqueta los glifos que aparecen en el código. Se rompe en cuanto construyes el nombre del icono de forma dinámica, y ahí vuelves a enviar la fuente entera.

El agujero más común son las fuentes de icono de terceros. FontAwesome o un pack propio viajan completos aunque uses cuatro símbolos, porque el tree shaking de Flutter no los toca. Si dependes de uno, subséalo tú y deja solo los glifos que usas. Lo mismo con las fuentes de texto: no empaquetes ocho pesos de una familia para acabar usando Regular y Bold.

Imágenes y assets

Es donde más peso muerto se acumula y donde menos cuesta arreglarlo. Pasa los PNG y las ilustraciones a WebP o AVIF, revisa que no estás enviando la versión 3x de imágenes que nunca se ven a esa densidad, y borra los assets que quedaron en pubspec.yaml de una pantalla que ya no existe. Un assets/ sin auditar durante un año suele esconder varios MB que no pinta nadie.

Dependencias nativas

Cada plugin con código nativo suma peso, y no siempre el que esperas. Un SDK de mapas, uno de vídeo o una librería de ML pueden pesar más que toda tu lógica de negocio, y en el desglose de --analyze-size los verás enseguida. La decisión la toma producto, no ingeniería: ¿esa pantalla que usa el 2% de la gente justifica los 12 MB que añade su dependencia? A veces la mejor optimización es quitar la feature.

Android: R8 y recursos

R8 viene activado en las builds de release y ya reduce el código nativo. Puedes exprimir un poco más añadiendo la reducción de recursos en el bloque release de tu build.gradle:

buildTypes {
    release {
        minifyEnabled true
        shrinkResources true
    }
}

shrinkResources elimina recursos empaquetados que ningún código referencia. El ahorro es modesto al lado de los assets o las fuentes, pero es gratis una vez configurado y no se rompe.

Ofuscación y símbolos de depuración

flutter build appbundle --obfuscate --split-debug-info=build/symbols

Este par de flags está pensado para ofuscar el código Dart y sacar los símbolos de depuración a un directorio aparte, que guardas para desimbolizar los crash reports. El efecto en tamaño es real pero pequeño: no lo actives esperando bajar 20 MB, hazlo porque quieres ofuscación y los símbolos fuera del binario. Y guarda esa carpeta de símbolos en tu CI; el día que tengas un crash en producción sin ella, no sabrás de dónde salió.

Cuando el tamaño es un problema real: deferred components

Si has hecho todo lo anterior y la app sigue siendo grande porque hace muchas cosas de peso (un editor complejo, un motor de mapas offline, un módulo que solo tocan los administradores), el siguiente nivel es partir la app. Los deferred components de Flutter, sobre App Bundle, te dejan definir módulos que se descargan bajo demanda en lugar de en la instalación. El usuario baja el núcleo y trae el resto cuando entra en esa parte.

Es potente y tiene coste. Añade complejidad de build, obliga a manejar en la UI el estado de "todavía no descargado" y solo existe de forma nativa en Android; en iOS lo más cercano son los On-Demand Resources, y solo para assets, no para código Dart. Déjalo para el final: es la herramienta de cuando el problema ya no es limpieza sino arquitectura, y tienes un módulo grande y bien delimitado que la mayoría de usuarios no llega a abrir.

Por dónde empezar

PalancaQué ahorraEsfuerzoCuándo
Publicar App BundleMucho (entrega por dispositivo)Ninguno si ya estás en PlaySiempre
Comprimir y limpiar assetsAltoBajoSiempre
Subsetear fuentes de iconoMedioBajoSi usas packs de terceros
R8 + shrinkResourcesBajoBajo, una vezAndroid, siempre
Auditar dependencias nativasVariable, a veces altoMedio (decisión de producto)Cuando el desglose lo señala
Deferred componentsAlto en apps grandesAltoSolo si lo anterior no bastó

El orden que seguimos nosotros es pragmático: medir, atacar assets y fuentes, confirmar que publicas App Bundle con R8, auditar dependencias contra el desglose y, solo si después de todo eso la app sigue por encima de lo que su público tolera, plantear deferred components. Cada capa que añades es también una capa de complejidad en tu build y tu CI, así que para cuando el siguiente MB cueste más de lo que vale.

Si estás peleándote con el tamaño de una app Flutter en producción, o quieres una segunda opinión antes de meter deferred components en un proyecto que quizá no los necesita, en Dribba llevamos apps Flutter a store desde hace años: somos Flutter Partner oficial de Google desde 2017, la única agencia española en su directorio. Escríbenos y lo miramos con tus datos, no con recetas de hilo.