Android Coden
Android 4 min lesen

Hilt Scopes: Abhängigkeiten richtig scopen

Hilt Scopes steuern, wie lange eine Abhängigkeit im Speicher lebt. Lerne, welchen Scope du wann einsetzt.

Hilt macht Dependency Injection in Android deutlich einfacher – aber erst wenn du verstehst, wie lange eine injizierte Abhängigkeit leben soll, holst du wirklich alles heraus. Genau das regeln Hilt Scopes: Sie legen fest, ob Hilt bei jeder Injection eine neue Instanz erzeugt oder ob es dieselbe Instanz wiederverwenden soll. Die richtige Wahl entscheidet über Speicherverbrauch, Korrektheit und Testbarkeit deiner App.

Was ist das?

Ein Scope in Hilt ist eine Annotation, die den Lebenszyklus einer Abhängigkeit an einen bestimmten Android-Komponenten-Lebenszyklus koppelt. Ohne Scope-Annotation erzeugt Hilt bei jeder Injection eine brandneue Instanz – das ist das Standardverhalten, auch „unscoped” genannt. Mit einer Scope-Annotation merkt sich Hilt die erste erzeugte Instanz und gibt sie für alle weiteren Injections im selben Scope-Kontext zurück.

In der Praxis bedeutet das: Hält eine Klasse teuren Zustand – etwa eine Netzwerkverbindung, einen gecachten Datensatz oder ein Room-Datenbankhandle –, dann willst du nicht, dass sie mehrfach instantiiert wird. Mit dem richtigen Scope vermeidest du redundante Objekte und kontrollierst gezielt, wann Ressourcen freigegeben werden.

Hilt liefert für die Standard-Android-Komponenten fertige Scopes: @Singleton, @ActivityRetainedScoped, @ViewModelScoped, @ActivityScoped, @FragmentScoped und @ServiceScoped. Die wichtigsten drei für die moderne MVVM-Architektur sind die ersten drei.

Wie funktioniert es?

Hilt erzeugt intern einen Component-Graphen, und jeder Scope korrespondiert mit einem dedizierten Hilt-Component. Die folgende Übersicht zeigt die drei zentralen Scopes:

AnnotationHilt-ComponentLebenszeit
@SingletonSingletonComponentGesamte App-Laufzeit
@ActivityRetainedScopedActivityRetainedComponentActivity inkl. Rotation
@ViewModelScopedViewModelComponentGenau ein ViewModel

@Singleton

@Singleton ist der weitreichendste Scope. Eine damit annotierte Klasse wird genau einmal erzeugt und existiert, solange der Prozess läuft. Typische Kandidaten sind ein OkHttpClient, ein Room-Datenbankhandle oder ein globaler Token-Cache. Der Scope kostet keine zusätzliche Infrastruktur – Hilt hält die Instanz einfach im SingletonComponent, bis die App beendet wird.

@ActivityRetainedScoped

Dieser Scope überlebt Konfigurationsänderungen (Rotation, Locale-Wechsel), wird aber beim echten Beenden der Activity zerstört. Er passt für Abhängigkeiten, die du in mehreren ViewModels einer Activity teilst, die aber selbst kein ViewModel sein sollen – etwa ein Use-Case, der von zwei koordinierten ViewModels aufgerufen wird.

@ViewModelScoped

Mit @ViewModelScoped verbindest du eine Abhängigkeit direkt mit dem ViewModel, in das sie injiziert wird. Wird das ViewModel zerstört – zum Beispiel beim Verlassen des Back-Stacks –, räumt Hilt die scoped Abhängigkeit ebenfalls auf. Das ist der richtige Scope für Repositories oder Use-Cases, die nur innerhalb eines einzigen ViewModels gebraucht werden.

In der Praxis

Angenommen, du hast ein UserRepository, das Netzwerk- und Datenbankzugriffe bündelt. Es ist teuer zu erzeugen, soll aber nur im Kontext eines ViewModels leben:

@ViewModelScoped
class UserRepository @Inject constructor(
    private val api: UserApi,
    private val dao: UserDao
) {
    suspend fun getUser(id: String): User = api.fetchUser(id)
}

@HiltViewModel
class ProfileViewModel @Inject constructor(
    private val repo: UserRepository
) : ViewModel() {
    val userState = repo.getUser("42")
}

Hilt stellt sicher, dass UserRepository für jede ProfileViewModel-Instanz genau einmal erzeugt wird – und gemeinsam mit dem ViewModel zerstört wird, sobald der Nutzer den Screen verlässt.

Typische Stolperfalle: Repository als @Singleton deklariert

Ein häufiger Fehler ist es, ein Repository als @Singleton zu deklarieren, obwohl es einen CoroutineScope oder UI-gebundenen Zustand enthält. Das Ergebnis: Der Zustand überlebt das Navigation-Back-Stack-Pop, und nach einer Rückkehr siehst du veraltete oder falsche Daten. Umgekehrt gilt: Ein echtes @Singleton – zum Beispiel ein gecachtes Auth-Token –, das du fälschlicherweise als @ViewModelScoped deklarierst, wird bei jeder ViewModel-Erstellung neu instantiiert und verliert seinen Cache.

Faustregel: Frage dich, wann soll dieser Zustand freigegeben werden? Die Antwort gibt dir den Scope. App-weit und zustandslos → @Singleton. Überlebt Rotation, aber nicht Navigation → @ActivityRetainedScoped. Lebt genau so lang wie ein ViewModel → @ViewModelScoped.

Debugging mit dem Memory Profiler

Wenn du dir nicht sicher bist, ob dein Scope greift, öffne in Android Studio den Memory Profiler und erstelle einen Heap Dump während die App läuft. Mehrere Instanzen einer Klasse, von der du nur eine erwartest, sind ein sicherer Hinweis auf einen fehlenden oder falsch gesetzten Scope.

Fazit

Hilt Scopes sind kein Detail am Rand – sie sind der Mechanismus, der dafür sorgt, dass dein Dependency-Graph robust, speichereffizient und testbar bleibt. Nimm dir nach diesem Artikel etwas Zeit und gehe deine eigenen Module durch: Welche Klassen sind als @Singleton deklariert, obwohl sie nur in einem ViewModel gebraucht werden? Welche laufen unscoped, obwohl sie teuren Zustand halten? Ein kurzer Blick in den Memory Profiler und ein Unit-Test, der den Hilt-Graphen mit @UninstallModules isoliert prüft, zeigen dir schnell, wo Verbesserungspotenzial liegt – und festigen gleichzeitig dein Verständnis dafür, wie lange Objekte in deiner App wirklich leben sollen.

Quellen (6)
Redaktion

Geschrieben von

Redaktion

Das Redaktionsteam recherchiert und schreibt Artikel zu aktuellen Themen rund um Tech, Lifestyle und Ratgeber.