如何在 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>()
我想为现有片段创建一个 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.
但在我开始之前,值得一提的是 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>()