如何在 Kotlin 中为 ViewModel 使用泛型?

How to use generics for ViewModel in Kotlin?

我想为现有片段创建一个 MainFragment,并为提供的每个片段创建一个 ViewModel 对象 by viewModels<FragmentName::class>(),如下所示:

class MainFragment<VM: ViewModel>: Fragment() {
    private val viewModel by viewModels<VM::class>()
}

但是我得到这个错误:

Cannot use 'VM' as reified type parameter. Use a class instead.

这就是我想要的:

class ProfileFragment: MainFragment<ProfileViewModel>() {}

并且只需使用来自父对象的 viewModel 对象 class。

如何解决这个问题?

我做了类似的数据绑定方法看看它:

1st 我创建了一个抽象的基础片段:

abstract class BaseFragment<Binding:ViewDataBinding>:Fragment() {

 protected abstract fun setLayout(inflater: LayoutInflater, container: ViewGroup?):Binding
}

之后我在下面的片段中访问它:

class HomeFragment : BaseFragment<FragmentHomeBinding>() {


    override fun setLayout(inflater: LayoutInflater, container: ViewGroup?): FragmentHomeBinding {

        return DataBindingUtil.inflate(inflater,R.layout.fragment_home,container,false)
    }
}

您可以将 Viewbinding 替换为 viewmodel。

让你的MainFragment抽象

abstract class MainFragment<VM: ViewModel>: Fragment() {
       abstract private val viewModel : VM
    }

覆盖您的子片段

class ProfileFragment: MainFragment<ProfileViewModel>() {
    private val mViewModel by viewModels<YourViewModel>()
    override val viewModel get() = mViewModel

}    

您不能使用普通的泛型参数,例如内联函数中具体化的参数 (VM::class)。但是如果你想从为每个片段编写 by viewModels() 中解脱出来,你可以使用一个肮脏的解决方法从它的通用 class.

实例化 viewModel

但在我开始之前,值得一提的是 viewModels<>() 是一个内联函数,它通过 ViewModelProvider(store).get(vmClass) 懒惰地创建您的 viewModel。因此,如果我们可以从我们的参数化(通用)片段 class 中提取 viewModel 的 Java Class,我们就可以使用它来获取我们的 viewModel。

在最简单的实现中,我们可以假设除了 BaseFragment(99% 的情况)之外,我们的片段中没有继承。我们将在其 actualTypeParameters 中获得 genericSuperclass,它将代表实际类型参数(我们正在寻找的 ViewModel class),然后我们使用第一个元素实例化 viewModel

abstract class BaseFragment<VM : ViewModel> : Fragment() {
    lateinit var viewModel: VM
    private set

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // e.g. we are ProfileFragment<ProfileVM>, get my genericSuperclass which is BaseFragment<ProfileVM>
        // Actually ParameterizedType will give us actual type parameters
        val parameterizedType = javaClass.genericSuperclass as? ParameterizedType

        // now get first actual class, which is the class of VM (ProfileVM in this case)
        @Suppress("UNCHECKED_CAST")
        val vmClass = parameterizedType?.actualTypeArguments?.getOrNull(0) as? Class<VM>?

        if(vmClass != null)
            viewModel = ViewModelProvider(this).get( vmClass )
        else
            Log.i("BaseFragment", "could not find VM class for $this")
    }
}
class ProfileVM : ViewModel(){
    var x = 1
}

class ProfileFragment : BaseFragment<ProfileVM>() {
    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        Log.i("ProfileFragment", "vm.x: ${viewModel.x}")
        return super.onCreateView(inflater, container, savedInstanceState)
    }
}

此外,如果你想支持继承和复杂的层次结构,你可以使用 superClass 找到 BaseFragment,我将添加它作为另一个答案,因为我想保持这个答案干净整洁:D

PS:我不推荐你要找的东西,因为如果你想创建一些只需要sharedViewModel a.k.a的片段。 activityViewModel() 您必须为此添加一些更复杂的逻辑或处理手动创建一些 viewModel 的双重性,而这个神奇的代码将为您实例化其余部分!

除了我之前的回答,还有一个更脏(但更复杂)的方法,你可以使用。您可以遍历所有层次结构并找到可从 ViewModel 分配的类型参数。在这种方法中,我们迭代每个 super 直到我们找到可以从 ViewModel 分配的东西。

首先,我们检查我们正在处理的当前类型是否具有 ViewModel 的泛型类型参数,如果我们找到它,return 它作为答案。否则,对超类重复相同的逻辑。

fun<CLS> Class<*>.findGenericWithType(targetClass: Class<*>) : Class<out CLS>?{
    var currentType: Type? = this

    while(true){
        val answerClass = (currentType as? ParameterizedType)?.actualTypeArguments //get current arguments
            ?.mapNotNull { it as? Class<*> } //cast them to class
            ?.findLast { targetClass.isAssignableFrom(it) } // check if it is a target (ViewModel for example)

        // We found a target (ViewModel)
        if(answerClass != null){
            @Suppress("UNCHECKED_CAST")
            return answerClass as Class<out CLS>?
        }

        currentType = when{
            currentType is Class<*> -> currentType.genericSuperclass // Not a ParameterizedType so go to parent
            currentType is ParameterizedType -> currentType.rawType // a parameterized type which we could't find any ViewModel yet, so the raw type (parent) should have it
            else -> return null //or throw an exception
        }
    }
}

abstract class BaseFragment<VM : ViewModel> : Fragment() {
    lateinit var viewModel: VM
    private set

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val vmClass = this.javaClass.findGenericWithType<VM>(ViewModel::class.java)

        if(vmClass != null)
            viewModel = ViewModelProvider(this).get( vmClass )
        else
            Log.i("BaseFragment", "could not find VM class for $this")
    }
}

最后

class ProfileFragment : BaseFragment<ProfileVM>()