MFormations
Modern Mobile Engineering

Chapitre 19

19 - Exercices pratiques

19 - Exercices pratiques

Chapitre 19 — 40 exercices pratiques

Objectif : appliquer l'ensemble des concepts des chapitres 02 à 18 sur 40 exercices progressifs, couvrant Kotlin, Compose, Swift, SwiftUI, React Native, Flutter, l'architecture MVVM, la persistance, le networking, les tests, la performance et la sécurité.

Comment utiliser ce chapitre

  1. Chaque exercice est indépendant mais les sections 7 à 12 réutilisent les acquis des sections 1 à 6.
  2. Un niveau est indiqué : ⭐ débutant, ⭐⭐ intermédiaire, ⭐⭐⭐ avancé.
  3. Les livrables sont précisés pour chaque exercice ; le chapitre 21 donne les corrigés détaillés.
  4. Respectez les contraintes (interdictions, limites) : elles représentent des exigences réalistes de production.

Section 1 — Kotlin & Coroutines (Exercices 1 à 4)

Exercice 1 — Les bases du langage Kotlin (⭐)

Objectifs : réviser la syntaxe Kotlin : classes, data classes, null-safety, extension functions.

Contexte : une application de suivi de colis. Un colis possède un identifiant, un statut (En attente, En transit, Livré), une adresse de destination et une date estimée de livraison.

Consignes :

  1. Créer une data class Parcel avec les propriétés ci-dessus.
  2. Créer une enum class ParcelStatus avec une propriété label en français.
  3. Ajouter une extension function Parcel.isDelayed(today: LocalDate): Boolean.
  4. Écrire une fonction updateStatus(parcel: Parcel?, status: ParcelStatus) qui gère le null proprement et retourne le colis mis à jour.

Contraintes :

  • Toutes les propriétés non optionnelles doivent être non-null.
  • Interdiction d'utiliser !!.
  • Le code doit compiler sans warning.

Livrables : un fichier parcel.kt + 3 tests unitaires (JUnit).

Exercice 2 — Coroutines : parallélisme contrôlé (⭐⭐)

Objectifs : utiliser launch, async, withContext, Dispatchers et la structure supervisorScope.

Contexte : un écran d'accueil doit charger 3 sources en parallèle : profil utilisateur, notifications, météo. Une source en échec ne doit pas bloquer les autres.

Consignes :

  1. Créer 3 fonctions suspend fun simulant des appels réseau (délai + résultat).
  2. Les lancer en parallèle avec async dans un supervisorScope.
  3. Si la météo échoue, l'écran affiche les 2 autres résultats et un message d'erreur.
  4. Ajouter un timeout de 3 secondes sur l'ensemble (avec withTimeoutOrNull).

Contraintes :

  • Interdiction d'utiliser runBlocking dans le code de production.
  • Les coroutines doivent être annulées proprement si l'écran est fermé.

Livrables : un fichier HomeRepository.kt + tests avec kotlinx.coroutines.test.

Exercice 3 — Flow : flux de données réactifs (⭐⭐)

Objectifs : créer, transformer et collecter un Flow ; gérer les erreurs avec catch ; combiner deux flux.

Contexte : une recherche de produits en temps réel. Chaque frappe utilisateur déclenche un flux de requêtes ; seuls les résultats de la dernière requête doivent être affichés (anti-tremblement).

Consignes :

  1. Créer un Flow<String> de requêtes de recherche (intervalles de 300 ms entre chaque frappe).
  2. Appliquer debounce(300), distinctUntilChanged() et flatMapLatest { searchProduct(it) }.
  3. Gérer les erreurs réseau avec catch et émettre un état RechercheEnEchec.
  4. Exposer le résultat dans un StateFlow<SearchState>.

Contraintes :

  • Le StateFlow doit être exposé en lecture seule (asStateFlow()).
  • Aucune fuite mémoire : annulation à la fin du scope.

Livrables : un SearchViewModel complet + tests de Flow.

Exercice 4 — Structure de données : tri et recherche (⭐)

Objectifs : implémenter un tri et une recherche sans bibliothèque externe ; comparer les complexités.

Contexte : une liste de 10 000 clients doit être triée par nom puis recherchée par identifiant.

Consignes :

  1. Implémenter un tri par fusion (mergeSort) générique sur List<T>.
  2. Implémenter une recherche binaire sur une liste triée.
  3. Mesurer le temps d'exécution des deux algorithmes sur 10 000 éléments.
  4. Comparer avec sorted() de la stdlib et justifier les écarts.

Livrables : un fichier Algorithms.kt + un rapport comparatif en Markdown.


Section 2 — Jetpack Compose (Exercices 5 à 8)

Exercice 5 — Première UI Compose : écran de connexion (⭐)

Objectifs : utiliser @Composable, les TextField, Button, Card, l'état avec remember et les thèmes Material 3.

Consignes :

  1. Créer un écran de connexion : champ email, champ mot de passe (masqué), bouton « Se connecter ».
  2. Valider les champs en temps réel : email conforme, mot de passe ≥ 8 caractères.
  3. Afficher les erreurs sous les champs (textes Material).
  4. Désactiver le bouton tant que les champs sont invalides.
  5. Ajouter un Lottie ou une CircularProgressIndicator pendant la connexion simulée.

Contraintes : uniquement Material 3, préview @Preview incluse, aucun View système.

Livrables : LoginScreen.kt + tests de la validation avec compose-ui-test.

Exercice 6 — État et hoisting : compteur d'articles (⭐)

Objectifs : comprendre le state hoisting, mutableStateOf et la séparation état/UI.

Contexte : un panier affiche une liste d'articles, chacun avec un bouton « + / − » et un total en bas.

Consignes :

  1. Créer un composable ArticleRow stateless (état remonté).
  2. Créer un composable CartScreen qui détient l'état (liste + quantités).
  3. Le total est recalculé automatiquement à chaque changement.
  4. Ajouter un bouton « Vider le panier » avec confirmation.

Contraintes : pas de ViewModel pour cet exercice (réservé à l'exercice 21), uniquement remember.

Livrables : CartScreen.kt + tests de UI sur le total.

Exercice 7 — Liste + LazyColumn avec état dérivé (⭐⭐)

Objectifs : LazyColumn, rememberLazyListState, clés stables, animateItem et recherche.

Contexte : une liste de 500 contacts avec recherche par nom et un en-tête de défilement (bouton retour en haut).

Consignes :

  1. Afficher les contacts dans une LazyColumn avec clés stables (key = { contact.id }).
  2. Filtrer la liste selon la requête de recherche.
  3. Ajouter un bouton flottant « ↑ » visible uniquement après 200 éléments parcourus.
  4. Le clic ramène en haut avec animateScrollToItem(0).

Contraintes : la recherche doit être déléguée (ContentType = Snapshot) pour éviter de re-filtrer tout l'état.

Livrables : ContactsScreen.kt + test de scroll.

Exercice 8 — Thème dynamique et dark mode (⭐⭐)

Objectifs : MaterialTheme, dynamicColor (Android 12+), thème sombre et typographie.

Consignes :

  1. Définir un ColorScheme clair et sombre dans theme/Theme.kt.
  2. Activer dynamicDarkColorScheme sur Android 12+.
  3. Utiliser MaterialTheme.typography dans toute l'app (titres, corps, labels).
  4. Ajouter un écran de réglages avec un Switch pour basculer le thème à chaud.

Contraintes : le thème doit être testable (aucun hardcoded color dans les composables), compatibilité avec les écrans de l'exercice 5.

Livrables : Theme.kt + SettingsScreen.kt.


Section 3 — Swift & async/await (Exercices 9 à 11)

Exercice 9 — Types et protocol (⭐)

Objectifs : réviser les struct, enum avec valeurs associées, protocol et extension.

Contexte : une application de paiement. Un paiement est soit par carte, soit par PayPal, soit par virement.

Consignes :

  1. Créer une struct Payment (montant, devise, date).
  2. Créer une enum PaymentMethod avec valeurs associées (carte : Card, PayPal : String (email), virement : BIC).
  3. Créer un protocol Processable avec func process() async throws -> PaymentReceipt.
  4. Implémenter Processable pour chaque méthode.

Contraintes : préférer struct et enum aux classes ; utiliser Decimal pour le montant.

Livrables : Payment.swift + tests unitaires (XCTest).

Exercice 10 — async/await : chargement parallèle (⭐⭐)

Objectifs : async let, TaskGroup, Actor et gestion d'erreurs.

Contexte : un dashboard charge 4 widgets en parallèle : solde, transactions récentes, budget mensuel, objectif d'épargne.

Consignes :

  1. Utiliser async let pour charger les 4 sources en parallèle.
  2. Remplacer par une withThrowingTaskGroup si une source échoue : continuer avec les autres.
  3. Protéger une ressource partagée (compteur de requêtes) avec un actor.
  4. Tester l'annulation avec Task.checkCancellation().

Contraintes : pas de closure d'échappement de rappel ; tout doit être async/await.

Livrables : DashboardLoader.swift + tests avec Swift Testing (ou XCTest).

Exercice 11 — Actors et data race (⭐⭐⭐)

Objectifs : comprendre les data races et la concurrence sécurisée avec les actor.

Contexte : un cache mémoire partagé entre plusieurs tâches concurrentes.

Consignes :

  1. Créer un actor InMemoryCache avec insertion, lecture et éviction LRU.
  2. Implémenter l'éviction LRU (least recently used) avec Dictionary + liste d'ordre.
  3. Écrire un test qui lance 1000 insertions concurrentes et vérifie l'intégrité du cache.
  4. Mesurer le temps avec et sans actor (en mode --enable-experimental-feature RegionBasedIsolation si disponible).

Livrables : InMemoryCache.swift + test de concurrence.


Section 4 — SwiftUI (Exercices 12 à 14)

Exercice 12 — SwiftUI de base : liste de tâches (⭐)

Objectifs : @State, @Binding, List, ForEach, onDelete, onMove et sheet.

Consignes :

  1. Afficher une liste de tâches (titre, priorité, date d'échéance).
  2. Ajouter une tâche via un sheet avec formulaire.
  3. Supprimer avec onDelete et réordonner avec onMove.
  4. Cocher une tâche (barré).
  5. Afficher le nombre de tâches restantes dans la NavigationStack toolbar.

Contraintes : données en mémoire uniquement, aucune persistance (exercice 26).

Livrables : TasksView.swift + tests de modèle.

Exercice 13 — ViewModel ObservableObject et Combine (⭐⭐)

Objectifs : ObservableObject, @Published, @EnvironmentObject, @Observable (Swift 6).

Contexte : une application de conversion de devises avec taux fixes en mémoire.

Consignes :

  1. Créer un CurrencyViewModel (@MainActor) exposant les montants et résultats.
  2. Implémenter la conversion avec un enum Currency.
  3. Utiliser @Observable (macros Swift 5.9+/6) ou ObservableObject.
  4. Inscrire une Preview qui fournit le ViewModel par l'environnement.

Contraintes : pas d'import d'UIKit ; uniquement SwiftUI.

Livrables : CurrencyView.swift + CurrencyViewModel.swift + tests.

Exercice 14 — Animations et transitions (⭐⭐)

Objectifs : withAnimation, matchedGeometryEffect, transitions et TimelineView.

Contexte : un écran de profil avec un avatar, une carte de stats et des vues dépliables.

Consignes :

  1. Animer l'agrandissement de l'avatar au tap (matchedGeometryEffect).
  2. Déplier/replier une section de détails avec transition(.move(...).combined(with: .opacity)).
  3. Ajouter un compteur animé avec TimelineView (nombre qui compte de 0 à la valeur).
  4. Respecter accessibilityReduceMotion (désactiver les animations si activé).

Livrables : ProfileView.swift complet.


Section 5 — React Native Hooks (Exercices 15 à 17)

Exercice 15 — useState et useEffect : formulaire contrôlé (⭐)

Objectifs : useState, useEffect, dérivation d'état, cleanup.

Contexte : un formulaire d'inscription (email, mot de passe, confirmation).

Consignes :

  1. Gérer les 3 champs avec un seul useState (objet).
  2. Dériver un booléen isValid sans useState supplémentaire (calcul).
  3. Afficher un message après 2 secondes d'inactivité (debounce via useEffect + setTimeout).
  4. Nettoyer le setTimeout dans le cleanup.

Contraintes : TypeScript strict, pas de bibliothèque de formulaire externe.

Livrables : SignUpForm.tsx + tests avec React Native Testing Library.

Exercice 16 — Custom hooks : useDebounce et useFetch (⭐⭐)

Objectifs : factoriser la logique réutilisable dans des hooks personnalisés.

Consignes :

  1. Créer un hook useDebounce<T>(value: T, delay: number): T.
  2. Créer un hook useFetch<T>(url, options) qui expose { data, error, loading, refetch }.
  3. Combiner les deux dans un écran de recherche.
  4. Annuler les requêtes obsolètes (AbortController dans le cleanup).

Contraintes : hooks typés en TypeScript, testés unitairement.

Livrables : hooks/useDebounce.ts, hooks/useFetch.ts + tests.

Exercice 17 — React.memo, useCallback et useMemo (⭐⭐)

Objectifs : comprendre quand (et pourquoi) mémoriser dans RN.

Contexte : une liste de 1000 lignes avec des boutons de favori ; la liste est re-rendue à chaque changement de thème.

Consignes :

  1. Envelopper l'item de liste dans React.memo.
  2. Stabiliser le handler « toggle favorite » avec useCallback.
  3. Mémoriser la liste triée/filtrée avec useMemo.
  4. Mesurer le nombre de re-rendus avec Profiler de React et expliquer les gains.

Contraintes : la mémorisation doit être justifiée par des mesures, pas appliquée partout.

Livrables : VirtualizedListScreen.tsx + rapport de mesures.


Section 6 — Flutter Widgets (Exercices 18 à 20)

Exercice 18 — Widgets de base : carte de profil (⭐)

Objectifs : StatelessWidget, StatefulWidget, Text, Row, Column, Stack, Card.

Consignes :

  1. Créer une carte de profil (avatar, nom, bio, boutons).
  2. Mettre l'avatar en CircleAvatar avec image réseau et fallback initiales.
  3. Utiliser Stack pour superposer un badge de statut en ligne sur l'avatar.
  4. Ajouter un Switch qui bascule le statut « disponible ».

Contraintes : respect des directives Material (spacing de 8), aucun package externe.

Livrables : profile_card.dart + un widget test.

Exercice 19 — Listes et gestion d'état : StatefulWidget + setState (⭐⭐)

Objectifs : ListView.builder, setState, ValueNotifier, Dismissible.

Consignes :

  1. Afficher 200 éléments dans une ListView.builder avec const constructeurs.
  2. Cocher un élément (case à cocher) avec setState.
  3. Supprimer un élément avec Dismissible (confirmation si besoin).
  4. Extraire l'état dans un ValueNotifier et le réutiliser hors widget.

Contraintes : performance : aucune construction non-const dans le builder.

Livrables : checklist_screen.dart + tests de widget (tap, dismiss).

Exercice 20 — Theme et responsive : layout adaptatif (⭐⭐)

Objectifs : ThemeData, MediaQuery, LayoutBuilder, GridView responsive.

Consignes :

  1. Créer 2 ThemeData (clair/sombre) et les appliquer globalement.
  2. Grille d'articles : 2 colonnes en portrait, 3 en paysage, 4 sur tablette (≥ 900 px).
  3. Ajuster les paddings et la taille des textes selon la taille d'écran.
  4. Persister le choix de thème avec shared_preferences.

Livrables : responsive_grid.dart + tests.


Section 7 — Architecture MVVM (Exercices 21 à 24)

Exercice 21 — MVVM avec Android ViewModel (⭐⭐)

Objectifs : ViewModel, StateFlow, viewModelScope, LiveData (ou équivalent), SavedStateHandle.

Contexte : refactoriser l'écran de l'exercice 6 (panier) pour suivre MVVM.

Consignes :

  1. Créer un CartViewModel qui expose un StateFlow<CartUiState>.
  2. Les intéractions de l'écran appellent des méthodes du ViewModel (onQtyChange, onRemove).
  3. Gérer la rotation : état conservé par le ViewModel.
  4. Remplacer l'état local remember par l'état du ViewModel.

Contraintes : aucune logique métier dans le composable, UI 100 % pilotée par l'état.

Livrables : CartViewModel.kt + CartScreen.kt refactorisés + tests du ViewModel.

Exercice 22 — Clean Architecture : layers et use cases (⭐⭐⭐)

Objectifs : séparer domain, data, presentation ; DI avec Hilt ou manual.

Contexte : une app de citation du jour consomme une API, met en cache et affiche la citation.

Consignes :

  1. Créer le module domain : Quote, QuoteRepository (interface), use case GetQuoteOfTheDay.
  2. Créer le module data : RemoteQuoteDataSource (Retrofit), LocalQuoteDataSource (Room), implémentation QuoteRepositoryImpl.
  3. Créer le module presentation : ViewModel qui appelle le use case.
  4. Brancher l'injection de dépendances (Hilt ou constructeur manuel).

Contraintes : le domaine ne doit dépendre d'aucun framework Android.

Livrables : les 3 modules + tests unitaires du use case (fakes).

Exercice 23 — MVI : unidirectional data flow (⭐⭐⭐)

Objectifs : Intent, State, Effect ; réduction de state.

Contexte : un écran de paiement multi-étapes (montant → moyens → confirmation) avec états d'erreur.

Consignes :

  1. Définir PaymentIntent (sealed class), PaymentState (data class), PaymentEffect (un événement unique : toast, navigation).
  2. Implémenter reduce(intent) dans le ViewModel.
  3. Gérer les états Loading, Success, Error avec retry.
  4. Émettre un Effect unique pour la navigation post-paiement.

Contraintes : aucune modification d'état hors reduce, immutabilité stricte.

Livrables : PaymentViewModel.kt + tests de réduction.

Exercice 24 — Multi-module Gradle (⭐⭐⭐)

Objectifs : organiser un projet Android en modules Gradle, gérer les dépendances avec version catalog.

Consignes :

  1. Créer les modules :app, :core:ui, :core:network, :feature:cart, :feature:checkout.
  2. Mettre en place un libs.versions.toml (version catalog).
  3. Faire dépendre :feature:cart de :core:network sans cycle.
  4. Activer le configuration cache et vérifier les temps de build.
  5. Documenter les règles de visibilité (internal, public).

Livrables : le multi-module complet + un schéma de dépendances dans diagrams/.


Section 8 — Room & CoreData (Exercices 25 à 27)

Exercice 25 — Room : DAO, entités et relations (⭐⭐)

Objectifs : @Entity, @Dao, @Database, Flow de requêtes, @Relation.

Contexte : une app de notes avec catégories (une note appartient à une catégorie).

Consignes :

  1. Créer NoteEntity et CategoryEntity avec clé étrangère et index.
  2. Créer NoteDao avec Flow<List<Note>>, insertion avec conflit REPLACE, suppression.
  3. Mapper vers un Note du domaine avec une jointure @Relation.
  4. Effectuer une migration de schéma (ajout d'une colonne) avec Migration.

Contraintes : schéma versionné, migration testée, aucune requête sur le main thread.

Livrables : les entités + DAO + AppDatabase + test d'instrumentation.

Exercice 26 — CoreData : stack et CRUD (⭐⭐)

Objectifs : NSPersistentContainer, NSManagedObject, @FetchRequest, contexte de vue.

Contexte : reprendre l'app de tâches de l'exercice 12 et la rendre persistante.

Consignes :

  1. Créer le modèle CoreData (Task avec attributs titre, priorité, date, done).
  2. Implémenter un TaskStore (actor) qui expose des méthodes async CRUD.
  3. Afficher la liste via @FetchRequest + NSFetchedResultsController (ou @Query si Swift 6/observation).
  4. Gérer les erreurs de sauvegarde (try? context.save() et alerte).

Livrables : modèle .xcdatamodeld + TaskStore.swift + tests.

Exercice 27 — Synchronisation offline-first (⭐⭐⭐)

Objectifs : travailler en mode hors-ligne, file de synchronisation, conflits.

Contexte : une app de suivi de tâches doit fonctionner hors-ligne puis synchroniser au retour du réseau.

Consignes :

  1. Persister les modifications locales dans une file d'opérations (table PendingOperation).
  2. Au retour du réseau, rejouer les opérations dans l'ordre.
  3. Gérer un conflit simple : la version la plus récente gagne (horodatage).
  4. Exposer l'état de synchronisation (en attente / en cours / à jour) dans l'UI.

Contraintes : aucune perte de données ; test du scénario offline → online.

Livrables : implémentation Room + WorkManager (ou CoreData + BackgroundTask) + tests.


Section 9 — Networking (Exercices 28 à 30)

Exercice 28 — Retrofit + Moshi + intercepteurs (⭐⭐)

Objectifs : Retrofit, sérialisation JSON, intercepteurs, gestion d'erreurs.

Consignes :

  1. Créer une API GitHub (GET /users/{user}/repos) avec Retrofit + Moshi.
  2. Ajouter un intercepteur de log et un intercepteur d'authentification (token en en-tête).
  3. Mapper les erreurs HTTP vers un type ApiError scellé (Network, Server, Auth, NotFound).
  4. Exposer les résultats sous forme de Resource<T> (Success/Error/Loading).

Contraintes : aucun appel réseau dans la couche UI ; types Kotlin immuables.

Livrables : GithubApi.kt + tests avec MockWebServer (OkHttp).

Exercice 29 — URLSession et async/await (⭐⭐)

Objectifs : URLSession.data(for:), Decodable, gestion des erreurs, @NetworkActor si Swift 6.

Consignes :

  1. Récupérer la liste des publications d'un flux JSON public avec URLSession.
  2. Décoder avec un type Article: Decodable.
  3. Gérer les statuts HTTP non-2xx avec un APIError typé.
  4. Exposer une API async et une API « closures » pour comparer.

Contraintes : pas d'Alamofire (stdlib seulement) pour comprendre le fonctionnement.

Livrables : NetworkClient.swift + tests avec un serveur local (swift-server ou mock).

Exercice 30 — WebSocket et notifications temps réel (⭐⭐⭐)

Objectifs : connecter un WebSocket, réagir aux messages, sécuriser la reconnexion.

Contexte : un chat simple : envoi de messages, réception temps réel, indicateur de connexion.

Consignes :

  1. Ouvrir une connexion WebSocket (OkHttp côté Android, URLSessionWebSocketTask côté iOS).
  2. Exposer un Flow/AsyncStream des messages reçus.
  3. Implémenter la reconnexion exponentielle (1 s, 2 s, 4 s…, max 60 s) avec jitter.
  4. Fermer proprement la connexion à la déconnexion de l'utilisateur.

Contraintes : tests de reconnexion sans réseau réel (MockWebServer ou serveur local).

Livrables : ChatWebSocket.kt / ChatWebSocket.swift + tests.


Section 10 — Testing (Exercices 31 à 34)

Exercice 31 — Tests unitaires : strategie et edge cases (⭐)

Objectifs : écrire des tests unitaires couvrant les cas limites d'une fonction.

Contexte : une fonction calculateDiscount(price: Double, coupon: String?) avec des règles précises.

Consignes :

  1. Lister les règles : coupon nul → 0 %, « SAVE10 » → 10 %, « VIP20 » → 20 %, max 30 %.
  2. Écrire les tests des cas normaux, limites (0 €, montant max), et invalides.
  3. Calculer le taux de couverture (JaCoCo / XcodeCoverage) et viser ≥ 90 %.
  4. Nommer les tests selon la convention method_stateUnderTest_expectedResult.

Livrables : la suite de tests + rapport de couverture.

Exercice 32 — Tests d'UI Compose / SwiftUI (⭐⭐)

Objectifs : automatiser le parcours utilisateur sur l'écran de l'exercice 5.

Consignes :

  1. Écrire un test Compose : saisir un email invalide → vérifier le message d'erreur.
  2. Tester l'état du bouton (désactivé → activé) selon la validité.
  3. Côté iOS : même parcours avec XCUITest (identifiants d'accessibilité).
  4. Lancer les tests sur 2 configurations (phone + tablette si possible).

Livrables : LoginScreenTest (Compose) et LoginScreenUITests (XCUITest).

Exercice 33 — Tests de couche data avec fakes (⭐⭐)

Objectifs : tester repository et ViewModel avec des fakes, sans réseau ni base de données.

Consignes :

  1. Créer un FakeQuoteRepository (de l'exercice 22) avec comportements configurés.
  2. Tester le ViewModel : succès, erreur, retry.
  3. Tester la persistance avec une base Room in-memory (inMemoryDatabaseBuilder).
  4. Mesurer et limiter le temps d'exécution des tests (< 30 s).

Livrables : la suite complète de la couche data.

Exercice 34 — E2E avec Maestro (⭐⭐⭐)

Objectifs : écrire un flux E2E complet sur un device/émulateur avec Maestro.

Consignes :

  1. Installer Maestro et écrire un flow YAML : lancement → connexion → navigation → déconnexion.
  2. Ajouter des assertions (texte visible, élément affiché) et un waitForAnimationToEnd.
  3. Exécuter le flow en CI (GitHub Actions ou équivalent).
  4. Capturer une vidéo et des screenshots en sortie.

Livrables : flows/ YAML + job CI.


Section 11 — Performance (Exercices 35 à 37)

Exercice 35 — Profilage et jank (⭐⭐)

Objectifs : identifier les frames lentes et les corriger avec les outils de profiling.

Consignes :

  1. Construire une liste volontairement lente (chargement synchrone dans onDraw/layout).
  2. Utiliser Android Studio Profiler (GPU rendering) / Instruments (Core Animation) pour mesurer.
  3. Identifier le point de blocage et le corriger (chargement asynchrone, mise en cache).
  4. Comparer les courbes de frames avant/après et documenter.

Livrables : les captures avant/après + rapport.

Exercice 36 — Démarrage à froid et multithreading (⭐⭐⭐)

Objectifs : réduire le cold start et répartir la charge sur les threads.

Consignes :

  1. Mesurer le cold start avec adb shell am start -W (Android) et MetricKit (iOS).
  2. Trouver les travaux bloquant le démarrage (lecture de préférences, init de DI…).
  3. Déplacer les travaux lourds hors du main thread ou les retarder après le premier frame.
  4. Cibler < 1,5 s (démarrage à froid Android) et documenter chaque gain.

Livrables : rapport de mesure avant/après + code optimisé.

Exercice 37 — Mémoire : fuites et images (⭐⭐)

Objectifs : détecter et corriger des fuites mémoire et une surconsommation d'images.

Consignes :

  1. Créer une fuite volontaire (listener non retiré / ViewModel non libéré).
  2. La détecter avec LeakCanary (Android) ou Instruments Leaks (iOS).
  3. Corriger la fuite et vérifier sa disparition.
  4. Implémenter un cache d'images (Coil / SDWebImage) et comparer l'empreinte mémoire.

Livrables : capture LeakCanary/Instruments avant/après.


Section 12 — Sécurité (Exercices 38 à 40)

Exercice 38 — Chiffrement et stockage sécurisé (⭐⭐)

Objectifs : stocker un secret de façon sécurisée et chiffrer des données.

Consignes :

  1. Stocker un token d'API dans Android Keystore (EncryptedSharedPreferences) et/ou iOS Keychain.
  2. Implémenter le chiffrement AES-GCM d'une note avec une clé générée par l'appareil.
  3. Vérifier que le secret n'est pas lisible en clair dans le dossier de l'app.
  4. Tester la restauration après redémarrage de l'app.

Contraintes : aucune clé codée en dur, aucune donnée sensible dans SharedPreferences/UserDefaults bruts.

Livrables : SecureStore.kt / SecureStore.swift + tests.

Exercice 39 — Écouter et durcir le trafic réseau (⭐⭐)

Objectifs : valider la configuration réseau avec network_security_config (Android) et ATS (iOS).

Consignes :

  1. Bloquer tout le trafic HTTP non chiffré (base URL en HTTPS uniquement).
  2. Configurer les exceptions de domaine explicitement, puis les supprimer.
  3. Vérifier avec mitmproxy qu'une interception échoue sans certificat approuvé.
  4. Activer le certifiicate pinning sur un domaine critique et tester l'échec de l'interception.

Livrables : fichiers de config + procédure de vérification documentée.

Exercice 40 — OWASP MASVS : audit d'une app (⭐⭐⭐)

Objectifs : appliquer la méthode OWASP MASVS/MASWE à une application fournie.

Consignes :

  1. Choisir 8 exigences MASVS pertinentes (stockage, crypto, réseau, code).
  2. Tester chacune sur une app de démonstration (un-jaibreaké + émulateur).
  3. Produire un rapport de vulnérabilités : sévérité, impact, preuve, remédiation.
  4. Prioriser les corrections par risque (matrice impact/probabilité).

Livrables : rapport d'audit Markdown + checklist MASVS complétée.


Critères généraux de validation

CritèreDétail
CompilationAucun warning bloquant ; build release OK
TestsTous les tests de chaque exercice passent
StyleSuivi des conventions du chapitre concerné
CommentairesKDoc / DocC / JSDoc sur les API publiques
GitUn commit propre par exercice, messages conventionnels
AutonomieLes corrigés (chapitre 21) ne sont consultés qu'après tentative

Pour aller plus loin

  1. Refaites les exercices 21 à 40 sur la deuxième plateforme (Android ↔ iOS).
  2. Ajoutez un exercice d'intégration : combinez networking + persistance + MVVM + tests E2E.
  3. Faites relire vos exercices par un pair et appliquez le retour (code review).