MVVM : qu'est-ce que c'est, le modèle Model-View-ViewModel dans le développement mobile

Auteur : IT Sectr Publié le : 2026-02-16 Temps de lecture : 9 min

MVVM (Model-View-ViewModel) est un modèle architectural dans lequel ViewModel remplace Presenter et utilise des mécanismes réactifs pour communiquer avec View : ObservableObject dans SwiftUI, LiveData/StateFlow dans Android. ViewModel n'a pas de référence à View — les données sont transmises par abonnement, ce qui élimine le besoin d'interfaces ViewContract et rend les tests encore plus simples. Apple recommande MVVM avec SwiftUI depuis 2019, Google recommande MVVM avec Jetpack comme architecture officielle d'Android. En savoir plus dans Android Architecture Guide.

Points clés

  • MVVM — Model (données), View (interface), ViewModel (état et logique sans référence à View)
  • Liaison réactive — LiveData, StateFlow, ObservableObject mettent à jour automatiquement l'UI lors des changements de données
  • ViewModel — survit à la rotation de l'écran et ne dépend pas d'Android SDK/UIKit, testable avec des tests unitaires
  • Android Jetpack — ViewModel, LiveData, DataBinding — stack officiel de Google pour MVVM
  • SwiftUI + Combine — implémentation native de MVVM dans iOS avec @Published et @ObservedObject

Qu'est-ce que MVVM : l'essence du modèle Model-View-ViewModel

MVVM (Model-View-ViewModel) est un modèle architectural décrit par John Gossman en 2005 pour Windows Presentation Foundation (WPF) de Microsoft. ViewModel est le composant central qui contient l'état de l'écran et la logique métier mais n'a pas de référence à View. Les données sont transmises via des mécanismes de liaison réactive : View s'abonne aux changements de ViewModel et se réaffiche automatiquement lorsque les données changent.

Différence clé entre MVVM et MVP — absence de ViewContract. Dans MVP, Presenter appelle les méthodes view.showUser(data), c'est-à-dire que Presenter "pousse" activement les données vers View. Dans MVVM, View elle-même "tire" les données de ViewModel par abonnement : ViewModel ne sait pas s'il a un abonné. Cela élimine le problème de View déconnectée — si Activity est détruite lors de la rotation, ViewModel continue de fonctionner et la nouvelle Activity s'abonne simplement aux données actuelles. Chez IT Sectr, nous utilisons MVVM dans tous les nouveaux projets depuis 2020 — le code est devenu plus prévisible, les tests plus stables.

ComposantResponsabilitéPlateforme
ModelDonnées, logique métier, repositoriesAndroid/iOS
ViewAffichage, abonnement à ViewModelActivity/Composable, UIView/SwiftUI View
ViewModelÉtat de l'écran, logique, navigationViewModel (Jetpack), ObservableObject

Liaison réactive — le fondement de MVVM. Dans Android, LiveData (partie de Jetpack) est un conteneur de données observable. Activity s'abonne via observe() : viewModel.user.observe(this) { user -> binding.name.text = user.name }. Lorsque user change, tous les abonnés reçoivent automatiquement la nouvelle valeur. Dans iOS, SwiftUI utilise les propriétés @Published dans ViewModel — les changements réaffichent automatiquement View. Cela élimine les appels manuels showUser/hideLoading requis dans MVP.

MVVM dans Android : ViewModel, LiveData et StateFlow

ViewModel de Jetpack — le composant officiel de Google pour implémenter MVVM. ViewModel survit à la rotation de l'écran : lorsque la configuration change, Activity est détruite et recréée, tandis que ViewModel reste en mémoire. La nouvelle instance d'Activity obtient le même ViewModel via ViewModelProvider. ViewModel n'a pas de références à Activity, Context ou View — il est propre et testable avec des tests unitaires sans Robolectric.

kotlin
// ViewModel avec StateFlow — implémentation moderne de MVVM
class UserViewModel(
    private val repository: UserRepository
) : ViewModel() {

    private val _state = MutableStateFlow<UserState>(UserState.Loading)
    val state: StateFlow<UserState> = _state.asStateFlow()

    fun loadUser(userId: Int) {
        viewModelScope.launch {
            _state.value = UserState.Loading
            repository.getUser(userId)
                .onSuccess { user ->
                    _state.value = UserState.Success(user)
                }
                .onFailure { e ->
                    _state.value = UserState.Error(e.message ?: "Unknown")
                }
        }
    }
}

sealed interface UserState {
    data object Loading : UserState
    data class Success(val user: User) : UserState
    data class Error(val message: String) : UserState
}

// View (Activity) s'abonne à state
class UserActivity : AppCompatActivity() {
    private val viewModel: UserViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        viewModel.state.onEach { state ->
            when (state) {
                is UserState.Loading -> /* afficher le chargement */
                is UserState.Success -> /* afficher les données */
                is UserState.Error -> /* afficher l'erreur */
            }
        }.launchIn(lifecycleScope)
        viewModel.loadUser(42)
    }
}

LiveData vs StateFlow — LiveData (2017) — le premier composant réactif de Jetpack, optimisé pour le cycle de vie d'Activity : désabonnement automatique à onStop. StateFlow (2021) — implémentation Kotlin Flow, non liée au cycle de vie, mais nécessitant un désabonnement manuel via lifecycleScope. StateFlow prend en charge les coroutines, concat, map et d'autres opérateurs Flow, que LiveData n'a pas. Chez IT Sectr, nous utilisons StateFlow pour tous les nouveaux ViewModels — il est plus court, plus puissant et s'intègre mieux avec les coroutines.

DataBinding et ViewBinding — DataBinding lie ViewModel au XML via @{viewModel.user.name} directement dans le layout, éliminant le code dans Activity. ViewBinding génère une classe typée pour accéder aux vues. Google recommande ViewBinding pour les projets simples et DataBinding pour les projets avec liaison de données complexe. Dans Jetpack Compose, DataBinding n'est pas nécessaire — les fonctions @Composable se réaffichent automatiquement lorsque State change.

MVVM dans iOS : ObservableObject et SwiftUI

MVVM dans iOS est implémenté via ObservableObject de Combine. ViewModel est une classe héritant d'ObservableObject, avec des propriétés @Published. La SwiftUI View s'abonne à ViewModel via @ObservedObject ou @StateObject. Lorsqu'une propriété @Published change, SwiftUI réaffiche automatiquement la View qui dépend de cette propriété. Apple a présenté SwiftUI en 2019 à la WWDC avec Combine — depuis lors, MVVM est devenu le modèle officiellement recommandé pour iOS.

swift
import SwiftUI
import Combine

// ViewModel — ObservableObject avec des champs @Published
final class UserViewModel: ObservableObject {
    @Published private(set) var state: UserState = .loading
    private let service: UserService

    init(service: UserService) {
        self.service = service
    }

    func loadUser(id: Int) {
        state = .loading
        service.fetchUser(id: id) { [weak self] result in
            guard let self else { return }
            switch result {
            case .success(let user):
                self.state = .success(user)
            case .failure(let error):
                self.state = .error(error.localizedDescription)
            }
        }
    }
}

enum UserState {
    case loading
    case success(User)
    case error(String)
}

// SwiftUI View — s'abonne à ViewModel
struct UserView: View {
    @StateObject private var viewModel: UserViewModel

    var body: some View {
        switch viewModel.state {
        case .loading:
            ProgressView()
        case .success(let user):
            VStack {
                Text(user.name).font(.title)
                Text(user.email).font(.body)
            }
        case .error(let message):
            Text(message).foregroundColor(.red)
        }
    }
}

@StateObject vs @ObservedObject — @StateObject crée ViewModel et gère son cycle de vie (une fois par durée de vie de View). @ObservedObject — ViewModel est créé à l'extérieur et passé à View. La WWDC 2022 recommande @StateObject pour la création et @ObservedObject pour passer ViewModel entre Views. Dans iOS 17 (2023), la macro @Observable est apparue — automatisant l'abonnement et éliminant les annotations @Published. @Observable est l'évolution de Combine, rapprochant le développement iOS de la réactivité de Kotlin Flow.

UIKit + MVVM — pour les projets UIKit (sans SwiftUI), MVVM est implémenté via Combine et @Published avec abonnement dans UIViewController via sink(). ViewModel est le même, View est UIViewController avec des abonnements à @Published. Combine est disponible depuis iOS 13 (2019) et intégré au système — aucune dépendance supplémentaire requise. Selon Apple Developer Survey (2025), 45% des projets iOS utilisent Combine même avec UIKit, 35% utilisent SwiftUI + Combine, 20% utilisent RxSwift (legacy).

Comparaison de MVVM avec MVP : avantages et inconvénients

MVVM gagne face à MVP dans trois aspects clés : absence d'interfaces ViewContract, gestion automatique des abonnements et survie à la rotation de l'écran. Dans MVP, chaque écran nécessite une interface ViewContract + classe Presenter + abonnement/désabonnement dans onStart/onStop. Dans MVVM, seul ViewModel est créé — l'abonnement dans Activity se fait via observe() sans detach() manuel.

CritèreMVPMVVM
Interfaces ViewContract1 par écranNon nécessaires
Gestion des abonnementsattach/detach manuelAutomatique (lifecycle-aware)
Rotation d'écranRetain-fragmentViewModel survit à la rotation
TestsMock ViewContractClasse propre sans dépendances
RéactivitéCallbacks dans PresenterLiveData/StateFlow/Combine

Inconvénients de MVVM — complexité du débogage des chaînes réactives et risque de fuites mémoire avec un abonnement incorrect. LiveData résout la sécurité du cycle de vie, StateFlow nécessite lifecycleScope, Combine nécessite sink avec AnyCancellable. Dans MVP, tous les appels sont explicites (view.showUser), dans MVVM les données arrivent via un flux réactif — le traçage nécessite des points d'arrêt dans les closures subscribe. Dans les grands ViewModels avec plusieurs StateFlows, vous pouvez manquer une mise à jour de l'UI si View n'est pas abonnée à un Flow spécifique.

Quand MVP est encore meilleur — dans les projets avec une version minimale d'Android inférieure à API 21 (Android 5), où Jetpack ViewModel n'est pas disponible sans AndroidX, et dans les projets utilisant UIKit pur sans Combine (iOS 12 et inférieur). Pour les projets legacy dont toute la base de code est déjà en MVP, une transition complète vers MVVM n'est pas toujours justifiée — il est moins coûteux de maintenir MVP avec une extraction progressive de la logique dans des services que de réécrire 100 écrans en 3 mois.

Tests de ViewModel sur Android et iOS

ViewModel est testé avec des tests unitaires sans dépendances plateforme — c'est l'argument principal en faveur de MVVM. Sur Android, ViewModel ne contient pas Activity, Context ou View — toutes les dépendances (Repository, UseCase) sont passées via le constructeur et remplacées par des objets mock. Sur iOS, ObservableObject est testé via XCTest sans lancer l'application, offrant stabilité et vitesse d'exécution des tests.

kotlin
// Test unitaire du ViewModel Android avec MockK
class UserViewModelTest {

    private val repository = mockk<UserRepository>()
    private val viewModel = UserViewModel(repository)

    @Test
    fun loadUser_success_updatesState() = runTest {
        val user = User(1, "John", "john@test.com")
        coEvery { repository.getUser(1) } returns Result.success(user)

        viewModel.loadUser(1)

        assertEquals(UserState.Success(user), viewModel.state.value)
    }

    @Test
    fun loadUser_error_updatesErrorState() = runTest {
        val error = RuntimeException("Network error")
        coEvery { repository.getUser(1) } returns Result.failure(error)

        viewModel.loadUser(1)

        val state = viewModel.state.value
        assertTrue(state is UserState.Error)
        assertEquals("Network error", (state as UserState.Error).message)
    }
}

ViewModel iOS est testé de manière similaire : injecter un mock UserService, appeler loadUser, vérifier l'état via XCTestExpectation. Combine Publisher est testé via XCTestCase avec wait(for: expectations, timeout: 1.0). La structure UserState — enum avec valeurs associées — permet de vérifier l'état exact de l'écran après une opération.

Couverture de code dans les projets IT Sectr utilisant MVVM est de 75 à 90% pour ViewModel et Repository. ViewModel est couvert par des tests unitaires, Repository par des tests d'intégration avec une base de données de test. View dans SwiftUI et Jetpack Compose est testée avec des tests UI (XCUITest, Compose Test) pour les scénarios critiques. Le reste de l'UI est vérifié avec des tests de capture d'écran (Snapshot Testing) — c'est plus rapide que les tests UI et offre 95% de confiance dans l'affichage correct.

Questions fréquentes

Quelle est la principale différence entre MVVM et MVP ?

Dans MVVM, ViewModel n'a pas de référence à View — les données sont transmises via des mécanismes réactifs (LiveData, StateFlow, @Published). Dans MVP, Presenter appelle directement les méthodes de View via l'interface ViewContract. MVVM élimine ViewContract et l'attach/detach manuel, mais nécessite la compréhension des flux réactifs. ViewModel survit à la rotation de l'écran sur Android, Presenter nécessite un retain-fragment.

Quelles bibliothèques sont nécessaires pour MVVM sur Android ?

Ensemble minimum : lifecycle-viewmodel-ktx (ViewModel), lifecycle-livedata-ktx ou kotlinx-coroutines-core (StateFlow). Pour l'injection — Hilt ou Koin. Pour les opérations asynchrones — Kotlin Coroutines. Pour la liaison de données complexe — DataBinding. Dans Jetpack Compose (recommandé par Google depuis 2022), compose-runtime et lifecycle-viewmodel-compose suffisent.

Pourquoi Apple recommande-t-il MVVM pour iOS ?

SwiftUI (2019) est conçu pour une architecture réactive : @State et @Published réaffichent automatiquement View lorsque les données changent. MVVM est un ajustement naturel pour SwiftUI : View — @ViewBuilder, ViewModel — ObservableObject. Apple n'impose pas MVVM comme unique modèle, mais tous les supports de formation depuis 2019 utilisent ViewModel + SwiftUI. Pour UIKit, Apple recommande MVC ou Coordinator.

Comment éviter les fuites mémoire dans ViewModel ?

Android : viewModelScope annule automatiquement les coroutines lors du nettoyage de ViewModel. iOS : AnyCancellable de Combine se désabonne automatiquement lors de la libération de l'objet le contenant. SwiftUI @StateObject gère le cycle de vie automatiquement. Règles principales : ne pas stocker de références à View/Context dans ViewModel, annuler les opérations longues lors du nettoyage, utiliser weak self dans les closures.

Que choisir : LiveData ou StateFlow pour Android ?

StateFlow est le choix moderne. LiveData est plus simple et sûr pour le cycle de vie, mais StateFlow est plus puissant : fonctionne avec les coroutines, prend en charge flatMap, combine, filter, ne nécessite pas d'annotation @Nullable. Le seul scénario où LiveData est préférable — travailler avec du code Java où StateFlow (Kotlin Flow API) n'est pas disponible. Google recommande StateFlow pour les nouveaux projets Kotlin.

Résumé

  • MVVM (Model-View-ViewModel) — modèle réactif où ViewModel n'a pas de référence à View
  • ViewModel — survit à la rotation de l'écran, testable avec des tests unitaires, indépendant de l'UI
  • Android — ViewModel + StateFlow + Kotlin Coroutines — stack moderne de Google
  • iOS — ObservableObject + @Published + SwiftUI — implémentation native de MVVM
  • MVVM vs MVP — MVVM élimine ViewContract et l'attach/detach manuel
  • Tests — ViewModel est couvert par des tests unitaires sans dépendances plateforme
  • Recommandation — MVVM pour les nouveaux projets ; MVP pour le support legacy

Nous développerons une application mobile clé en main

IT Sectr crée des applications iOS et Android pour les startups et les entreprises depuis 2017. Nous vous conseillerons et vous proposerons la meilleure solution.

Discuter du projet

Lisez aussi