viewModelScope ist ein integrierter CoroutineScope aus der Bibliothek androidx.lifecycle, der an den Lebenszyklus von ViewModel gebunden ist und bei dessen Bereinigung automatisch abgebrochen wird. Laut Google Android Developers, 2025 ist viewModelScope der Standardmechanismus zum Starten von Coroutinen in der MVVM-Architektur und gewährleistet sichere asynchrone Operationen ohne Risiko von Speicherlecks. ViewModelScope verwendet standardmäßig Dispatchers.Main, und alle IO-Operationen darin müssen über withContext ausgeführt werden.
Wichtige Punkte
viewModelScope ist eine Erweiterungseigenschaft für das ViewModel-Interface, die in der Bibliothek lifecycle-viewmodel-ktx (ab Version 2.1.0) hinzugefügt wurde. Sie bietet einen gebrauchsfertigen CoroutineScope, der an den Lebenszyklus von ViewModel gebunden ist.
// Internal structure (simplified)
val ViewModel.viewModelScope: CoroutineScope
get() {
val scope = this.getTag(JOB_KEY)
if (scope != null) return scope
return CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
.also { setTag(JOB_KEY, it) }
}
Der Scope wird beim ersten Zugriff lazy erstellt und über setTag zwischengespeichert. Er verwendet SupervisorJob, was bedeutet, dass eine Ausnahme in einer Child-Coroutine die anderen nicht abbricht. Der Standard-Dispatcher ist Dispatchers.Main.immediate, der Code auf dem Hauptthread ohne zusätzliche Dispatchierung ausführt, wenn bereits auf Main.
Wenn die ViewModel den Lebenszyklus verlässt (Activity beendet oder Fragment entfernt), ruft das System clear() auf, das onCleared() auslöst. In diesem Callback bricht viewModelScope seinen Job ab, der rekursiv alle aktiven Coroutinen beendet. Der Mechanismus wird über die Closeable-Schnittstelle implementiert, bei der der Job des Scopes als Ressource für das automatische Schließen registriert wird.
Der Mechanismus der Bindung von viewModelScope an den ViewModel-Lebenszyklus basiert auf Tagging und dem onCleared-Callback. Betrachten wir es Schritt für Schritt.
Wenn die ViewModel viewModelScope.launch { ... } ausführt, prüft der Getter, ob bereits ein Scope unter dem Tag JOB_KEY gespeichert ist. Wenn kein Scope existiert, wird eine neue Instanz von CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate) erstellt. Der Scope wird über eine interne Tag-Map innerhalb der ViewModel gespeichert.
Alle über viewModelScope.launch oder viewModelScope.async gestarteten Coroutinen werden zu Kindern des SupervisorJob des Scopes. Sie laufen auf dem Hauptthread (sofern kein anderer Dispatcher über withContext angegeben wurde). Solange die ViewModel lebt, können Coroutinen aktiv, suspendiert oder abgeschlossen sein.
Wenn das System die ViewModel zerstört, wird ViewModel.clear() aufgerufen. Innerhalb von clear() geschieht Folgendes:
Bei Bildschirmrotation wird die Activity neu erstellt, aber die ViewModel überlebt (dank ViewModelStoreOwner). Das bedeutet, dass viewModelScope aktiv bleibt und Coroutinen ohne Unterbrechung weiterlaufen. Nach der Neuerstellung der Activity wird dieselbe ViewModel (und derselbe Scope) wiederverwendet — das Laden von Daten beginnt nicht von vorne.
MVVM (Model-View-ViewModel) ist die von Google empfohlene Architektur für Android-Anwendungen. viewModelScope nimmt darin eine zentrale Rolle als Ausführender asynchroner Operationen ein.
| Schicht | Komponente | Rolle von viewModelScope |
|---|---|---|
| UI | Activity / Fragment | Beobachtet StateFlow/LiveData von ViewModel |
| ViewModel | ViewModel | Startet Coroutinen über viewModelScope, verwaltet UI-Zustand |
| Repository | Repository | Stellt suspend-Funktionen bereit, die von viewModelScope-Coroutinen aufgerufen werden |
| Data | DAO / Api | Führt eigentliche Anfragen aus (Room, Retrofit) |
Die ViewModel startet Coroutinen über viewModelScope, in denen sie suspend-Funktionen des Repository aufruft. Das Ergebnis wird in StateFlow umgewandelt, das von der UI-Schicht beobachtet wird. Dieses Design gewährleistet klare Trennung der Verantwortlichkeiten und unabhängige Testbarkeit jeder Schicht.
Wenn Coroutinen von einem Fragment aus gestartet würden, würden sie bei Bildschirmrotation mit der Zerstörung des Fragments abgebrochen. ViewModel überlebt die Rotation, daher laufen in ihrem Scope gestartete Coroutinen weiter. Dies ist der Hauptvorteil von viewModelScope gegenüber lifecycleScope beim Laden von Daten.
Betrachten wir drei praktische Szenarien zur Verwendung von viewModelScope in einer Android-Anwendung mit Kotlin.
class ProfileViewModel(
private val repo: ProfileRepository
) : ViewModel() {
private val _profile = MutableStateFlow<Profile?>(null)
val profile: StateFlow<Profile?> = _profile
init {
loadProfile()
}
private fun loadProfile() {
viewModelScope.launch {
val result = repo.getProfile()
_profile.value = result
}
}
}
Im init-Block beginnt das Laden des Profils sofort. Die Coroutine läuft auf dem Hauptthread (standardmäßig). Das Repository verwendet withContext(Dispatchers.IO) für die Netzwerkanfrage innerhalb seiner suspend-Funktion, sodass sich die ViewModel nicht um Thread-Wechsel kümmern muss.
sealed class UiState {
object Loading : UiState()
data class Success(val data: List<Item>) : UiState()
data class Error(val message: String) : UiState()
}
fun fetchItems() {
_state.value = UiState.Loading
viewModelScope.launch {
try {
val items = repo.getItems()
_state.value = UiState.Success(items)
} catch (e: Exception) {
_state.value = UiState.Error(e.message ?: "Unknown error")
}
}
}
Der UI-Zustand wird durch eine sealed class UiState beschrieben. Die ViewModel aktualisiert den Zustand bei jeder Änderung. Das Fragment abonniert StateFlow und reagiert nur auf den aktuellen Zustand, wobei veraltete Aufrufe von früheren Rotationen ignoriert werden.
private var searchJob: Job? = null
fun search(query: String) {
searchJob?.cancel()
searchJob = viewModelScope.launch {
delay(300)
val results = repo.search(query)
_searchResults.value = results
}
}
Bei jeder neuen Suchanfrage wird die vorherige Coroutine abgebrochen. delay(300) implementiert Entprellung — die Suche wird erst nach 300 ms Inaktivität ausgeführt. Dies reduziert die Serverlast und verhindert veraltete Ergebnisse.
Beide Scopes werden von der AndroidX Lifecycle-Bibliothek bereitgestellt, sind jedoch an unterschiedliche Lebenszyklen gebunden. Die Wahl hängt von der Art der Aufgabe ab.
| Merkmal | viewModelScope | lifecycleScope |
|---|---|---|
| Besitzer | ViewModel | LifecycleOwner (Activity/Fragment) |
| Abbruch bei Rotation | Nein (ViewModel überlebt) | Ja (Activity wird neu erstellt) |
| Standard-Dispatcher | Dispatchers.Main.immediate | Dispatchers.Main.immediate |
| Verfügbar in | ViewModel | Activity, Fragment, Service |
| Typischer Anwendungsfall | Daten laden, Geschäftslogik | UI-Interaktionen, Animationen |
Google empfiehlt die Verwendung von viewModelScope für alle Aufgaben zum Laden und Verarbeiten von Daten. lifecycleScope sollte für Operationen verwendet werden, die an einen bestimmten Moment des UI-Lebenszyklus gebunden sind — zum Beispiel das Starten einer Animation beim ersten Erscheinen des Bildschirms oder das Abonnieren von Standortaktualisierungen, die beim Verlassen des Bildschirms gestoppt werden sollen.
Selbst in einer gut dokumentierten Android-API machen Entwickler typische Fehler. Betrachten wir vier der häufigsten Probleme.
Der heimtückischste Fehler ist der Versuch, StateFlow oder LiveData zu aktualisieren, nachdem die ViewModel bereinigt wurde. Obwohl viewModelScope bei onCleared() abgebrochen wird, kann eine Coroutine Code ausführen, bevor der Abbruch wirksam wird. Verwenden Sie isActive zur Überprüfung oder verlassen Sie sich auf den Abschluss des catch-Blocks.
viewModelScope verwendet intern SupervisorJob, das Fehler zwischen Coroutinen isoliert. Wenn Sie jedoch eine Coroutine mit einem eigenen Job() innerhalb von viewModelScope.launch starten, wird diese Coroutine zu einem Kind von SupervisorJob, ist aber nicht vor Abbruch durch Fehler in anderen Coroutinen geschützt.
Obwohl viewModelScope keine strikte Grenze hat, können tausende aktive Coroutinen das System verlangsamen. Verwenden Sie für lange Datenlisten Flow mit collectLatest anstatt separate Coroutinen für jedes Element zu erstellen.
Wenn versehentlich GlobalScope statt viewModelScope importiert wird, wird die Coroutine beim Bereinigen der ViewModel nicht abgebrochen. Dies führt zu Speicherlecks und potenziellen Abstürzen. Stellen Sie immer sicher, dass Coroutinen über viewModelScope gestartet werden, insbesondere in Fragment-Unterklassen.
Häufig gestellte Fragen
Sie können den Dispatcher von viewModelScope nicht direkt ändern — er ist fest als Dispatchers.Main.immediate festgelegt. Innerhalb einer Coroutine können Sie jedoch über withContext zu einem anderen Dispatcher wechseln. Um den Dispatcher in Tests zu ändern, verwenden Sie TestDispatcher über eine Rule.
Übergeben Sie den Scope nicht an das Repository — dies verstößt gegen Architekturprinzipien. Das Repository sollte suspend-Funktionen bereitstellen, und die ViewModel selbst verwaltet Coroutinen über viewModelScope. Wenn das Repository einen Scope benötigt, überdenken Sie die Architektur zugunsten von Clean Architecture.
SupervisorJob stellt sicher, dass eine Ausnahme in einer Coroutine (z. B. ein Ladefehler in einer von mehreren unabhängigen Anfragen) die anderen Coroutinen nicht abbricht. Dies entspricht dem ViewModel-Szenario, in dem verschiedene Bildschirme unabhängige Daten laden.
Ja, viewModelScope ist in jeder ViewModel verfügbar, unabhängig vom UI-Typ (View System oder Jetpack Compose). In Compose werden Coroutinen ebenfalls über viewModelScope gestartet, während für UI-Effekte LaunchedEffect und rememberCoroutineScope verwendet werden.
Der Aufruf von viewModelScope.cancel() bricht den Scope sofort ab — alle aktiven Coroutinen werden mit CancellationException beendet. Wenn danach viewModelScope.launch aufgerufen wird, wird beim nächsten Zugriff auf den Getter automatisch ein neuer Scope erstellt.
Zusammenfassung
Wir entwickeln eine mobile Applikation schlüsselfertig
IT Sectr entwickelt seit 2017 iOS- und Android-Apps für Startups und Unternehmen. Wir beraten Sie und schlagen die beste Lösung vor.
Lesen Sie auch