Avoid passing Generic and KClass of same Class when inheriting

The Goal: Have a basic Android class that takes away the need to do common stuff in subclasses.

The Result: It works, but you have to inherit it like this:
class HomeFragment : ViewModelFragment(R.layout.fragment_home,HomeViewModel::class)

Is there a way to avoid passing the ViewModel once as a Generic parameter and once as a class?

The Generic is needed so var viewModel has access to specific stuff, otherwise (when just writing var viewModel:ViewModel it doesn’t have access to specific methods/fields like that of HomeViewModel)

The KClass is needed because of the getViewModel method provided by the Koin Library, which is also below.

// Created by Merthan Erdem
// We need to pass the generic class because only
// then will the subclass have access to the specific viewmodel methods that it
// We need to pass the actual class (right) because Koin can't use generic classes
abstract class ViewModelFragment<VM : ViewModel>(@LayoutRes val layout: Int, private val viewModelClass: KClass<VM>) : Fragment() {    // Accessible, but generic, won't have specific methods/fields, see getSpecificBinding()
    open var binding: ViewDataBinding? = null    open lateinit var viewModel: VM    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        // Get binding (of passed type) for the passed layout, results in null when binding not available
        binding = DataBindingUtil.inflate(inflater, layout, container, false)
        // Set lifecycleowner to the current class
        binding?.lifecycleOwner = this
        // Get ViewModel of passed type from Koin (needs an actual class object/reified, not a generic)
        viewModel = getViewModel(viewModelClass)
        // If this class had a Binding, return it's root otherwise use inflater to retrieve view
        return binding?.root ?: inflater.inflate(layout, container, false)
    }    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        binding?.setVariable(BR.viewModel, viewModel)
    }    // Returns a binding of the specific type (instead of ViewDataBinding),
    // which allows access to non-generic methods and fields
    inline fun <reified specific : ViewDataBinding> getSpecificBinding() = binding as? specific
}

The getViewModel method

fun <T : ViewModel> SavedStateRegistryOwner.getViewModel(
    clazz: KClass<T>,
    qualifier: Qualifier? = null,
    parameters: ParametersDefinition? = null
): T {
    return getKoin().getViewModel(this, clazz, qualifier, parameters)
}

So, is there a way to prevent having to pass the ViewModel subclass twice? Reified doesn’t seem to work as this is a constructor. (Kotlin complains that the generic has to be reified when trying stuff like getViewModel(VM::class))

Thanks

I don’t think there is a way around this right now. The problem with reified type parameters is that they require the function to be inlined, which can’t be done with constructors.

It might be possible for kotlin to implement something that could work like an inlined constructor, but I don’t think anything of the sorts has been discussed before. I’m also not sure how often this would be used, so this might just be stopped by the “minus 100 point rule”, because of unnecessary complexity.

1 Like