ViewModel Factory (Old vs. Newer DSL)

Old vs. New

IMPORTANT

If you use big Dependency Injection frameworks like Hilt or Koin, you can skip this. But if you're a fan of cleaner, boilerplate-free code in smaller projects, or just hate writing ViewModelProvider.Factory classes, you'll want to see this massive improvement in how we handle ViewModel injection.

Summary

I used to use an older-style Factory method to create my ViewModel which I have now moved away from completely. I'll show you my current pattern using DSL. 

Situation

I tend to avoid Dependency Injection frameworks in my project as generally they are quite small. I was often able to create ViewModels without needing any external input and so things were quite easy.

When I did need to inject something into my ViewModel, I simply used a Factory method. This was no big deal and it was quite easy to copy/paste the boilerplate code. The reason I used it for so long was it was quite common for a lot of online code resources to reference this style.

I recently discovered a much nicer way to do this and then further improved on that a little.

Code Examples

Old - Here's what the older factory looked like and how it was created

class Factory(
private val name: String
) : ViewModelProvider.Factory {

override fun <T : ViewModel> create(
modelClass: Class<T>,
extras: CreationExtras
): T {
if (modelClass.isAssignableFrom(
OldStyleFactoryViewModel::class.java)) {
val handle = extras.createSavedStateHandle()
@Suppress("UNCHECKED_CAST")
return OldStyleFactoryViewModel(
name = name,
savedStateHandle = handle
) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}

New - This is the newer DSL version

fun factory(name: String) = viewModelFactory {
initializer {
ModernDSLStyleFactoryViewModel(
name = name,
savedStateHandle = createSavedStateHandle()
)
}
}

Both are created using the same pattern so I'll just include the older one here. I've split it out into a function just so it's a little easier.

@Composable
private fun getOldFactoryVm(): OldStyleFactoryViewModel {
return viewModel(factory = OldStyleFactoryViewModel
.Factory("Old Style ViewModel Factory"))
}

In both cases, we're just injecting a string which becomes the starting value for our UI Title.

Of note is the fact that we're also passing in a savedStateHandle to deal with Process Death (previous post).

Final Improved Version!

We can make this even easier. For the modern DSL example, we can have both the factory and a get function as a companion object.

companion object {
private const val MODERN_UI_STATE_KEY = "modern_ui_state"

@VisibleForTesting
internal fun factory(name: String) = viewModelFactory {
initializer {
ModernDSLStyleFactoryViewModel(
name = name,
savedStateHandle = createSavedStateHandle()
)
}
}

@Composable
fun get(
name: String = "Modern DSL ViewModel Factory"
): ModernDSLStyleFactoryViewModel {
return viewModel(factory = factory(name))
}
}

Notice: I've added @VisibibleForTesting and internal to allow the factory to be accessed outside of a composable. 

This in turn makes creating the ViewModel and passing arguments extremely simple. I much prefer grouping everything together like this.

val viewModel= ModernDSLStyleFactoryViewModel.get()
 or
val viewModel= ModernDSLStyleFactoryViewModel.get("example text")

Full Code List

I've put the full code listing up in case you want to play with this for yourself.

Below is a screenshot of the basic UI. The UI doesn't really do anything interesting, it just proves everything works and you can test that it survives process death if interested.


Dependencies

Ensure you have the following to run the code:

Toml
[versions]
lifecycle = "2.10.0" # Originally created on 2.9.4
[libraries]
androidx-lifecycle-viewmodel-compose = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-compose", version.ref = "lifecycle" }
androidx-lifecycle-runtime-compose = { group = "androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "lifecycle" }

[
plugins]

kotlin-parcelize = { id = "org.jetbrains.kotlin.plugin.parcelize", version.ref = "kotlin" }
Project and App build.gradle.kts (add to both)
plugins {
alias(libs.plugins.kotlin.parcelize) apply false
}

Links

Repeated from above

Updated

Updated 2026-01-09 - Small quality / readability changes and tested on latest Android stable dependencies. Thank you Rainxchzed for the feedback!

Updated 2026-02-10 - Improved initializer for testing purposes. Also added external constructors (see below) (thank you again, Rainxchzed).

Bonus - If Needed - External Constructors

We can use the full qualified name if we need to create external constructors as follows:
fun modernDslStyleVmFactory(name: String) = viewModelFactory {
addInitializer(
ViewModelFactoryComparison.ModernDSLStyleFactoryViewModel::class
) {
val savedStateHandle = createSavedStateHandle()

ViewModelFactoryComparison.ModernDSLStyleFactoryViewModel(
name = name,
savedStateHandle = savedStateHandle
)
}
}

fun oldStyleVmFactory(name: String) = viewModelFactory {
addInitializer(
ViewModelFactoryComparison.OldStyleFactoryViewModel::class
) {
val savedStateHandle = createSavedStateHandle()

ViewModelFactoryComparison.OldStyleFactoryViewModel(
name = name,
savedStateHandle = savedStateHandle
)
}
}
Important Note: You would need to handle creating the SavedStateHandle manually for testing purposes. That is beyond the scope of this post.

Comments

Popular Posts