Patterns mobile natif et cross-platform. Se charge à l'écriture/modification de : .kt, .kts (Kotlin/Android), .swift (iOS), .dart (Flutter), .tsx/.jsx dans un projet React Native/Expo. Détection projet : AndroidManifest.xml, Info.plist, pubspec.yaml, app.json ou app.config.js (Expo), dossiers android/ + ios/ (RN), build.gradle, Podfile. Couvre : Android (Kotlin, Jetpack Compose, XML layouts, Gradle, lifecycle), iOS (SwiftUI, UIKit, SwiftPM, CoreData), Flutter (widgets, Riverpod/Bloc, platform...
Scanned 5/27/2026
Install via CLI
openskills install vendeesign/codebloom---
name: mobile
description: "Patterns mobile natif et cross-platform. Se charge à l'écriture/modification de : .kt, .kts (Kotlin/Android), .swift (iOS), .dart (Flutter), .tsx/.jsx dans un projet React Native/Expo. Détection projet : AndroidManifest.xml, Info.plist, pubspec.yaml, app.json ou app.config.js (Expo), dossiers android/ + ios/ (RN), build.gradle, Podfile. Couvre : Android (Kotlin, Jetpack Compose, XML layouts, Gradle, lifecycle), iOS (SwiftUI, UIKit, SwiftPM, CoreData), Flutter (widgets, Riverpod/Bloc, platform channels), React Native/Expo (hooks, navigation, bridges natifs). Ne se charge PAS quand : discussion, review ou documentation sans écriture de code mobile."
---
# Mobile Dev — Patterns natifs et cross-platform
Se charge automatiquement quand du code touche Android, iOS, Flutter ou React Native.
## Règles universelles (tous les frameworks)
### Touch targets & Spacing
- Touch targets : **48x48dp** minimum (44pt iOS) — primary actions **56x56dp**
- Spacing grid : multiples de **8dp** (4, 8, 16, 24, 32, 48)
- Primary actions dans la **thumb zone** (moitié basse de l'écran)
### Accessibilité (non-négociable)
- Chaque élément interactif a un label accessible (`contentDescription` Android, `accessibilityLabel` iOS/RN)
- Ne pas décrire le type d'élément dans le label : "Sauvegarder" pas "Bouton Sauvegarder"
- Contraste texte : 4.5:1 (normal), 3:1 (grand texte ≥18sp)
- Ne jamais communiquer l'information par la couleur seule
- Respecter `prefers-reduced-motion` / `accessibilityReduceMotion`
- Support Bold Text et Increase Contrast
### Animation
- Animer uniquement `transform` et `opacity` — jamais layout (width, height, margin, padding)
- 60fps cible = < 16ms par frame
- Désactiver parallax/3D sur `pointer: coarse` (mobile)
### Navigation
- Tab bar : 3-5 sections principales, reste visible pendant la navigation
- Back swipe toujours fonctionnel
- Préserver l'état entre les tabs
- **Jamais** de hamburger menu / drawer comme navigation principale
---
## Android (Kotlin / Jetpack Compose)
### Fondation
Avant la logique métier : `./gradlew assembleDebug` doit réussir.
### Kotlin — Règles clés
- **Null safety** : éviter `!!` (crash). Utiliser `user?.name ?: "Unknown"`
- **Exceptions** : laisser remonter, wrapper dans `Result` au ViewModel : `runCatching { }.onSuccess { }.onFailure { }`
- **Server responses** : tous les champs `nullable` (`String?`) — le backend peut omettre des champs optionnels
### Threading
| Opération | Thread | Raison |
|-----------|--------|--------|
| Mutations UI/State | `Dispatchers.Main` | Obligatoire main thread |
| Network/File I/O | `Dispatchers.IO` | Ne pas bloquer le main |
| Compute intensif | `Dispatchers.Default` | JSON parsing, crypto, tri |
### Jetpack Compose
- `@Composable` uniquement dans `@Composable` — `LaunchedEffect(Unit)` pour suspend
- State local : `remember { mutableStateOf(...) }`
- State dérivé : `remember { derivedStateOf { ... } }` (évite calculs redondants)
- ViewModel state : `StateFlow<UiState>` (persiste les config changes)
- **Anti-pattern** : créer des objets dans composables = recréation à chaque recomposition → `viewModel()` ou `remember`
- `const` constructors sur les widgets statiques
### Material Design 3
- Grille 8dp
- Noms réservés interdits (`background`, `icon`, `view`, `button`) → préfixer : `app_background`, `ic_home`
- Style par catégorie d'app : utility → minimalist, finance → professional, social → expressive
### Build variants
- Product flavors : `dev`, `prod` avec `buildConfigField()` pour API URLs
- Source directories parallèles : `app/src/dev/`, `app/src/prod/`
---
## iOS (SwiftUI / UIKit)
### Layout
- Safe areas : contenu respecte les safe areas — `.ignoresSafeArea()` réservé aux backgrounds
- Écrans : iPhone SE (375pt) à Pro Max (430pt)
- Body text minimum : 11pt
### Typography
**UIKit** : `preferredFont(forTextStyle:)` + `adjustsFontForContentSizeCategory = true`
**SwiftUI** : styles sémantiques (`.headline`, `.body`, `.caption`) + `Font.custom(_:size:relativeTo:)`
Reflow aux tailles d'accessibilité sans troncature.
### Color & Dark Mode
- Colors système : `.systemBackground`, `.label`, `.primary`, `.secondary`
- Variantes light/dark dans l'asset catalog pour les couleurs custom
- Pas d'information par la couleur seule
### Navigation
| Pattern | Composant |
|---------|-----------|
| Tab-based (3-5 sections) | `UITabBarController` / `TabView` |
| Hiérarchique | `UINavigationController` / `NavigationStack` + `NavigationPath` |
| Modal / tâche focused | Sheet avec dismiss path explicite |
### SwiftUI — Patterns
- Listes : `List` avec `.insetGrouped`, `.searchable`
- Partage : `ShareLink`
- Progress : `ProgressView(value:total:)` (connu) ou `ProgressView()` (indéterminé)
- Environment : `@Environment(\.scenePhase)`, `@Environment(\.dynamicTypeSize)`, `@Environment(\.accessibilityReduceMotion)`
- State : `@SceneStorage` pour persistence entre les tabs
### UIKit — Composants modernes
| Tâche | Composant |
|-------|-----------|
| Listes | `UICollectionView` + `DiffableDataSource` |
| Grilles | `UICollectionViewCompositionalLayout` |
| Layouts linéaires | `UIStackView` |
| Boutons modernes | `UIButton.Configuration` |
| Feedback haptique | `UIImpactFeedbackGenerator` |
### Privacy
- Permissions en contexte (pas au launch)
- Explications custom avant les dialogues système
- Sign in with Apple si auth sociale
- Fonctionnalité de base sans compte
- Respecter le refus ATT
---
## Flutter
### Widget optimization
- `const` constructors sur tous les widgets statiques
- Extraire les widgets réutilisables dans des fichiers séparés
- Keys appropriées : `ValueKey` pour primitifs, `ObjectKey` pour objets
- `ConsumerWidget` plutôt que `StatefulWidget` avec providers
- Ne jamais construire de widgets dans `build()`
### State management
**Riverpod** (recommandé) :
- `StateProvider` + `ConsumerWidget` pour état simple
- `NotifierProvider` / `AsyncNotifierProvider` pour complexe
- `ref.watch(provider.select(...))` pour rebuilds ciblés
- State toujours immutable
**Bloc/Cubit** : pour workflows event-driven avec logique métier complexe
### Layout responsive
- Breakpoints : mobile < 650px, tablet 650-1100px, desktop > 1100px
- 8px spacing grid
- Material 3 ou Cupertino standards
### Performance
- Cible < 16ms par frame (60fps)
- `RepaintBoundary` pour isoler les animations complexes
- `compute()` pour les tâches lourdes (isolates)
- Profiler avec DevTools **avant** d'optimiser
### Navigation
- GoRouter avec routes type-safe
- Guards d'auth via redirects
- Deep linking supporté
- State préservé entre les transitions
---
## React Native / Expo
### Composants — Choix modernes
| Au lieu de... | Utiliser... | Pourquoi |
|--------------|-------------|----------|
| `FlatList` (> 20 items) | `FlashList` + memo | View recycling, perf |
| `Image` (RN natif) | `expo-image` | WebP + cache intégré |
| `TouchableOpacity` | `Pressable` | API moderne |
| `expo-av` | `expo-audio` / `expo-video` | Deprecation |
| `Animated` (RN natif) | `Reanimated 3` | GPU optimisé |
| `PanResponder` | `Gesture Handler` | Gestes propres |
| `Platform.OS` (Expo) | `process.env.EXPO_OS` | Détection Expo-specific |
| `useContext` (React 18+) | `React.use()` | Hook moderne |
### Layout & Safe Areas
- `contentInsetAdjustmentBehavior='automatic'` sur tous les scrollables — gère les safe areas sans `<SafeAreaView>`
- `headerLargeTitle` + `headerBackButtonDisplayMode` pour le feel natif iOS
### State management
| Besoin | Solution |
|--------|----------|
| UI locale | `useState` / `useReducer` |
| État partagé | Zustand (selectors) ou Jotai (atoms) |
| Données serveur | React Query |
| Formulaires | React Hook Form + Zod |
### Bundle optimization
- Import direct depuis les modules sources — **jamais** de barrel files (`index.ts` qui re-exporte tout)
- R8 code shrinking pour Android release
- `source-map-explorer` avant de ship pour identifier le bloat
### Pièges courants
- `{count && <Text />}` rend le string `"0"` → utiliser `{count > 0 && <Text />}`
- Route files en kebab-case — ne pas co-localiser composants/types/utils dans `app/`
- Essayer `npx expo start` (Expo Go) d'abord — custom builds uniquement si modules natifs locaux
---
## Testing mobile — Pyramide
- **70% unit** (JVM, ~ms) : ViewModel, repository, mapper, validateur
- **20% integration** (~100ms) : Compose UI tests, Robolectric, API routes
- **10% E2E** (~seconds) : Espresso (Android), XCTest (iOS), Detox/Maestro (RN)
Coverage par écran : happy path + error state + empty state + loading state + edge cases (offline, slow network, expired session)
## Tool Boundaries
- Appliquer les patterns du framework détecté dans le projet
- Ne pas migrer de framework mobile sauf demande explicite
- Ne pas changer de state management sans discussion
No comments yet. Be the first to comment!