Mobile App Architecture
Problème
Les apps mobiles sont souvent développées sans stratégie offline, avec un state management chaotique, et des builds qui cassent au moment de la publication.
Solution
Architecture en couches + offline-first + state management prévisible + pipeline de build automatisé.
1. Architecture en couches
┌─────────────────────────┐
│ UI Layer │ Widgets / Components
├─────────────────────────┤
│ State Management │ Zustand / Riverpod / Bloc
├─────────────────────────┤
│ Repository Layer │ Abstraction data
├─────────────────────────┤
│ Data Sources │ API client + Local DB
├─────────────────────────┤
│ Platform Bridge │ Native modules
└─────────────────────────┘
2. State management par stack
React Native — Zustand (simple) ou Redux Toolkit (complexe)
// store/authStore.ts
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
interface AuthState {
user: User | null;
token: string | null;
login: (email: string, password: string) => Promise<void>;
logout: () => void;
}
export const useAuthStore = create<AuthState>()(
persist(
(set) => ({
user: null,
token: null,
login: async (email, password) => {
const res = await api.post('/auth/login', { email, password });
set({ user: res.data.user, token: res.data.token });
},
logout: () => set({ user: null, token: null }),
}),
{ name: 'auth-storage' } // persisté en AsyncStorage
)
);
Flutter — Riverpod (recommandé) ou Bloc
// providers/auth_provider.dart
final authProvider = StateNotifierProvider<AuthNotifier, AuthState>((ref) {
return AuthNotifier(ref.read(apiProvider));
});
class AuthNotifier extends StateNotifier<AuthState> {
AuthNotifier(this._api) : super(const AuthState.initial());
final ApiClient _api;
Future<void> login(String email, String password) async {
state = const AuthState.loading();
try {
final res = await _api.post('/auth/login', {'email': email, 'password': password});
state = AuthState.authenticated(res.user, res.token);
} catch (e) {
state = AuthState.error(e.toString());
}
}
}
3. Offline-first
// React Native — WatermelonDB / SQLite / MMKV
// 1. Écrire en local d'abord
// 2. Sync en background quand réseau disponible
// 3. UI réactive aux changements locaux
import Database from 'watermelondb';
import { writable } from 'watermelondb/decorators';
class Post extends Model {
static table = 'posts';
@field('title') title!: string;
@field('body') body!: string;
@field('is_synced') isSynced!: boolean;
}
// Sync engine
async function sync() {
const unsynced = await database.collections.get('posts')
.query(Q.where('is_synced', false)).fetch();
for (const post of unsynced) {
await api.post('/posts', { title: post.title, body: post.body });
await post.update(p => { p.isSynced = true; });
}
}
4. Stockage local par besoin
| Besoin | React Native | Flutter |
|---|---|---|
| Key-value simple | MMKV, AsyncStorage | SharedPreferences |
| Données structurées | WatermelonDB, SQLite | Drift, sqflite |
| State persisté | Zustand persist | Riverpod + Hive |
| Secrets | Keychain (iOS) / Keystore (Android) | flutter_secure_storage |
| Cache images | FastImage, expo-image | cached_network_image |
5. Build et déploiement
React Native — EAS Build (Expo)
# Build pour stores
eas build --platform ios --profile production
eas build --platform android --profile production
# Submit aux stores
eas submit --platform ios --latest
eas submit --platform android --latest
Flutter — Codemagic / GitHub Actions
# .github/workflows/flutter_deploy.yml
build-ios:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- uses: subosito/flutter-action@v2
with: { flutter-version: '3.24' }
- run: flutter pub get
- run: flutter build ios --release --no-codesign
- run: xcodebuild -workspace ios/Runner.xcworkspace -scheme Runner -archivePath build/Runner.xcarchive archive
6. Checklist avant publication
- Tests sur device réel (pas seulement simulateur)
- App fonctionne en mode avion (offline-first)
- Permissions documentées et justifiées
- Icons et splash screen pour toutes les résolutions
- Version number incrémenté
- Changelog à jour
- Privacy policy accessible dans l'app
- Pas de secrets/API keys dans le code
- App Store screenshots (6.7", 6.5", 5.5")
- Play Store screenshots + feature graphic
Anti-patterns
- State global pour tout (over-engineering)
- Pas de gestion offline (app inutilisable sans réseau)
- AsyncStorage pour des données sensibles
- Pas de tests sur device réel avant publication
- Build debug en production
- Pas de versioning de la base locale (migrations)
Références
- [[KNOW-PAT-224]] — Secure Auth Implementation
- [[KNOW-PAT-225]] — Secrets Management
- [[KNOW-PAT-221]] — Privacy by Design
- [[KNOW-REF-035]] — React Native
- [[KNOW-REF-036]] — Flutter