Android ViewModel & Navigation
The koin-android
Gradle module introduces a new viewModel
DSL keyword that comes in complement of single
and factory
, to help declare a ViewModel
component and bind it to an Android Component lifecycle. The viewModelOf
keyword is also available, to let you declare a ViewModel with its constructor.
val appModule = module {
// ViewModel for Detail View
viewModel { DetailViewModel(get(), get()) }
// or directly with constructor
viewModelOf(::DetailViewModel)
}
Your declared component must at least extends the android.arch.lifecycle.ViewModel
class. You can specify how you inject the constructor of the class
and use the get()
function to inject dependencies.
The viewModel
/viewModelOf
keyword helps to declare a factory instance of ViewModel. This instance will be handled by internal ViewModelFactory and reattach ViewModel instance if needed.
It also will let inject parameters.
Injecting your ViewModel
To inject a ViewModel in an Activity
, Fragment
or Service
use:
by viewModel()
- lazy delegate property to inject a ViewModel into a propertygetViewModel()
- directly get the ViewModel instance
class DetailActivity : AppCompatActivity() {
// Lazy inject ViewModel
val detailViewModel: DetailViewModel by viewModel()
}
ViewModel key is calculated against Key and/or Qualifier
Activity Shared ViewModel
One ViewModel instance can be shared between Fragments and their host Activity.
To inject a shared ViewModel in a Fragment
use:
by activityViewModel()
- lazy delegate property to inject shared ViewModel instance into a propertygetActivityViewModel()
- directly get the shared ViewModel instance
The sharedViewModel
is deprecated in favor of activityViewModel()
functions. The naming of this last one is more explicit.
Just declare the ViewModel only once:
val weatherAppModule = module {
// WeatherViewModel declaration for Weather View components
viewModel { WeatherViewModel(get(), get()) }
}
Note: a qualifier for a ViewModel will be handled as a ViewModel's Tag
And reuse it in Activity and Fragments:
class WeatherActivity : AppCompatActivity() {
/*
* Declare WeatherViewModel with Koin and allow constructor dependency injection
*/
private val weatherViewModel by viewModel<WeatherViewModel>()
}
class WeatherHeaderFragment : Fragment() {
/*
* Declare shared WeatherViewModel with WeatherActivity
*/
private val weatherViewModel by activityViewModel<WeatherViewModel>()
}
class WeatherListFragment : Fragment() {
/*
* Declare shared WeatherViewModel with WeatherActivity
*/
private val weatherViewModel by activityViewModel<WeatherViewModel>()
}
Passing Parameters to Constructor
the viewModel
keyword and injection API is compatible with injection parameters.
In the module:
val appModule = module {
// ViewModel for Detail View with id as parameter injection
viewModel { parameters -> DetailViewModel(id = parameters.get(), get(), get()) }
// ViewModel for Detail View with id as parameter injection, resolved from graph
viewModel { DetailViewModel(get(), get(), get()) }
// or Constructor DSL
viewModelOf(::DetailViewModel)
}
From the injection call site:
class DetailActivity : AppCompatActivity() {
val id : String // id of the view
// Lazy inject ViewModel with id parameter
val detailViewModel: DetailViewModel by viewModel{ parametersOf(id)}
}
SavedStateHandle Injection (3.3.0)
Add a new property typed SavedStateHandle
to your constructor to handle your ViewModel state:
class MyStateVM(val handle: SavedStateHandle, val myService : MyService) : ViewModel()
In Koin module, just resolve it with get()
or with parameters:
viewModel { MyStateVM(get(), get()) }
or with Constructor DSL:
viewModelOf(::MyStateVM)
To inject a state ViewModel in a Activity
,Fragment
use:
by viewModel()
- lazy delegate property to inject state ViewModel instance into a propertygetViewModel()
- directly get the state ViewModel instance
class DetailActivity : AppCompatActivity() {
// MyStateVM viewModel injected with SavedStateHandle
val myStateVM: MyStateVM by viewModel()
}
All stateViewModel
functions are deprecated. You can just use the regular viewModel
function to inject a SavedStateHandle
Navigation Graph ViewModel
You can scope a ViewModel instance to your Navigation graph. Just retrieve with by koinNavGraphViewModel()
. You just need your graph id.
class NavFragment : Fragment() {
val mainViewModel: NavViewModel by koinNavGraphViewModel(R.id.my_graph)
}
ViewModel Scope API
see all API to be used for ViewModel and Scopes: ViewModel Scope
ViewModel Generic API
Koin provides some "under the hood" API to directly tweak your ViewModel instance. The available functions are viewModelForClass
for ComponentActivity
and Fragment
:
ComponentActivity.viewModelForClass(
clazz: KClass<T>,
qualifier: Qualifier? = null,
owner: ViewModelStoreOwner = this,
state: BundleDefinition? = null,
key: String? = null,
parameters: ParametersDefinition? = null,
): Lazy<T>
This function is still using state: BundleDefinition
, but will convert it to CreationExtras
Note that you can have access to the top level function, callable from anywhere:
fun <T : ViewModel> getLazyViewModelForClass(
clazz: KClass<T>,
owner: ViewModelStoreOwner,
scope: Scope = GlobalContext.get().scopeRegistry.rootScope,
qualifier: Qualifier? = null,
state: BundleDefinition? = null,
key: String? = null,
parameters: ParametersDefinition? = null,
): Lazy<T>
ViewModel API - Java Compat
Java compatibility must be added to your dependencies:
// Java Compatibility
implementation "io.insert-koin:koin-android-compat:$koin_version"
You can inject the ViewModel instance to your Java codebase by using viewModel()
or getViewModel()
static functions from ViewModelCompat
:
@JvmOverloads
@JvmStatic
@MainThread
fun <T : ViewModel> getViewModel(
owner: ViewModelStoreOwner,
clazz: Class<T>,
qualifier: Qualifier? = null,
parameters: ParametersDefinition? = null
)