如何在 Kotlin 中为 ViewModel 使用泛型? [英] How to use generics for ViewModel in Kotlin?

查看:105
本文介绍了如何在 Kotlin 中为 ViewModel 使用泛型?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想为现有片段创建一个 MainFragment 并为每个片段创建一个 ViewModel 对象,该对象由 viewModels() 提供,如下所示:

I want to create a MainFragment for my existing fragments and create a ViewModel object for each fragment that is provided by viewModels<FragmentName::class>() like this:

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 对象.

And simply use viewModel object from the parent class.

如何解决这个问题?

推荐答案

您不能使用普通的泛型参数,例如内联函数 (VM::class) 中的具体化参数.但是,如果您想摆脱为每个片段编写 by viewModels() 的麻烦,您可以使用肮脏的解决方法从其 Generic 类实例化 viewModel.

You cannot use normal generic arguments like reified ones from inline functions (VM::class). But if you want to free yourself from writing by viewModels() for each fragment, you can use a dirty workaround to instantiate the viewModel from its Generic class.

但在我开始之前,值得一提的是 viewModels<>() 是一个内联函数,它通过 ViewModelProvider(store).get(vmClass).因此,如果我们可以从参数化(通用)Fragment 类中提取 viewModel 的 Java 类,我们就可以使用它来获取我们的 viewModel.

But before I start, it's worth mentioning that viewModels<>() is an inline function which lazily creates your viewModels through ViewModelProvider(store).get(vmClass). So, if we can extract the Java Class of our viewModel from our parameterized (generic) Fragment class, we can get our viewModel using it.

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

In the simplest implementation, we can assume that there are no inheritance in our fragments other than BaseFragment (which is 99% of cases). We will get genericSuperclass which will represent the actual type parameters (the ViewModel class we were looking for) in its actualTypeParameters and then we instantiate the viewModel using the very first element

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

In addition, if you want to support inheritance and complex hierarchies, you can find the BaseFragment using superClass which I will add as another answer because I want to keep this answer clean and tidy :D

PS:我不推荐你在找什么,因为如果你想创建一些只需要 sharedViewModel aka activityViewModel() 的片段,你必须添加一些更复杂的逻辑或者处理手动创建一些视图模型的二元性,而这个神奇的代码会为你实例化其余的!

PS: I do not recommend what you're looking for, because if you want to create some fragments which only need a sharedViewModel a.k.a. activityViewModel() you have to add some more complex logic to this or deal with the duality of creating SOME viewModels manually, while this magic code will instantiate the rest for you!

这篇关于如何在 Kotlin 中为 ViewModel 使用泛型?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆