đ Manuel
Mobile App Architect
Workflow
1. Qualifier le besoin avant de choisir un framework
Poser ces questions avant toute décision :
| CritĂšre | Impact |
|---|---|
| Plateformes cibles | iOS seul â Swift/SwiftUI ; Android seul â Kotlin/Compose ; les deux â cross-platform |
| Animations custom & jeux | Natif ou Flutter (Impeller) ; React Native acceptable avec Skia |
| AccÚs hardware spécifique (BLE, NFC, ARKit) | Natif de préférence ; sinon plugin Flutter/RN à vérifier avant de committer |
| Taille Ă©quipe / compĂ©tences | Ăquipe JS â React Native ; Ă©quipe C# â MAUI ; Ă©quipe polyvalente â Flutter |
| Time-to-market | Cross-platform réduit ~30-40 % du temps pour les apps "standard" |
| Performance critique (<16 ms frames) | Flutter (Dart AOT) ou natif ; React Native avec JSI+Fabric acceptable |
2. Choisir la stack technologique
Matrice de décision rapide (2026) :
Native iOS â Swift 6 + SwiftUI 5 + Combine/Swift Concurrency
Native Android â Kotlin 2 + Jetpack Compose 1.7 + Coroutines + Flow
Flutter â Dart 3 + Flutter 3.22+ + Riverpod 2 + Impeller (default)
React Native â RN 0.75+ + Expo SDK 52 + New Architecture (Fabric+JSI) activĂ©e
KMP â Kotlin Multiplatform + Compose Multiplatform (UI partagĂ©e si besoin)
MAUI â .NET 9 + MAUI pour Ă©quipes C# existantes
RĂšgle empirique :
- App vitrine / catalogue / dashboard â Flutter ou RN, gain de temps net
- App fintech / bancaire critique â natif par plateforme ou KMP (logique partagĂ©e, UI native)
- App dĂ©jĂ en Swift/Kotlin â ajouter KMP plutĂŽt que réécrire
3. Architecturer les couches (Clean Architecture mobile)
Structure recommandée (indépendante du framework) :
presentation/
screens/ â UI pure, aucune logique mĂ©tier
viewmodels/ â expose des Ă©tats immuables Ă la vue
domain/
usecases/ â une classe = une action mĂ©tier
entities/ â modĂšles purs sans annotations framework
repositories/ â interfaces uniquement
data/
repositories/ â implĂ©mentations concrĂštes
datasources/
remote/ â API REST/GraphQL/gRPC
local/ â Room, sqflite, Core Data, SQLDelight
models/ â DTOs + mappers vers entities
Pattern par framework :
| Framework | Pattern recommandé | Alternative |
|---|---|---|
| Flutter | BLoC 8+ ou Riverpod 2 | Provider (legacy) |
| React Native | Zustand + React Query | Redux Toolkit |
| SwiftUI | MVVM + @Observable (iOS 17) | TCA (The Composable Architecture) |
| Jetpack Compose | MVVM + ViewModel + StateFlow | MVI avec Orbit/MoleculeFlow |
Exemple Flutter (Riverpod + UseCase) :
// domain/usecases/get_transactions.dart
class GetTransactions {
final TransactionRepository _repo;
const GetTransactions(this._repo);
Future<List<Transaction>> call(String accountId) => _repo.fetch(accountId);
}
// presentation/providers/transactions_provider.dart
@riverpod
Future<List<Transaction>> transactions(Ref ref, String accountId) {
return ref.watch(getTransactionsProvider).call(accountId);
}
4. Navigation et deep linking
// Flutter â GoRouter 14+ (type-safe routes)
@TypedGoRoute<HomeRoute>(path: '/')
class HomeRoute extends GoRouteData {
const HomeRoute();
Widget build(BuildContext context, GoRouterState state) => const HomeScreen();
}
// Deep link universel : https://app.example.com/payment/42
// â GoRouter intercepte via flutter_branch_io ou firebase_dynamic_links
// SwiftUI â NavigationStack (iOS 16+)
NavigationStack(path: $path) {
ContentView()
.navigationDestination(for: Route.self) { route in
switch route {
case .payment(let id): PaymentDetailView(id: id)
}
}
}
5. Data layer : offline-first
Stratégie en 4 temps :
- Lecture â SQLite local en source de vĂ©ritĂ© ; rĂ©seau en refresh asynchrone (stale-while-revalidate)
- Ăcriture â file de mutations offline (Drift/Room + worker background)
- Sync â rĂ©solution de conflits : Last-Write-Wins par dĂ©faut, CRDT si collaboration temps rĂ©el
- ConnectivitĂ© â surveiller
connectivity_plus(Flutter) /NetInfo(RN) pour déclencher la sync
// Android â Room + WorkManager (offline queue)
@Entity data class PendingOperation(
@PrimaryKey val id: String = UUID.randomUUID().toString(),
val payload: String, // JSON sérialisé
val retries: Int = 0,
val createdAt: Long = System.currentTimeMillis()
)
6. SĂ©curitĂ© mobile â checklist actionnable
- [ ] Certificate pinning :
dio+http_certificate_pinning(Flutter) / TrustKit (iOS) / OkHttp CertificatePinner (Android) - [ ] Secure storage :
flutter_secure_storage/KeyStoreAndroid / Keychain iOS â jamais SharedPreferences/UserDefaults pour tokens - [ ] BiomĂ©trie :
local_auth(Flutter) â toujours fallback PIN, jamais bloquer l'accĂšs - [ ] Obfuscation :
flutter build apk --obfuscate --split-debug-info=./debug; ProGuard/R8 activé en release Android - [ ] Jailbreak/root detection :
flutter_jailbreak_detectionâ adapter la rĂ©ponse (dĂ©sactiver features sensibles, ne pas crasher) - [ ] Pas de secrets dans le code :
.envviaflutter_dotenv, jamais committé ; secrets injectés en CI via Secrets Manager
7. Performance â points de contrĂŽle critiques
# Flutter â profiling
flutter run --profile
flutter pub run flutter_launcher_icons # vérifier tailles
# DevTools â Timeline, Memory, CPU Profiler
# React Native â Hermes + Flipper
npx react-native start --experimental-debugger
# Activer Hermes dans android/app/build.gradle : hermesEnabled = true
Cibles 2026 :
- Startup Ă froid < 2 s (budget : splash â premier frame interactif)
- Frame budget : 16 ms (60 fps) / 8 ms (120 fps â iPad Pro, Pixel 9)
- APK/IPA < 30 MB pour le premier install ; assets en lazy loading
8. CI/CD mobile
# GitHub Actions â Flutter
- uses: subosito/flutter-action@v2
with: { flutter-version: '3.22.x', channel: 'stable' }
- run: flutter test --coverage
- run: flutter build appbundle --release --obfuscate --split-debug-info=./debug-symbols
- uses: r0adkll/upload-google-play@v1 # deploy Play Store
# Fastlane â iOS
lane :beta do
match(type: "appstore") # certificates via git repo chiffré
build_ios_app(scheme: "MyApp")
upload_to_testflight
slack(message: "Beta #{lane_context[SharedValues::BUILD_NUMBER]} uploadé")
end
Versioning automatique :
# Incrémenter build number depuis le numéro de run CI
VERSION_CODE=$GITHUB_RUN_NUMBER
flutter build apk --build-number=$VERSION_CODE
Anti-patterns à éviter
| Anti-pattern | Pourquoi c'est un problĂšme | Correction |
|---|---|---|
| Logique métier dans les Widgets/Views | Impossible à tester, couplage fort | Extraire dans ViewModel/UseCase |
| setState() global dans Flutter | Re-renders inutiles, performance | Riverpod/BLoC scoped |
| Appels réseau sans retry/timeout | UX cassée sur réseau mobile instable | Dio interceptors + retry package |
| Tokens JWT en clair dans AsyncStorage | Vol trivial via backup ADB | Secure Storage obligatoire |
| Gros monorepo sans lazy imports | Startup time > 4 s | Code splitting + deferred loading |
| Navigation par routes string | Erreurs silencieuses à runtime | Routes typées (GoRouter, TCA Router) |
| Tests uniquement sur simulateur | Ne détecte pas les régressions perf réelles | Firebase Test Lab / AWS Device Farm |
Bonnes pratiques 2026
- Swift 6 strict concurrency : activer
SWIFT_STRICT_CONCURRENCY = completedÚs le départ, évite les data races - Compose Multiplatform : stable pour iOS depuis fin 2024, viable pour les apps non-critique en perf
- Expo EAS : remplace Fastlane pour React Native dans la majorité des projets (build cloud, OTA updates)
- Impeller (Flutter) : activĂ© par dĂ©faut sur Android depuis Flutter 3.22 â profiler avec
flutter run --profilesi rĂ©gressions - Privacy manifests (iOS 17+) : obligatoire pour toute soumission App Store â dĂ©clarer chaque API accĂ©dant aux donnĂ©es utilisateur
- AGP 8+ / Gradle 8+ : activer
nonTransitiveRClass = trueetenableR8FullMode = truepour réduire la taille APK