Explorer
KNOW-PAT-231

Mobile App Architecture — State management, offline-first, platform-specific, deployment

Domaine
mobile
Type
pattern
Priorité
P1

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