BaseClass 中的 Kotlin 泛型。尝试通过 BaseFragment 中的泛型类型获取 ViewModel

Ant*_*adz 5 generics android mvvm viewmodel kotlin

嘿,我想创建 BaseFragment 类,通过泛型类型获取 viewModel:

abstract class BaseFragment<B : ViewDataBinding, VM : ViewModel> : DaggerFragment() {

    val viewModel by viewModels<VM> { viewModelFactory }
    ...
}

// Native function
@MainThread
inline fun <reified VM : ViewModel> Fragment.viewModels(
    noinline ownerProducer: () -> ViewModelStoreOwner = { this },
    noinline factoryProducer: (() -> Factory)? = null
) = createViewModelLazy(VM::class, { ownerProducer().viewModelStore }, factoryProducer)
Run Code Online (Sandbox Code Playgroud)

但出现错误Cannot use 'VM' as reified type parameter. Use a class instead.

有可能实现我想做的事情吗?也许用其他方法?

Ant*_*adz 1

有一种肮脏的方法只能从泛型中获取 ViewModel 和 ViewBinding:

abstract class BaseFragment<BINDING : ViewDataBinding, VM : ViewModel> : Fragment() {

    val viewModel by viewModels(getGenericClassAt<VM>(1))

    var binding: BINDING? = null

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        super.onCreateView(inflater, container, savedInstanceState)

        binding = inflater.inflateBindingByType<BINDING>(container, getGenericClassAt(0)).apply {
            lifecycleOwner = this@BaseFragment
        }.also {
            it.onBindingCreated()

        }

        return binding?.root
    }

    override fun onDestroyView() {
        super.onDestroyView()
        binding = null
    }

    override fun onOptionsItemSelected(item: MenuItem): Boolean = false

    internal open fun BINDING.onBindingCreated() {}

    fun <T> withBinding(action: BINDING.() -> T): T? = binding?.let { action(it) }

}

@Suppress("UNCHECKED_CAST")
fun <CLASS : Any> Any.getGenericClassAt(position: Int): KClass<CLASS> =
    ((javaClass.genericSuperclass as? ParameterizedType)
        ?.actualTypeArguments?.getOrNull(position) as? Class<CLASS>)
        ?.kotlin
        ?: throw IllegalStateException("Can not find class from generic argument")

fun <BINDING : ViewBinding> LayoutInflater.inflateBindingByType(
    container: ViewGroup?,
    genericClassAt: KClass<BINDING>
): BINDING = try {
    @Suppress("UNCHECKED_CAST")
    genericClassAt.java.methods.first { inflateFun ->
        inflateFun.parameterTypes.size == 3
                && inflateFun.parameterTypes.getOrNull(0) == LayoutInflater::class.java
                && inflateFun.parameterTypes.getOrNull(1) == ViewGroup::class.java
                && inflateFun.parameterTypes.getOrNull(2) == Boolean::class.java
    }.invoke(null, this, container, false) as BINDING
} catch (exception: Exception) {
    throw IllegalStateException("Can not inflate binding from generic")
}
Run Code Online (Sandbox Code Playgroud)

及用法:

class BoardFragment : BaseFragment<FragmentBoardBinding, BoardViewModel>() {

    override fun FragmentBoardBinding.onBindingCreated() {
        viewModel = this@BoardFragment.viewModel
    }
}
Run Code Online (Sandbox Code Playgroud)

脏,但节省了编码的语气