How can I write a extension function to instantiate a AndroidViewModel in Kotlin?

1.3k views Asked by At

Somebody wrote two extension function (Code A2) for both Fragment and FragmentActivity to instantiate a ViewModel, it works well, you can see Code A1 and Code A3.

I hope to write two extension function (Code B2) for both Fragment and FragmentActivity to instantiate a AndroidViewModel, you can see Code B1 and Code B3, how can I do? Thanks!

Code A1

class HomeViewModel_A(private val mDBVoiceRepository: DBVoiceRepository) :  ViewModel() {

}

Code A2

inline fun <reified T : ViewModel> Fragment.getViewModel(noinline creator: (() -> T)? = null): T {
    return if (creator == null)
        ViewModelProvider(this).get(T::class.java)
    else
        ViewModelProvider(this, BaseViewModelFactory(creator)).get(T::class.java)
}

inline fun <reified T : ViewModel> FragmentActivity.getViewModel(noinline creator: (() -> T)? = null): T {
    return if (creator == null)
        ViewModelProvider(this).get(T::class.java)
    else
        ViewModelProvider(this, BaseViewModelFactory(creator)).get(T::class.java)
}

Code A3

class FragmentHome : Fragment() {
    private val mHomeViewModel_A by lazy {
        getViewModel {
            HomeViewModel_A(provideRepository(mContext))
        }
    }

}

Code B1

class HomeViewModel_B(application: Application,private val mDBVoiceRepository: DBVoiceRepository) :  AndroidViewModel(application)  {

}

Code B2

?

Code B3

class FragmentHome : Fragment() {
  private val mHomeViewModel_B by lazy {     
       ?      
  }
}
2

There are 2 answers

3
Tenfour04 On BEST ANSWER

The Ktx Fragments library already has a function for concisely creating a lazy delegate to retrieve a view model: Fragment.viewModels() and FragmentActivity.viewModels().

These work for both ViewModel and AndroidViewModels with the default constructors (empty, or an Application parameter respectively), or you can use the trailing lambda to return a view model factory. You would use it like this:

class FragmentHome : Fragment() {
  private val mHomeViewModel_B: MyViewModel by viewModels()
}

or

class FragmentHome : Fragment() {
  private val mHomeViewModel_B: MyViewModel by viewModels { getMyViewModelFactory() }
}

To get something equivalent to what you have in A2, you can wrap this function to have it build a factory for you:

@Suppress("UNCHECKED_CAST")
inline fun <reified VM : ViewModel> Fragment.viewModelFactory(crossinline creator: () -> VM): Lazy<VM> {
    return viewModels {
        object : ViewModelProvider.Factory {
            override fun <T : ViewModel?> create(modelClass: Class<T>): T {
                return creator() as T
            }
        }
    }
}
0
iamanbansal On

Fragment Ktx

by viewModels(...) is part of fragment-ktx library, it's a convienience short hand for creating a lazy delegate obtaining ViewModels.

// creates lazy delegate for obtaining zero-argument MyViewModel
private val viewModel : MyViewModel by viewModels()
// it's functionally equal to:
private val viewModel by lazy {
    ViewModelProvider(this).get(MyViewModel::class.java)
}

// with factory:
private val viewModel : MyViewModel by viewModels { BaseViewModelFactory {  } }
// is equal to:
private val viewModel by lazy {
    ViewModelProvider(this, BaseViewModelFactory {  }).get(MyViewModel::class.java)
}

class BaseViewModelFactory(val creator: () -> ViewModel) : ViewModelProvider.Factory {
    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        return creator() as T
    }
}