androidx.hilt:hilt-*:1.4.0 landed stable on July 1, 2026. It's a smaller release than the jump from 1.1 to 1.2, but the two changes it ships matter more than the version number suggests — especially if you're using Lifecycle 2.11's rememberViewModelStoreOwner. The two libraries were clearly designed to work together, and 1.4.0 is the piece that makes that pairing actually clean to write.
I've been running Hilt in Nodat since day one, first with manual Dagger components and later migrating the full DI graph to Hilt's generated components. So I've lived through every Hilt API evolution from the early ViewModelInject days to the SavedStateHandle integration to the Compose artifact split in 1.3.0. This one is smaller in scope than those, but it's the kind of change that makes existing patterns less awkward — which matters when you're wiring together a lot of ViewModels across deep navigation hierarchies.
What Actually Changed From 1.3.x to 1.4.0
The headline API change is rememberHiltViewModelFactory() — a composable function that gives you a Hilt-aware ViewModelProvider.Factory you can pass around manually. In 1.3.x, this function took a ViewModelStoreOwner parameter. In 1.4.0, that parameter is gone.
The old signature forced you to pass in a ViewModelStoreOwner at the call site, which created a circular dependency when using it with rememberViewModelStoreOwner from Lifecycle 2.11 — you'd be creating the store owner in one expression and trying to pass it to the factory in the same expression. The new signature resolves the store owner internally, which removes that tension entirely.
// 1.3.x — you had to pass the store owner explicitly
val storeOwner = rememberViewModelStoreOwner()
val factory = rememberHiltViewModelFactory(viewModelStoreOwner = storeOwner)
// 1.4.0 — factory resolves context on its own
val storeOwner = rememberViewModelStoreOwner()
val factory = rememberHiltViewModelFactory()
val viewModel: MyViewModel = viewModel(
viewModelStoreOwner = storeOwner,
factory = factory
)
Less noise at the call site, and the semantics are cleaner: the factory knows how to source a Hilt component from the current composition context without you threading a store owner through both sides of the call.
The second parameter, delegateFactory, remains for cases where you need to compose the Hilt factory with a custom factory — for example, if a ViewModel has assisted injection parameters that Hilt can't provide on its own. That pattern hasn't changed; only the store owner threading has been removed.
The Combination That Actually Matters: Hilt + rememberViewModelStoreOwner for Pager Pages
When I wrote about Lifecycle 2.11, I covered rememberViewModelStoreOwner as the solution for scoping ViewModels to individual pages in a Compose Pager. The problem is real: without a scoped store owner, every page in a HorizontalPager shares the same ViewModel instance, and composing multiple pages with independent state becomes fragile. Lifecycle 2.11 gave you the mechanism. Hilt 1.4.0 completes it for Hilt users.
Here's what the full pattern looks like now:
@Composable
fun ArticlePager(articles: List) {
val pagerState = rememberPagerState(pageCount = { articles.size })
HorizontalPager(state = pagerState) { page ->
// Each page gets its own ViewModel scope, survived across config changes
val pageStoreOwner = rememberViewModelStoreOwner()
val hiltFactory = rememberHiltViewModelFactory()
val viewModel: ArticleViewModel = viewModel(
viewModelStoreOwner = pageStoreOwner,
factory = hiltFactory
)
ArticlePage(viewModel = viewModel, article = articles[page])
}
}
What this gives you is genuine per-page ViewModel isolation with full Hilt injection support. Each page's ArticleViewModel is scoped to that page's store owner. It survives configuration changes (the store owner is remembered by Compose across recompositions). When the page is removed from the Pager, its store owner is cleared and onCleared() fires. And because rememberHiltViewModelFactory() pulls the Hilt component from the current composition context, SavedStateHandle and any @HiltViewModel-injected dependencies resolve correctly without any manual wiring.
Nodat context: I'm using this pattern for the note editor pages in Nodat, where each page in a tabbed session needs its own draft state, undo history, and sync status — all injected via Hilt. Pre-Lifecycle-2.11 I was managing this with a manual Map<Int, ViewModelStore>, which worked but required explicit lifecycle cleanup. rememberViewModelStoreOwner makes that whole approach unnecessary.
The one thing to be aware of: rememberViewModelStoreOwner() is keyed by the composition local by default. If you're in a Pager, you want each page to have a distinct key — otherwise pages at the same index slot share the same store. Pass a stable, unique key that identifies the page content (not just the page index, since the Pager can reuse index positions as you scroll far enough):
val pageStoreOwner = rememberViewModelStoreOwner(key = articles[page].id)
That one line is the difference between "each page has truly independent state" and "pages sometimes share state in ways you can't predict." Something worth checking explicitly if you migrate to this pattern from an existing approach.
The Compose Artifact Split — If You Haven't Migrated Yet
Hilt 1.3.0, which shipped in September 2025, moved the Compose-specific APIs — hiltViewModel() and the new rememberHiltViewModelFactory() — to a dedicated artifact:
// Before 1.3.0 — everything in one artifact
implementation("androidx.hilt:hilt-navigation-compose:1.2.0")
// 1.3.0+ — Compose ViewModel API now separate from Navigation
implementation("androidx.hilt:hilt-navigation-compose:1.4.0") // for NavHost integration
implementation("androidx.hilt:hilt-lifecycle-viewmodel-compose:1.4.0") // for rememberHiltViewModelFactory
The reason for the split: hilt-navigation-compose pulled in androidx.navigation as a transitive dependency, which meant any app using hiltViewModel() in a non-navigation context was picking up the entire Navigation artifact for no reason. The new hilt-lifecycle-viewmodel-compose artifact has no navigation dependency — it's just the factory and ViewModel integration.
If you're on 1.2.0 and upgrading to 1.4.0, the migration is mechanical:
- If you use
hiltViewModel()inside a NavHost: keephilt-navigation-compose, bump the version. - If you use
hiltViewModel()without Navigation, or if you're switching torememberHiltViewModelFactory()manually: addhilt-lifecycle-viewmodel-compose, drophilt-navigation-composeif you don't need it for the NavHost integration. - The package is different:
androidx.hilt.lifecycle.viewmodel.composeinstead of the old navigation package. IntelliJ will flag the unresolved import and offer the fix.
Most apps will need both artifacts: hilt-navigation-compose for hiltViewModel() at NavHost-scoped screens, and hilt-lifecycle-viewmodel-compose for rememberHiltViewModelFactory() in manual scoping scenarios like the Pager pattern above. They can coexist without issues.
Toolchain Requirements — Check These Before Bumping
Hilt 1.4.0 has stricter toolchain minimums than 1.3.x:
- Kotlin Gradle Plugin (KGP) 2.2.0 or higher — required from 1.4.0-alpha01. If you're on KGP 2.1.x, you'll need to bump Kotlin too. Kotlin 2.2.0 is stable and the upgrade path from 2.1.x is clean.
- AGP 9.2.0 or higher when using Compose — the Compose module updated its
compileSdkto API 37 in 1.4.0-beta01, which requires AGP 9.2.0 minimum. If you're still on AGP 8.x, this is a larger upgrade chain. AGP 9.x migration has its own set of default property flips and API changes — worth a separate read before taking it on. - Dagger/Hilt (Google) version: The AndroidX Hilt wrapper (
androidx.hilt:*) at 1.4.0 tracks the underlying Dagger Hilt version independently. As of this writing,com.google.dagger:hilt-androidis at 2.57.x. Make sure your Dagger Hilt version is recent enough to be compatible — the AndroidX release notes call out version requirements if there are hard constraints.
// build.gradle.kts (project-level)
plugins {
id("com.google.dagger.hilt.android") version "2.57" apply false
kotlin("android") version "2.2.0" apply false // minimum for Hilt 1.4.0
}
// build.gradle.kts (app module)
dependencies {
implementation("androidx.hilt:hilt-navigation-compose:1.4.0")
implementation("androidx.hilt:hilt-lifecycle-viewmodel-compose:1.4.0")
kapt("com.google.dagger:hilt-android-compiler:2.57")
// or ksp(...) if you've already switched KSP
}
KSP vs KAPT
If you haven't switched from KAPT to KSP for Hilt yet, now is a reasonable time. Dagger's KSP support has been stable since 2.50 and build times improve materially — on Nodat's module graph, switching to KSP cut incremental build times by roughly 20-30% for files that touch Hilt-generated components. The swap is a one-line change in each module's build file, and the generated code is identical.
Should You Upgrade to 1.4.0 Now?
Yes — this is a stable release, not an alpha or RC. The API changes are backward-compatible: hiltViewModel() and existing @HiltViewModel classes work identically. The only things that break are the removed ViewModelStoreOwner parameter on rememberHiltViewModelFactory() (a compile error you'll catch immediately) and the new minimum toolchain versions (a Gradle sync error you'll catch immediately).
The upgrade path that makes sense for most teams: bump to 1.4.0, fix the toolchain versions if needed, update any direct calls to the old rememberHiltViewModelFactory(viewModelStoreOwner = ...)` signature, and verify the artifact dependencies are correct after the 1.3.0 split. None of this is risky. All of it is mechanical.
The rememberViewModelStoreOwner + rememberHiltViewModelFactory combination for Pager-scoped ViewModels is the pattern I'd push teams toward if they've been faking it with manual stores or accepting shared ViewModel state across pages. Now that both Lifecycle 2.11 and Hilt 1.4.0 are stable, the whole stack is production-ready — no experimental flags, no RC caveats.
No comments yet. Be the first to leave one!