【问题标题】:How to use generics for ViewModel in Kotlin?如何在 Kotlin 中为 ViewModel 使用泛型?
【发布时间】:2021-05-31 17:24:23
【问题描述】:

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

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

如何解决?

【问题讨论】:

  • 您不能使用VM::class,因为它不是内联具体化的,但有两件事,首先,可以这样做,我会为它添加一个答案(使用反射的肮脏解决方法,我猜即使你会否决它,因为它有点棘手:D),在某些情况下这也不会很好,特别是如果你的片段将只使用一个共享的 viewModel

标签: android kotlin generics android-viewmodel


【解决方案1】:

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

但在我开始之前,值得一提的是viewModels&lt;&gt;() 是一个内联函数,它通过ViewModelProvider(store).get(vmClass) 懒惰地创建您的视图模型。因此,如果我们可以从参数化(通用)Fragment 类中提取 viewModel 的 Java 类,我们就可以使用它来获取我们的 viewModel。

在最简单的实现中,我们可以假设我们的片段中除了 BaseFragment 之外没有继承(这是 99% 的情况)。我们将得到genericSuperclass,它将在其actualTypeParameters 中表示实际的类型参数(我们正在寻找的ViewModel 类),然后我们使用第一个元素实例化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() 的片段,您必须为此添加一些更复杂的逻辑或处理对偶性手动创建一些视图模型,而这个神奇的代码将为您实例化其余的!

【讨论】:

    【解决方案2】:

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

    第一次我创建了一个抽象的基础片段:

    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。

    【讨论】:

      【解决方案3】:

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

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

      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>()
      

      【讨论】:

        【解决方案4】:

        让您的 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
        
        }    
        

        【讨论】:

        • 在我的 ProfileFragment 中我已经有 private val viewModel by viewModels&lt;ProfileViewModel::class&gt;()。我为什么要使用它?
        • 在你的具体类中使用抽象和实现抽象
        猜你喜欢
        • 1970-01-01
        • 2017-12-06
        • 1970-01-01
        • 2021-03-28
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多