Skip to main content
Version: 4.2

Android ViewModel

This page covers Android-specific ViewModel features. For core ViewModel DSL and multiplatform support, see ViewModel.

Overview

ViewModels are architecture components designed to survive configuration changes and manage UI-related data. Koin provides special support for ViewModels with lifecycle-aware injection.

Key Concepts

  • Survives Configuration Changes - ViewModels persist through rotations and theme changes
  • Scoped to Lifecycle - Tied to Activity, Fragment, or Navigation Graph lifecycle
  • Lazy Creation - Created only when first accessed
  • Shared Instances - Can be shared between Fragments and their host Activity
info

Multiplatform ViewModel - Koin ViewModel DSL is fully multiplatform via koin-core-viewmodel. For Compose Multiplatform, see Compose ViewModel.

ViewModel Scope Limitations

warning

Important: ViewModels are created against the root Koin scope and cannot access Activity or Fragment scoped dependencies. This prevents memory leaks as ViewModels outlive Activities and Fragments.

Need scoped dependencies in ViewModel? Use ViewModel Scope to create a dedicated scope tied to your ViewModel's lifecycle.

Declaring ViewModels

Compiler Plugin DSL

val appModule = module {
viewModel<DetailViewModel>()
viewModel<UserViewModel>()
}

Annotations

@KoinViewModel
class DetailViewModel(
private val repository: DetailRepository
) : ViewModel()

@KoinViewModel
class UserViewModel(
private val userRepository: UserRepository
) : ViewModel()

Classic DSL

val appModule = module {
// With constructor reference
viewModelOf(::DetailViewModel)

// With lambda
viewModel { DetailViewModel(get()) }
}

Injecting ViewModels

In Activity, Fragment or Service, use:

  • by viewModel() - lazy delegate property
  • getViewModel() - eager fetch
class DetailActivity : AppCompatActivity() {

// Lazy inject ViewModel
private val viewModel: DetailViewModel by viewModel()

// Or eager
// private val viewModel: DetailViewModel = getViewModel()
}

Shared ViewModel (Activity)

Share a ViewModel between Fragments and their host Activity:

  • by activityViewModel() - lazy delegate for shared ViewModel
  • getActivityViewModel() - eager fetch
class WeatherActivity : AppCompatActivity() {
private val weatherViewModel: WeatherViewModel by viewModel()
}

class WeatherHeaderFragment : Fragment() {
// Shared with Activity
private val weatherViewModel: WeatherViewModel by activityViewModel()
}

class WeatherListFragment : Fragment() {
// Same instance as WeatherHeaderFragment
private val weatherViewModel: WeatherViewModel by activityViewModel()
}

Passing Parameters

Compiler Plugin DSL

class DetailViewModel(
@InjectedParam val itemId: String,
private val repository: DetailRepository
) : ViewModel()

val appModule = module {
viewModel<DetailViewModel>()
}

Annotations

@KoinViewModel
class DetailViewModel(
@InjectedParam val itemId: String,
private val repository: DetailRepository
) : ViewModel()

Classic DSL

val appModule = module {
viewModel { params ->
DetailViewModel(
itemId = params.get(),
repository = get()
)
}
}

Injection Site

class DetailActivity : AppCompatActivity() {

private val itemId: String by lazy { intent.getStringExtra("ITEM_ID")!! }

// Pass parameter at injection
private val viewModel: DetailViewModel by viewModel { parametersOf(itemId) }
}

SavedStateHandle

Add SavedStateHandle to your ViewModel constructor - Koin injects it automatically:

Annotations

@KoinViewModel
class MyStateViewModel(
private val handle: SavedStateHandle,
private val repository: MyRepository
) : ViewModel()

DSL

class MyStateViewModel(
private val handle: SavedStateHandle,
private val repository: MyRepository
) : ViewModel()

val appModule = module {
viewModel<MyStateViewModel>() // Compiler Plugin DSL
// or
viewModelOf(::MyStateViewModel) // Classic DSL
}

Usage

class DetailActivity : AppCompatActivity() {
// SavedStateHandle automatically injected
private val viewModel: MyStateViewModel by viewModel()
}
info

All stateViewModel functions are deprecated. Use the regular viewModel function - SavedStateHandle is injected automatically.

Scope a ViewModel to a Navigation graph:

class NavFragment : Fragment() {

// Scoped to navigation graph
private val navViewModel: NavViewModel by koinNavGraphViewModel(R.id.my_graph)
}

The ViewModel is:

  • Created when first fragment in graph accesses it
  • Shared across all fragments in the graph
  • Destroyed when navigation graph is popped

ViewModel with Scoped Dependencies

If your ViewModel needs its own scoped dependencies, use ViewModel Scope:

val appModule = module {
viewModelScope {
scoped<UserCache>()
scoped<UserRepository>()
viewModel<UserViewModel>()
}
}
@ViewModelScope
class UserCache

@ViewModelScope
class UserRepository(private val cache: UserCache)

@KoinViewModel
@ViewModelScope
class UserViewModel(
private val repository: UserRepository
) : ViewModel()

ViewModel Generic API

For advanced use cases, Koin provides lower-level APIs:

// From ComponentActivity or Fragment
val viewModel = viewModelForClass(
clazz = MyViewModel::class,
qualifier = null,
owner = this,
key = null,
parameters = { parametersOf("param") }
)

Java Compatibility

Add the compat dependency:

implementation "io.insert-koin:koin-android-compat:$koin_version"

Use ViewModelCompat static methods:

MyViewModel viewModel = ViewModelCompat.getViewModel(this, MyViewModel.class);

Quick Reference

ActionCode
Declare ViewModelviewModel<MyVM>() / @KoinViewModel
Inject in Activity/Fragmentby viewModel()
Share with Activityby activityViewModel()
Pass parametersby viewModel { parametersOf(id) }
Navigation graph scopeby koinNavGraphViewModel(R.id.graph)
With SavedStateHandleJust add to constructor

Next Steps