Kotlin does not understand ViewModelProviders.of(activity ?: fragment)
Asked Answered
N

3

6

Inside my Fragment I initialize a ViewModel using ViewModelProviders. I want it to take its Activity if not null, otherwise itself (Fragment).

private val viewModel: MainViewModel by lazy {
    ViewModelProviders.of(activity ?: this).get(MainViewModel::class.java)
}

None of the following functions can be called with the arguments supplied.

  • of(Fragment) defined in androidx.lifecycle.ViewModelProviders
  • of(FragmentActivity) defined in androidx.lifecycle.ViewModelPro

It seems the language does not allow me to invoke conflicting method signatures (between of(Activity) and of(Fragment). (It might be understandable, maybe the compiler has to reference only one method and cannot link to both on the same line.) Is that so?

I now have to use

    activity?.let {
        ViewModelProviders.of(it).get(MainViewModel::class.java)
    } ?: run {
        ViewModelProviders.of(this).get(MainViewModel::class.java)
    }

Is there any better way of doing this?

Neoplasty answered 15/10, 2018 at 7:27 Comment(1)
Yes, java doesn't allow this either because the method overload is chosen at compile time, Kotlin's bytecode is bound by the same restriction, although they could generate your workaround code under the hood. It would only work if there was a method that would take a common supertype of Activity and Fragment, e.g. Context. If you do that frequently, add one? A bit shorter: activity.let { it?.let { ViewModelProviders.of(it) } ?: ViewModelProviders.of(this) }.get(ViewModel::class.java)Colettecoleus
C
6

Yes, it's compiler ambiguity, because you're passing activity & this (fragment instance) at the same time which has different implementations in ViewModelProviders.

Another approach to do this is by using when condition like below, (although your approach is also good):

private val viewModel: MainViewModel by lazy {
    return@lazy when {
        activity != null -> {
            ViewModelProviders.of(activity as FragmentActivity).get(MainViewModel::class.java) // you can either pass activity object
        }
        else -> {
            ViewModelProviders.of(this).get(MainViewModel::class.java) // or pass fragment object, both are not possible at same time.
        }
    }
}
Clove answered 15/10, 2018 at 7:43 Comment(0)
D
5

if you are using androidx so should add two lines for lifecycle :

 def lifecycle_version = "2.2.0-rc02"
    implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
    kapt "androidx.lifecycle:lifecycle-compiler:$lifecycle_version"

and use it like this :

 val mainViewModel = ViewModelProvider(this).get(MainViewModel::class.java)
Diecious answered 13/12, 2019 at 19:36 Comment(1)
This is good answer. Slight change just add this (implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0')Pledgee
B
4

In addition to Jeel's answer, I'd recommend that if you commonly need to use this pattern, you should define an extension function on Fragment for it to avoid repetition. For example:

fun Fragment.getViewModelProvider() =
    activity?.let(ViewModelProviders::of) ?: ViewModelProviders.of(this)

inline fun <reified T : ViewModel> Fragment.getViewModel() = 
    getViewModelProvider().get(T::class.java)

From there, within any Fragment you can just call either:

val viewModel: MainViewModel = getViewModel()
val viewModel = getViewModel<MainViewModel>()

It'll avoid both the boilerplate of fetching the provider as well as specifying the Java class specifically.

Bronson answered 17/10, 2018 at 17:42 Comment(1)
I already made an extension like the first one you wrote. But thanks for posting this as I'm sure it would help others. Upvoted.Neoplasty

© 2022 - 2024 — McMap. All rights reserved.