📃 Índice
- ⏭️ Introducción
- 🎨 Creación de Flavors
- 🚀 Launch.json
- 🧩 Assets y Variables de aplicación
- 📱 Launcher Icon
- 🖼️ Splash Screen
- 🔥 Configuración de Firebase
- 🆔 Package Name / BundleId
- 🏷️ Nombre de Aplicación
- 🔢 Número de Versión
- 🔑 Cuenta de AppleID y Team
- 🛠️ Ejecución y Compilación
- 📫 Contacto
⏭️ Introducción
En proyectos de desarrollo con múltiples clientes o entornos, es esencial contar con una estructura que permita manejar configuraciones diferenciadas sin duplicar el código base. En Flutter, esto se logra mediante Flavors, una técnica que facilita la administración de distintas versiones de una misma aplicación, ya sea para entornos de desarrollo (development, staging, production) o para clientes específicos.
Esta guía es aplicable a cualquier proyecto Flutter que busque implementar flavors, tanto para entornos de desarrollo internos como para productos entregables a distintos clientes.
Empecemos.
🎨 Creación de Flavors
Dentro de Flutter, los flavors se configuran principalmente en los archivos de plataforma (iOS y Android).
Android
Abre el archivo
android/app/build.gradle
.Si has creado el proyecto con very_good_cli (opción que recomiendo), verás una sección donde ya están definidos los productFlavors (
development
,production
,staging
). Si no lo están, crea dicha sección dentro de android { … }.Aquí configuraremos los flavors específicos para cada cliente y/o entorno.
ℹ️ A lo largo de esta guía utilizaremos “cliente1” y “cliente2” como ejemplos de clientes, y “Prueba Flavors” como nombre del proyecto.
Sustitúyelos por el nombre real de tu aplicación o de cada cliente según corresponda.
Modifica la sección productFlavors
como sigue:
android {
...
flavorDimensions "default"
productFlavors {
production {
dimension "default"
applicationIdSuffix ""
manifestPlaceholders = [appName: "Prueba Flavors"]
versionName "1.0.0"
versionCode 1
}
staging {
dimension "default"
applicationIdSuffix ".stg"
manifestPlaceholders = [appName: "[STG] Prueba Flavors"]
versionName "1.0.0"
versionCode 1
}
development {
dimension "default"
applicationIdSuffix ".dev"
manifestPlaceholders = [appName: "[DEV] Prueba Flavors"]
versionName "1.0.0"
versionCode 1
}
cliente1 {
dimension "default"
applicationIdSuffix ".cliente1"
manifestPlaceholders = [appName: "Cliente 1 App"]
versionName "1.0.0"
versionCode 1
}
cliente2 {
dimension "default"
applicationIdSuffix ".cliente2"
manifestPlaceholders = [appName: "Cliente 2 App"]
versionName "1.0.0"
versionCode 1
}
}
...
}
IOS
En iOS, trabajamos dentro de Xcode para configurar los Schemes y los Configurations. Vamos a crear los esquemas y configuraciones adicionales para cliente1 y cliente2.
Abre Xcode y abre el proyecto
ios/Runner.xcworkspace
.Duplicar las configuraciones de build:
En Xcode, selecciona el proyecto
Runner
en el panel de la izquierda.Ve a la pestaña "Info" y verás las configuraciones de build existentes para
Debug
,Release
yProfile
.Duplicaremos estas configuraciones para
cliente1
ycliente2
. Haz clic en el icono de + de la parte inferior del menú de Configurations y duplicaDebug
,Release
yProfile
.Nombra las nuevas configuraciones como
Debug-cliente1
,Release-cliente1
,Profile-cliente1
, y así con cada cliente / flavor.
- Crear esquemas para los nuevos flavors:
Ve al menú de esquemas en la parte superior de Xcode y selecciona "Manage Schemes".
Duplicamos los esquemas existentes (como
development
) para los clientes. Nombra estos esquemascliente1
ycliente2
.Edita cada esquema y asegúrate de que esté asociado con la configuración de build correcta (por ejemplo, para el esquema
cliente1
, usaDebug-cliente1
para Debug yRelease-cliente1
para Release).
🚀 Launch.json
El archivo .vscode/launch.json
permite ejecutar y depurar tu app con diferentes flavors en VSCode. A continuación, se muestra un extracto de la configuración para el flavor cliente1 en modo debug.
{
"name": "Launch cliente1 (debug)",
"request": "launch",
"type": "dart",
"program": "lib/main_cliente1.dart",
"flutterMode": "debug",
"args": [
"--flavor",
"cliente1",
"--target",
"lib/main_cliente1.dart"
]
},
Configura los demás flavors y modos (debug, release, profile) según las necesidades de tu proyecto.
Ahora, si usas VSCode, puedes ejecutar y depurar cada flavor en un modo específico desde la pestaña Run and Debug:
🧩 Assets y Variables de aplicación
Cada flavor podrá tener variables específicas (por ejemplo, el nombre de la app, el nombre de la empresa, la url base de tu api, y más) así como assets propios (como los logos, entre otros).
Para resolver esto, crearemos un archivo dart de configuración que cargue las variables y rutas de assets correspondientes dependiendo del flavor. Estas variables serán usadas dentro del código de la aplicación.
Primero, crea un archivo lib/core/clients_config/config.dart
, tal que:
// ignore_for_file: no_default_cases
enum Flavor { development, production, staging, cliente1, cliente2 }
class Config {
static Flavor appFlavor = Flavor.development;
//
// APP VARIABLES
//
static String get appName {
switch (appFlavor) {
case Flavor.cliente1:
return 'Cliente 1 App';
case Flavor.cliente2:
return 'Cliente 2 App';
case Flavor.development:
return 'Development App';
case Flavor.production:
return 'Production App';
case Flavor.staging:
return 'Staging App';
}
}
static String get companyName {
switch (appFlavor) {
case Flavor.cliente1:
return 'Cliente 1 Company';
case Flavor.cliente2:
return 'Cliente 2 Company';
default:
return 'Default Company';
}
}
//
// APP ASSETS
//
static String get logoAsset {
switch (appFlavor) {
case Flavor.cliente1:
return 'assets/cliente1/logos/logo_cliente1.png';
case Flavor.cliente2:
return 'assets/cliente2/logos/logo_cliente2.png';
default:
return 'assets/common/logos/logo_default.png';
}
}
}
ℹ️ Para los assets puedes crear la jerarquía de carpetas que prefieras. Sin embargo, la recomendación es crear carpetas separadas para cada flavor / cliente, manteniendo así esta diferenciación en todos los ámbitos del proyecto.
Asegúrate de hacer referencia a las rutas de tus assets en el archivo pubspec.yaml:
flutter:
assets:
# common assets
- assets/common/logos/
# cliente1
- assets/cliente1/logos/
# cliente2
- assets/cliente2/logos/
A continuación, en cada archivo main_<flavor>.dart
, inicializa el flavor correcto:
import 'package:prueba_flavors/bootstrap.dart';
import 'package:prueba_flavors/clients_config/config.dart';
import 'package:prueba_flavors/src/features/app/app.dart';
void main() {
Config.appFlavor = Flavor.cliente1; // Inicialización del flavor
bootstrap(() => const App());
}
Finalmente, utiliza las variables de tu archivo Config dentro de la aplicación.
import 'package:prueba_flavors/clients_config/config.dart';
return Scaffold(
appBar: AppBar(
title: Text(Config.appName), // Usamos la variable appName
),
💡 Esto no es una idea fija, aprovecha el concepto que acabamos de ver a tu favor creando más archivos Config si así lo deseas. Por ejemplo, podrías tener uno para las variables y otro para los assets, en caso de que tu proyecto tenga muchos recursos diferentes para cada cliente.
📱 Launcher Icon
Para la configuración de un launcher icon por cada flavor, vamos a utilizar el paquete flutter_launcher_icons
. Para ello, necesitamos realizar una serie de configuraciones previas.
Para empezar, deberemos crear los archivos flutter_launcher_icons-<flavor>.yaml
en la raíz del proyecto. Dentro de cada uno de estos, deberemos estructurar las opciones del paquete según nos interese. En este caso, como queremos configurar los iconos tanto para iOS como Android, el archivo tendría el siguiente aspecto.
flutter_launcher_icons:
android: true
ios: true
image_path_android: 'assets/development/launcher_icon/android_launcher_icon.png'
image_path_ios: 'assets/development/launcher_icon/ios_launcher_icon.jpg'
En este caso, cuando indicamos las variables android
e iOS
como true, estamos indicando que queremos que se sobreescriba el icono que Flutter nos instaura por defecto para estas plataformas, mientras que image_path_android
e image_path_ios
nos llevan al lugar donde tenemos guardada la imagen que queremos convertir en nuestro icono.
Por otro lado, también existe la posibilidad de tener una única imagen para icono, no diferenciando entre android e iOS. Para construir un archivo con estas características, deberíamos editar ligeramente nuestro código.
flutter_launcher_icons:
android: true
ios: true
image_path: 'assets/development/launcher_icon/android_launcher_icon.png'
remove_alpha_ios: true
Como podemos ver, hemos sustituido image_path_android
e image_path_ios
por image_path
, donde indicamos el mismo archivo con transparencias (png) que asignábamos anteriormente a image_path_android
. Además, hemos añadido una nueva variable remove_alpha_ios
, la cual marcamos como true.
Esto se debe a que los iconos para iOS
no deben tener transparencia, por lo que con esta configuración conseguiríamos quitarle esa propiedad a la imagen del image_path
.
Una vez se tienen todos los archivos debidamente configurados, ya podemos utilizar el siguiente comando para la creación de todos los iconos en sus correspondientes tamaños.
dart run flutter_launcher_icons -f flutter_launcher_icons*
Con este comando, no debería ser necesario hacer ninguna configuración extra para que los iconos estén listos para Android. Se pueden comprobar que todas las imágenes han sido creadas en la ruta android/app/src/<flavor>/res
en cada una de las carpetas mipmap
.
Sin embargo, para terminar la configuración en iOS hace falta entrar en Xcode y modificar la variable Primary App Icon Set Name
. Para ello, entramos en Runner → Build Setting
con las pestañas All
y Combined
seleccionadas y buscamos el término Asset. Con esto, debería aparecernos una sección llamada Asset Catalog Compiler - Options, donde encontraremos la variable anteriormente citada. Esta variable nos aparecerá desglosada por nuestros flavors, pero con el mismo valor en todos ellos. La modificación que debemos hacer es adaptar el valor a cada uno de estos flavors, como en la siguiente imagen.
Con esto debería ser suficiente para tener todos nuestros iconos modificados, tanto para Android como para iOS.
🖼️ Splash Screen
Para la gestión de la splash_screen por cada flavor hemos utilizado el paquete flutter_native_splash
.
Primero, en la raíz del proyecto, debemos crear un archivo flutter_native_splash-<flavor>.yaml
por cada flavor. Cada uno de esos archivos debe contener lo siguiente:
flutter_native_splash:
android: true
ios: true
color: "#ffffff"
image: "assets/common/splash_icon/splash_icon.png"
En este ejemplo, colocamos el color del background que queremos en la variable color y la imagen deseada en la variable image.
Una vez creados todos los archivos pertinentes, ejecutamos el comando para crear todas las splash images necesarias.
dart run flutter_native_splash:create --flavors <flavor1>,<flavor2>,<flavorN>
Para Android no sería necesario hacer nada más, pero para iOS necesitamos hacer un par de configuraciones:
Entra en la ruta
ios/Runner/Base.lproj
y selecciona los nuevos .storyboard que se hayan creado a raíz de tus flavors. Por ejemplo, si tienes como flavors development, staging y production, se te habrán creado los archivosLaunchScreenDevelopment.storyboard
,LaunchScreenStaging.storyboard
yLaunchScreenProduction.storyboard
.-
Abre el proyecto en XCode y pega estos archivos a la misma altura que los otros .storyboard que ya tengas por defecto.
Después de esto haz clic en el proyecto Runner (normalmente arriba del todo en la parte izquierda). A continuación, a la izquierda del centro de la pantalla, haz clic en Runner en el apartado de Targets.
Después, en la parte alta selecciona ‘Build Settings’, y asegúrate de tener seleccionadas las pestañas ‘All’ y ‘Combined’.
Al lado de estas pestañas se encuentra el botón ‘+’, púlsalo y elige la opción ‘Add User-Defined Setting’. Esto hará que Xcode cree una nueva variable para su uso.
Cambia el nombre a esta variable por el de
LAUNCH_SCREEN_STORYBOARD
(nombre sugerido).-
Una vez que hagas esto, tendrás que asignar a cada Flavor un nombre concreto de variable, todos a raíz del nombre que tengan los archivos .storyboard que hemos añadido al proyecto en pasos anteriores (sin añadir la extensión .storyboard). La variable se debería de quedar configurada de una forma similar a la siguiente:
Después de este paso, hay que modificar nuestro archivo
Info.plist
. Entra en el archivo (lo verás dentro de la carpeta Runner, en la columna izquierda de XCode) y busca la variable ‘Launch screen interface file base name’.El valor por defecto será 'LaunchScreen'. Cámbialo por la variable que hemos creado antes. Si has seguido los pasos exactamente, el nombre de la variable será LAUNCH_SCREEN_STORYBOARD, por lo que deberás poner $(LAUNCH_SCREEN_STORYBOARD).
¡Listo! Esto sería todo lo que tienes que hacer para configurar tu SplashScreen correctamente tanto en Android como en iOS.
ℹ️ Todo este apartado ha sido generado como una versión traducida del tutorial que ofrece la propia página del paquete en pub.dev en su apartado 'Flavor Support'. Si esta ayuda te resulta confusa en algún punto o ha quedado desfasada, recomiendo visitar la entrada original: https://pub.dev/packages/flutter_native_splash.
🔥 Configuración de Firebase
Si utilizas Firebase en tu proyecto Flutter, requerirás de varios archivos de configuración, los cuales pueden ser distintos para cada flavor. A continuación vamos a ver dónde guardar y utilizar dichos archivos.
💡 Para más información sobre cómo implementar Firebase con Flutter Flavors, echa un vistazo a este artículo:
https://codewithandrea.com/articles/flutter-flavors-for-firebase-apps
firebase_options
Dentro de lib/src/core/firebase/firebase_options/
(normalmente esta carpeta se crea automáticamente al generar los firebase_options) guarda los archivos firebase_options_<flavor>.dart
que hayas generado.
Para usar estos archivos, añade en cada main_<flavor>.dart
el siguiente código, asegurándote de importar el archivo firebase_options correspondiente.
await Firebase.initializeApp(
name: 'distribucion-app-dev',
options: DefaultFirebaseOptions.currentPlatform,
);
Por ejemplo, para el flavor development, añadiremos este código en main_development.dart e importaremos 'package:/…/firebase_options/firebase_options_development.dart'.
Google_services.json
Dentro de android/app/src/
crea una carpeta para cada flavor (si no están ya creadas) y guarda ahí los archivos google_services.json de cada uno.
android/
app/
src/
cliente1/
google_services.json
cliente2/
google_services.json
Google_service-info.plist
Primero, dentro de ios/Runner/
crea una carpeta config/
y dentro crea una carpeta para cada flavor, guarda ahí los archivos google-service-info.plist de cada uno.
ios/
Runner/
config/
cliente1/
google_services-info.plist
cliente2/
google_services-info.plist
A continuación, debemos asignar a cada flavor su archivo google_service-info.plist correspondiente. Esto podemos hacerlo manualmente desde la pestaña BuildSettings
de XCode, sin embargo, se puede volver algo tedioso hacerlo con cada nuevo cliente, por lo que vamos a automatizar este proceso de la siguiente manera:
- Abre el proyecto en XCode.
Navega hasta Targets → Runner → Pestaña
BuildPhases
.Haz clic en el botón “+” en la parte superior izquierda de la lista de BuildPhases y selecciona
New Run Script Phase
.Copia y pega el siguiente script:
#!/bin/sh
# Nombre del archivo plist que estamos buscando
GOOGLESERVICE_INFO_PLIST=GoogleService-Info.plist
# Determinar el flavor (esquema) actual basado en el nombre de la configuración
# El $CONFIGURATION variable puede tener valores como Debug-cliente1, Debug-prod, etc.
if [[ $CONFIGURATION =~ -([^-]*)$ ]]; then
environment=${BASH_REMATCH[1]}
fi
echo "Environment: $environment"
# Rutas de los archivos GoogleService-Info.plist
GOOGLESERVICE_INFO_FILE="${PROJECT_DIR}/Runner/config/${environment}/${GOOGLESERVICE_INFO_PLIST}"
# Asegurarse de que el archivo GoogleService-Info.plist existe
if [ ! -f "$GOOGLESERVICE_INFO_FILE" ]; then
echo "Error: No se encontró el archivo GoogleService-Info.plist para el environment ${environment} en ${GOOGLESERVICE_INFO_FILE}."
exit 1
fi
# Copiar el archivo GoogleService-Info.plist a la ubicación esperada por Firebase
PLIST_DESTINATION="${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app"
echo "Copiando ${GOOGLESERVICE_INFO_PLIST} al destino: ${PLIST_DESTINATION}"
cp "${GOOGLESERVICE_INFO_FILE}" "${PLIST_DESTINATION}"
Este script seleccionará automáticamente el archivo GoogleService-Info.plist correcto dependiendo del flavor.
- En el campo Shell selecciona /bin/sh.
- Asegúrate de que el script esté en la parte superior de la lista de BuildPhases, justo después de “Target Dependencies”.
- Elimina el GoogleService-Info.plist original del proyecto (si lo hay), alojado en la carpeta ios/Runner, ya que ahora usaremos diferentes archivos según el flavor.
🆔 Package Name / BundleId
Android
Para modificar el packageName de Android, usaremos el paquete change_app_package_name.
Primero añade el paquete en tu pubspec.yaml y usa el comando para renombrar el packageName en Android. Para ello, ejecuta:
flutter pub add -d change_app_package_name
flutter pub get
dart run change_app_package_name:main com.new.package.name --android
Sustituye com.new.package.name por el nombre que desees. Este nombre será el packageName base del proyecto.
A continuación, para que cada flavor tenga su propio bundleID, vamos a añadirles un prefijo personalizado. Abre tu archivo build.gradle
dentro de android/app
, y modifica la variable applicationIdSuffix para cada flavor dentro del productFlavors.
Aquí un extracto del productFlavors modificado:
productFlavors {
cliente1 {
...
applicationIdSuffix ".cliente1"
}
cliente2 {
...
applicationIdSuffix ".cliente2"
}
...
}
De esta manera, si nuestro packageName base es com.pruebaflavors.app, el packageName para el flavor cliente1 será com.pruebaflavors.app.cliente1.
IOS
En IOS, esta operación debe realizarse manualmente, ya que el Bundle Identifier está muy vinculado a la configuración del proyecto en Xcode. Lo haremos de la siguiente manera:
- Abre el proyecto en XCode.
- Navega hasta Targets → Runner → Pestaña
BuildSettings
. - En el buscador de arriba a la derecha, busca “Product Bundle Identifier”.
- Modifica esta variable para cada configuración de flavor.
Ejemplo de configuración del Product Bundle Identifier por flavors:
Por último, asegúrate de que la variable PRODUCT_BUNDLE_IDENTIFIER está definida en tu info.plist
tal que:
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
🏷️ Nombre de Aplicación
El nombre de aplicación es el nombre que aparece en la pantalla de inicio, en las stores, en las configuraciones de aplicación del dispositivo, entre otros. Es posible adaptar un nombre de aplicación para cada flavor.
Android
Primero, debemos añadir la variable appName al archivo android/app/src/main/AndroidManifest.xml
, de la siguiente manera:
...
<application
android:label="${appName}"
...
</application>
Ahora, en tu archivo android/app/build.gradle
, asigna diferentes nombres de aplicación (appName) para cada flavor dentro de la configuración de productFlavors.
productFlavors {
cliente1 {
...
manifestPlaceholders = [appName: "Cliente 1 App"]
}
cliente2 {
...
manifestPlaceholders = [appName: "Cliente 2 App"]
}
...
}
IOS
Para IOS lo haremos utilizando el archivo info.plist
, mediante la variable FLAVOR_APP_NAME.
Primero, asegúrate de que esté definida en el info.plist que se encuentra en ios/Runner de la siguiente manera:
<key>CFBundleDisplayName</key>
<string>$(FLAVOR_APP_NAME)</string>
<key>CFBundleName</key>
<string>$(FLAVOR_APP_NAME)</string>
A continuación,
- Abre el proyecto en XCode.
- Navega hasta Targets → Runner → Pestaña
BuildSettings
. - En el buscador de arriba a la derecha, busca “FLAVOR_APP_NAME”.
- Modifica esta variable para cada configuración de flavor.
Ejemplo de configuración del FLAVOR_APP_NAME por flavors:
🔢 Número de Versión
Android
Para Android, en tu archivo build.gradle
dentro de android/app
, puedes asignar diferentes versionCode y versionName para cada flavor dentro de la configuración de productFlavors.
Aquí un extracto del productFlavors modificado:
productFlavors {
cliente1 {
...
versionName "1.0.0"
versionCode 1
}
cliente2 {
...
versionName "1.0.0"
versionCode 1
}
...
}
ℹ️ VersionName es la versión que los usuarios verán en Google Play Store.
VersionCode es el número de versión que se usa internamente para saber si una versión es más nueva que otra. Debe incrementarse con cada nueva versión que subas a la Play Store.
IOS
Para IOS lo haremos utilizando el archivo info.plist
, mediante las variables FLUTTER_BUILD_NAME y FLUTTER_BUILD_NUMBER.
Primero, asegúrate de que estén definidas en el info.plist que se encuentra en ios/Runner de la siguiente manera:
<key>CFBundleShortVersionString</key>
<string>$(FLUTTER_BUILD_NAME)</string>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
A continuación,
- Abre el proyecto en XCode.
- Navega hasta Targets → Runner → Pestaña
BuildSettings
. - En el buscador de arriba a la derecha, busca “Versioning”.
- Modifica los valores de Current Project Version y Marketing Version para cada Flavor.
ℹ️ VersionName es la versión que los usuarios verán en Google Play Store.
Marketing Version es la versión que los usuarios verán en Apple Store. Corresponde a la variable FLUTTER_BUILD_NAME del Info.plist, y a la variable VersionName de la configuración de Android.Current Project Version es el número de versión que se usa internamente para saber si una versión es más nueva que otra. Debe incrementarse con cada nueva versión que subas al Apple Store. Corresponde a la variable FLUTTER_BUILD_NUMBER del Info.plist, y a la variable VersionCode de la configuración de Android.
🔑 Cuenta de AppleID y Team
Cada flavor en tu proyecto de Flutter puede tener diferentes configuraciones de AppleID, Team de Apple, y otros detalles relacionados con la firma y distribución de la app (como perfiles de aprovisionamiento y certificados).
Abre el proyecto en XCode.
Navega hasta Targets → Runner → Pestaña Signing&Capabilities.
Aquí podrás ver una sección de configuración por cada flavor que hayas creado, lo único que debes hacer es modificar el apartado “Team”:
Recuerda configurar un Bundle Identifier distinto para cada flavor, como se explica más arriba en este documento, ya que las Stores no permiten tener dos identificadores iguales para distintas aplicaciones.
🛠️ Ejecución y Compilación
Ejecución
Utiliza el siguiente comando en tu terminal, reemplazando <flavor>
por development
, staging
,production
, cliente1
o cliente2
y <mode>
por debug
, profile
, o release
según sea tus necesidades:
```text
flutter run --flavor <flavor> --target lib/main_<flavor>.dart --<mode>
```
Ahora deberías ser capaz de ver la aplicación corriendo en tu dispositivo o emulador.
ℹ️ Alternativamente, si estás utilizando Visual Studio Code, puedes seleccionar y ejecutar cada configuración desde el menú de "Run and Debug" basado en el archivo
launch.json
(Ver sección Launch.json).
Compilación
Utiliza el siguiente comando en tu terminal para generar una APK de Android del proyecto, reemplazando <flavor>
por development
, staging
,production
, cliente1
o cliente2
y <mode>
por debug
, profile
, o release
según sea tus necesidades:
flutter build apk --flavor <flavor> --target lib/main_<flavor>.dart --<mode>
Los archivos generados se encuentran en build/app/outputs/apk/<flavor>/<mode>
🎉 ¡Fin! ¡Felicidades por llegar hasta aquí!
📫 Contacto
👋 Artículo escrito por David González Íñiguez
💌 davidgab08@gmail.com
🔗 linkedin.com/in/davidgonzaleziniguez
🧡 Si este post te ha sido útil: deja una reacción
🔖 Guárdalo en tu Reading List
💬 Comparte dudas o feedback en los comentarios.
👨💻 Abierto a colaboraciones y oportunidades relacionadas con Flutter.
Escríbeme por email o conéctame en LinkedIn.
Top comments (0)