App móvil
Elegir la tecnología y montar una arquitectura que aguante tres sprints
Ninguna tecnología es obligatoria. Lo que cuenta es que la decisión esté justificada y que el código sea limpio, probado y fácil de cambiar cuando cambien el backend o el servicio del otro equipo.
Las opciones
Android
Kotlin + Jetpack Compose, la opción oficial. Java o Kotlin con Views es el mundo antiguo: lo encontraréis en código heredado.
iOS
Swift + SwiftUI. UIKit es la opción antigua. Hace falta un Mac para compilar y para publicar.
Flutter
Dart y un solo código para Android, iOS, web y escritorio. Muchos paquetes en pub.dev y buena integración con Firebase o Supabase.
React Native
JavaScript o TypeScript con React. Ionic y Cordova son alternativas más basadas en web.
Kotlin Multiplatform
La lógica en Kotlin compartida entre Android e iOS. Con Compose Multiplatform también compartís la UI.
Preguntas clave
- ¿Qué sabe ya el equipo?
- ¿Necesitáis iOS y tenéis Mac?
- ¿Hay librerías para lo que queréis (mapas, login social, chat)?
- ¿Lo justificáis en la memoria?
El mismo modelo en tres lenguajes
data class Station(val id: Int, val name: String, val freeBikes: Int)
// null safety: si no hay estación, un texto por defecto
fun label(s: Station?) = s?.name ?: "Desconocida"
class Station {
final int id;
final String name;
final int freeBikes;
const Station({required this.id, required this.name, required this.freeBikes});
}
String label(Station? s) => s?.name ?? 'Desconocida';
struct Station: Codable {
let id: Int
let name: String
let freeBikes: Int
}
func label(_ s: Station?) -> String { s?.name ?? "Desconocida" }
Dos arquitecturas que separan la UI de la lógica
La regla es la misma: la vista nunca habla directamente con la API. Así podéis probar la lógica sin pantallas y cambiar la fuente de datos (mock o real) sin tocar la UI.
MVVM (Android, iOS)
El binder es el enlace reactivo: StateFlow, LiveData, Combine. La vista observa el estado y se redibuja sola (patrón Observer).
BLoC (Flutter)
Los events entran (un toque, abrir la pantalla), el BLoC los procesa y emite states que la UI pinta.
class StationsViewModel(private val source: StationsDataSource) : ViewModel() {
private val _stations = MutableStateFlow<List<Station>>(emptyList())
val stations: StateFlow<List<Station>> = _stations
init {
viewModelScope.launch { _stations.value = source.getStations().sortedBy { it.name } }
}
}
@Composable
fun StationsScreen(vm: StationsViewModel = koinViewModel()) {
val stations by vm.stations.collectAsStateWithLifecycle()
LazyColumn {
items(stations) { StationRow(it) }
}
}
class StationsBloc {
StationsBloc(this.source);
final StationsDataSource source;
final stations = ValueNotifier<List<Station>>([]);
Future<void> load() async {
final list = await source.getStations();
stations.value = [...list]..sort((a, b) => a.name.compareTo(b.name));
}
}
class StationsPage extends StatelessWidget {
const StationsPage({super.key});
@override
Widget build(BuildContext context) {
final bloc = locator<StationsBloc>();
return ValueListenableBuilder(
valueListenable: bloc.stations,
builder: (context, stations, _) => ListView(
children: [for (final s in stations) StationTile(station: s)],
),
);
}
}
Para proyectos más grandes, el paquete flutter_bloc hace explícitos los events y los states. Para empezar, un ValueNotifier ya separa la lógica de la UI.
Linters: código listo para enseñar
Un linter analiza el código sin ejecutarlo y encuentra errores y problemas de estilo. El objetivo es un código limpio, como un documento que otra persona tendrá que leer. Cuenta para la gestión de la calidad.
- Kotlin: ktlint (estilo) y detekt (problemas de código).
- Swift: SwiftLint.
- Dart:
flutter analyzecon el paqueteflutter_lints, ydart format. - JavaScript o TypeScript: ESLint y Prettier.
- Todo junto: SonarQube o SonarCloud para seguirlo a lo largo del curso.
Un test que el mock hace posible
test('ordena las estaciones por nombre', () async {
final bloc = StationsBloc(FakeStationsDataSource());
await bloc.load();
final names = bloc.stations.value.map((s) => s.name).toList();
expect(names, [...names]..sort());
});
Sin red y en milisegundos. Generad la fuente de datos falsa en el generador y añadid la cobertura a la memoria (flutter test --coverage, Kover o JaCoCo en Kotlin).
Integración continua con GitHub Actions
Cada pull request pasa el linter, los tests y el build antes de fusionarse. Es la gestión de configuraciones que se evalúa. Guardad el fichero en .github/workflows/ci.yml. Alternativas: GitLab CI, Jenkins, Codemagic o Azure DevOps.
name: Android CI
on:
pull_request:
push:
branches: [develop, main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '17'
- name: Lint, tests y build
run: |
chmod +x gradlew
./gradlew lint testDebugUnitTest assembleDebug
# con los plugins de ktlint y detekt: ./gradlew ktlintCheck detekt
name: Flutter CI
on:
pull_request:
push:
branches: [develop, main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: subosito/flutter-action@v2
with:
channel: stable
cache: true
- run: flutter pub get
- run: dart format --output=none --set-exit-if-changed .
- run: flutter analyze
- run: flutter test --coverage
- run: flutter build apk --debug
Hacer llegar la app a profesores y stakeholders
Firebase App Distribution
Enviad cada versión por correo a quien queráis que la pruebe, Android e iOS. La manera más cómoda para PES.
TestFlight
Para iOS. Hace falta el programa de desarrolladores de Apple.
Google Play y App Store
La pista de pruebas interna de Google Play es un buen primer paso. También hay tiendas alternativas como F-Droid.
Para la entrega final, la información de la release tiene que explicar paso a paso cómo ejecutar la app. Un APK en GitHub Releases más un enlace de distribución lo pone muy fácil. Ved el Fag-5 →
Asistentes de IA: sí, pero con ojo
Gemini, Copilot, Claude y similares ayudan a planificar y diseñar (borradores de arquitectura), a generar código repetitivo, a depurar (explicar errores, refactorizar) y a automatizar flujos con agentes.
- Revisad, probad y validad siempre el código generado. Si no lo entendéis, no lo fusionéis.
- Si los usáis para tareas de ingeniería, explicad el proceso en la memoria: es uno de los aspectos metodológicos evaluados.
Ejemplos y recursos
Dos implementaciones de la misma app de estaciones de Bicing, mostradas en directo en la sesión de desarrollo móvil del curso. Fijaos en cómo separan UI, lógica y datos.
BicingAround
Android: Kotlin, Jetpack Compose y MVVM.
bicing-around-flutter
Flutter: Dart y arquitectura BLoC.
Kotlin y Android
- Kotlin Koans
- Android Basics with Compose
- ViewModel (MVVM)
- Retrofit, para las peticiones REST
- The Clean Architecture
Flutter y Dart
- Documentación de Flutter y el cookbook
- Dart y DartPad, para probar código en el navegador
- Comparativa de arquitecturas de Flutter