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
- Chaque exercice est indépendant mais les sections 7 à 12 réutilisent les acquis des sections 1 à 6.
- Un niveau est indiqué : ⭐ débutant, ⭐⭐ intermédiaire, ⭐⭐⭐ avancé.
- Les livrables sont précisés pour chaque exercice ; le chapitre 21 donne les corrigés détaillés.
- 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 :
- Créer une
data class Parcelavec les propriétés ci-dessus. - Créer une
enum class ParcelStatusavec une propriétélabelen français. - Ajouter une extension function
Parcel.isDelayed(today: LocalDate): Boolean. - Écrire une fonction
updateStatus(parcel: Parcel?, status: ParcelStatus)qui gère lenullproprement 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 :
- Créer 3 fonctions
suspend funsimulant des appels réseau (délai + résultat). - Les lancer en parallèle avec
asyncdans unsupervisorScope. - Si la météo échoue, l'écran affiche les 2 autres résultats et un message d'erreur.
- Ajouter un timeout de 3 secondes sur l'ensemble (avec
withTimeoutOrNull).
Contraintes :
- Interdiction d'utiliser
runBlockingdans 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 :
- Créer un
Flow<String>de requêtes de recherche (intervalles de 300 ms entre chaque frappe). - Appliquer
debounce(300),distinctUntilChanged()etflatMapLatest { searchProduct(it) }. - Gérer les erreurs réseau avec
catchet émettre un étatRechercheEnEchec. - Exposer le résultat dans un
StateFlow<SearchState>.
Contraintes :
- Le
StateFlowdoit ê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 :
- Implémenter un tri par fusion (
mergeSort) générique surList<T>. - Implémenter une recherche binaire sur une liste triée.
- Mesurer le temps d'exécution des deux algorithmes sur 10 000 éléments.
- 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 :
- Créer un écran de connexion : champ email, champ mot de passe (masqué), bouton « Se connecter ».
- Valider les champs en temps réel : email conforme, mot de passe ≥ 8 caractères.
- Afficher les erreurs sous les champs (textes Material).
- Désactiver le bouton tant que les champs sont invalides.
- Ajouter un
Lottieou uneCircularProgressIndicatorpendant 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 :
- Créer un composable
ArticleRowstateless (état remonté). - Créer un composable
CartScreenqui détient l'état (liste + quantités). - Le total est recalculé automatiquement à chaque changement.
- 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 :
- Afficher les contacts dans une
LazyColumnavec clés stables (key = { contact.id }). - Filtrer la liste selon la requête de recherche.
- Ajouter un bouton flottant « ↑ » visible uniquement après 200 éléments parcourus.
- 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 :
- Définir un
ColorSchemeclair et sombre danstheme/Theme.kt. - Activer
dynamicDarkColorSchemesur Android 12+. - Utiliser
MaterialTheme.typographydans toute l'app (titres, corps, labels). - Ajouter un écran de réglages avec un
Switchpour 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 :
- Créer une
struct Payment(montant, devise, date). - Créer une
enum PaymentMethodavec valeurs associées (carte :Card, PayPal :String(email), virement :BIC). - Créer un
protocol Processableavecfunc process() async throws -> PaymentReceipt. - Implémenter
Processablepour 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 :
- Utiliser
async letpour charger les 4 sources en parallèle. - Remplacer par une
withThrowingTaskGroupsi une source échoue : continuer avec les autres. - Protéger une ressource partagée (compteur de requêtes) avec un
actor. - 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 :
- Créer un
actor InMemoryCacheavec insertion, lecture et éviction LRU. - Implémenter l'éviction LRU (least recently used) avec
Dictionary+ liste d'ordre. - Écrire un test qui lance 1000 insertions concurrentes et vérifie l'intégrité du cache.
- Mesurer le temps avec et sans
actor(en mode--enable-experimental-feature RegionBasedIsolationsi 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 :
- Afficher une liste de tâches (titre, priorité, date d'échéance).
- Ajouter une tâche via un
sheetavec formulaire. - Supprimer avec
onDeleteet réordonner aveconMove. - Cocher une tâche (barré).
- Afficher le nombre de tâches restantes dans la
NavigationStacktoolbar.
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 :
- Créer un
CurrencyViewModel(@MainActor) exposant les montants et résultats. - Implémenter la conversion avec un
enum Currency. - Utiliser
@Observable(macros Swift 5.9+/6) ouObservableObject. - Inscrire une
Previewqui 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 :
- Animer l'agrandissement de l'avatar au tap (
matchedGeometryEffect). - Déplier/replier une section de détails avec
transition(.move(...).combined(with: .opacity)). - Ajouter un compteur animé avec
TimelineView(nombre qui compte de 0 à la valeur). - 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 :
- Gérer les 3 champs avec un seul
useState(objet). - Dériver un booléen
isValidsansuseStatesupplémentaire (calcul). - Afficher un message après 2 secondes d'inactivité (debounce via
useEffect+setTimeout). - Nettoyer le
setTimeoutdans 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 :
- Créer un hook
useDebounce<T>(value: T, delay: number): T. - Créer un hook
useFetch<T>(url, options)qui expose{ data, error, loading, refetch }. - Combiner les deux dans un écran de recherche.
- Annuler les requêtes obsolètes (
AbortControllerdans 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 :
- Envelopper l'item de liste dans
React.memo. - Stabiliser le handler « toggle favorite » avec
useCallback. - Mémoriser la liste triée/filtrée avec
useMemo. - Mesurer le nombre de re-rendus avec
Profilerde 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 :
- Créer une carte de profil (avatar, nom, bio, boutons).
- Mettre l'avatar en
CircleAvataravec image réseau et fallback initiales. - Utiliser
Stackpour superposer un badge de statut en ligne sur l'avatar. - Ajouter un
Switchqui 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 :
- Afficher 200 éléments dans une
ListView.builderavecconstconstructeurs. - Cocher un élément (case à cocher) avec
setState. - Supprimer un élément avec
Dismissible(confirmation si besoin). - Extraire l'état dans un
ValueNotifieret 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 :
- Créer 2
ThemeData(clair/sombre) et les appliquer globalement. - Grille d'articles : 2 colonnes en portrait, 3 en paysage, 4 sur tablette (≥ 900 px).
- Ajuster les paddings et la taille des textes selon la taille d'écran.
- 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 :
- Créer un
CartViewModelqui expose unStateFlow<CartUiState>. - Les intéractions de l'écran appellent des méthodes du ViewModel (
onQtyChange,onRemove). - Gérer la rotation : état conservé par le ViewModel.
- Remplacer l'état local
rememberpar 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 :
- Créer le module
domain:Quote,QuoteRepository(interface), use caseGetQuoteOfTheDay. - Créer le module
data:RemoteQuoteDataSource(Retrofit),LocalQuoteDataSource(Room), implémentationQuoteRepositoryImpl. - Créer le module
presentation: ViewModel qui appelle le use case. - 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 :
- Définir
PaymentIntent(sealed class),PaymentState(data class),PaymentEffect(un événement unique : toast, navigation). - Implémenter
reduce(intent)dans le ViewModel. - Gérer les états
Loading,Success,Erroravec retry. - Émettre un
Effectunique 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 :
- Créer les modules
:app,:core:ui,:core:network,:feature:cart,:feature:checkout. - Mettre en place un
libs.versions.toml(version catalog). - Faire dépendre
:feature:cartde:core:networksans cycle. - Activer le
configuration cacheet vérifier les temps de build. - 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 :
- Créer
NoteEntityetCategoryEntityavec clé étrangère et index. - Créer
NoteDaoavecFlow<List<Note>>, insertion avec conflitREPLACE, suppression. - Mapper vers un
Notedu domaine avec une jointure@Relation. - 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 :
- Créer le modèle CoreData (
Taskavec attributs titre, priorité, date, done). - Implémenter un
TaskStore(actor) qui expose des méthodes async CRUD. - Afficher la liste via
@FetchRequest+NSFetchedResultsController(ou@Querysi Swift 6/observation). - 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 :
- Persister les modifications locales dans une file d'opérations (table
PendingOperation). - Au retour du réseau, rejouer les opérations dans l'ordre.
- Gérer un conflit simple : la version la plus récente gagne (horodatage).
- 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 :
- Créer une API GitHub (GET /users/{user}/repos) avec Retrofit + Moshi.
- Ajouter un intercepteur de log et un intercepteur d'authentification (token en en-tête).
- Mapper les erreurs HTTP vers un type
ApiErrorscellé (Network,Server,Auth,NotFound). - 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 :
- Récupérer la liste des publications d'un flux JSON public avec
URLSession. - Décoder avec un type
Article: Decodable. - Gérer les statuts HTTP non-2xx avec un
APIErrortypé. - 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 :
- Ouvrir une connexion WebSocket (OkHttp côté Android,
URLSessionWebSocketTaskcôté iOS). - Exposer un
Flow/AsyncStreamdes messages reçus. - Implémenter la reconnexion exponentielle (1 s, 2 s, 4 s…, max 60 s) avec jitter.
- 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 :
- Lister les règles : coupon nul → 0 %, « SAVE10 » → 10 %, « VIP20 » → 20 %, max 30 %.
- Écrire les tests des cas normaux, limites (0 €, montant max), et invalides.
- Calculer le taux de couverture (JaCoCo / XcodeCoverage) et viser ≥ 90 %.
- 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 :
- Écrire un test Compose : saisir un email invalide → vérifier le message d'erreur.
- Tester l'état du bouton (désactivé → activé) selon la validité.
- Côté iOS : même parcours avec XCUITest (identifiants d'accessibilité).
- 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 :
- Créer un
FakeQuoteRepository(de l'exercice 22) avec comportements configurés. - Tester le ViewModel : succès, erreur, retry.
- Tester la persistance avec une base Room in-memory (
inMemoryDatabaseBuilder). - 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 :
- Installer Maestro et écrire un flow YAML : lancement → connexion → navigation → déconnexion.
- Ajouter des assertions (texte visible, élément affiché) et un
waitForAnimationToEnd. - Exécuter le flow en CI (GitHub Actions ou équivalent).
- 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 :
- Construire une liste volontairement lente (chargement synchrone dans
onDraw/layout). - Utiliser Android Studio Profiler (GPU rendering) / Instruments (Core Animation) pour mesurer.
- Identifier le point de blocage et le corriger (chargement asynchrone, mise en cache).
- 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 :
- Mesurer le cold start avec
adb shell am start -W(Android) et MetricKit (iOS). - Trouver les travaux bloquant le démarrage (lecture de préférences, init de DI…).
- Déplacer les travaux lourds hors du main thread ou les retarder après le premier frame.
- 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 :
- Créer une fuite volontaire (listener non retiré / ViewModel non libéré).
- La détecter avec LeakCanary (Android) ou Instruments Leaks (iOS).
- Corriger la fuite et vérifier sa disparition.
- 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 :
- Stocker un token d'API dans Android Keystore (EncryptedSharedPreferences) et/ou iOS Keychain.
- Implémenter le chiffrement AES-GCM d'une note avec une clé générée par l'appareil.
- Vérifier que le secret n'est pas lisible en clair dans le dossier de l'app.
- 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 :
- Bloquer tout le trafic HTTP non chiffré (base URL en HTTPS uniquement).
- Configurer les exceptions de domaine explicitement, puis les supprimer.
- Vérifier avec mitmproxy qu'une interception échoue sans certificat approuvé.
- 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 :
- Choisir 8 exigences MASVS pertinentes (stockage, crypto, réseau, code).
- Tester chacune sur une app de démonstration (un-jaibreaké + émulateur).
- Produire un rapport de vulnérabilités : sévérité, impact, preuve, remédiation.
- 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ère | Détail |
|---|---|
| Compilation | Aucun warning bloquant ; build release OK |
| Tests | Tous les tests de chaque exercice passent |
| Style | Suivi des conventions du chapitre concerné |
| Commentaires | KDoc / DocC / JSDoc sur les API publiques |
| Git | Un commit propre par exercice, messages conventionnels |
| Autonomie | Les corrigés (chapitre 21) ne sont consultés qu'après tentative |
Pour aller plus loin
- Refaites les exercices 21 à 40 sur la deuxième plateforme (Android ↔ iOS).
- Ajoutez un exercice d'intégration : combinez networking + persistance + MVVM + tests E2E.
- Faites relire vos exercices par un pair et appliquez le retour (code review).