BaseClass 中的 Kotlin 泛型。尝试通过 BaseFragment 中的泛型获取 ViewModel
Kotlin generic in BaseClass. Trying to get ViewModel by generic type in BaseFragment
嘿,我想创建 BaseFragment class,它通过通用类型获取 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)
但出现错误 Cannot use 'VM' as reified type parameter. Use a class instead.
是否有可能实现我想要做的事情?也许用其他方法?
找到工作方式,但是否足够干净?
abstract class BaseModelFragment<VM : ViewModel>(viewModelClass: KClass<VM>) : DaggerFragment() {
@Inject
lateinit var viewModelFactory: ViewModelProvider.Factory
val viewModel by viewModel(viewModelClass) { viewModelFactory }
private fun Fragment.viewModel(
clazz: KClass<VM>,
ownerProducer: () -> ViewModelStoreOwner = { this },
factoryProducer: (() -> ViewModelProvider.Factory)? = null,
) = createViewModelLazy(clazz, { ownerProducer().viewModelStore }, factoryProducer)
}
和用法:
open class SomeFragment : BaseModelFragment<CustomerSupportViewModel>(CustomerSupportViewModel::class) {
...
}
它已经过测试并且可以正常工作。任何想法如何改进它? :)
有更清晰的解决方案:
abstract class BaseActivity<VM : BaseViewModel> : AppCompatActivity {
protected val viewModel: VM by viewModel(clazz = getViewModelClass())
private fun getViewModelClass(): KClass<VM> = (
(javaClass.genericSuperclass as ParameterizedType)
.actualTypeArguments[0] as Class<VM>
).kotlin
}
和用法:
class MainActivity : BaseActivity<MainViewModel>(R.layout.activity_main) {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel.onViewCreated()
}
}
仅从泛型获取 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")
}
和用法:
class BoardFragment : BaseFragment<FragmentBoardBinding, BoardViewModel>() {
override fun FragmentBoardBinding.onBindingCreated() {
viewModel = this@BoardFragment.viewModel
}
}
有点脏,但省去了编码的调子
嘿,我想创建 BaseFragment class,它通过通用类型获取 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)
但出现错误 Cannot use 'VM' as reified type parameter. Use a class instead.
是否有可能实现我想要做的事情?也许用其他方法?
找到工作方式,但是否足够干净?
abstract class BaseModelFragment<VM : ViewModel>(viewModelClass: KClass<VM>) : DaggerFragment() {
@Inject
lateinit var viewModelFactory: ViewModelProvider.Factory
val viewModel by viewModel(viewModelClass) { viewModelFactory }
private fun Fragment.viewModel(
clazz: KClass<VM>,
ownerProducer: () -> ViewModelStoreOwner = { this },
factoryProducer: (() -> ViewModelProvider.Factory)? = null,
) = createViewModelLazy(clazz, { ownerProducer().viewModelStore }, factoryProducer)
}
和用法:
open class SomeFragment : BaseModelFragment<CustomerSupportViewModel>(CustomerSupportViewModel::class) {
...
}
它已经过测试并且可以正常工作。任何想法如何改进它? :)
有更清晰的解决方案:
abstract class BaseActivity<VM : BaseViewModel> : AppCompatActivity {
protected val viewModel: VM by viewModel(clazz = getViewModelClass())
private fun getViewModelClass(): KClass<VM> = (
(javaClass.genericSuperclass as ParameterizedType)
.actualTypeArguments[0] as Class<VM>
).kotlin
}
和用法:
class MainActivity : BaseActivity<MainViewModel>(R.layout.activity_main) {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel.onViewCreated()
}
}
仅从泛型获取 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")
}
和用法:
class BoardFragment : BaseFragment<FragmentBoardBinding, BoardViewModel>() {
override fun FragmentBoardBinding.onBindingCreated() {
viewModel = this@BoardFragment.viewModel
}
}
有点脏,但省去了编码的调子