重构:如何通过ViewModel的MutableState在RecyclerView加载泛型数据
泛型密封类State的Fragment数据加载与类型安全处理
问题背景
定义了包含泛型Loaded<T>的密封类State,需要将其中的数据加载到Fragment的RecyclerView中。现有代码功能正常,但在类型转换时出现Unchecked cast警告;尝试直接在when语句中判断is State.Loaded<List<Resort>>,编译报错:无法检查已擦除类型的实例。
现有代码(存在类型转换警告)
override fun initFlow(){ viewLifecycleOwner.lifecycleScope.launch { viewModel.state.collect { state -> when (state) { State.Waiting -> { binding.progressBar.isVisible = false } State.Loading -> { binding.progressBar.isVisible = true } State.Success -> { binding.progressBar.isVisible = false } is State.Loaded<*> -> { if (state.data is List<*> && state.data.all { item -> item is Resort } ) { val resorts = state.data as List<Resort> addResortsOnMap(resorts) } if (state.data is List<*> && state.data.all { item -> item is Route } ) { val roads = state.data roadsAdapter.setData(roads as List<Route>) } binding.progressBar.isVisible = false } is State.Error -> { binding.progressBar.isVisible = false Toast.makeText( requireContext(), state.message, Toast.LENGTH_SHORT ).show() } is State.TokenError -> { Toast.makeText(requireContext(), state.message, Toast.LENGTH_SHORT).show() findNavController().navigate(R.id.login) } } } } }
解决方案
方案1:拆分具体的Loaded子类(推荐)
放弃泛型,为每种数据类型定义单独的Loaded子类,彻底避免类型转换问题,代码更清晰且类型安全。
修改密封类State
sealed class State { object Waiting : State() object Loading : State() object Success : State() // 为每种数据类型定义专属子类 data class ResortLoaded(val data: List<Resort>) : State() data class RouteLoaded(val data: List<Route>) : State() data class Error(val message: String) : State() data class TokenError(val message: String) : State() }
Fragment中处理状态
override fun initFlow(){ viewLifecycleOwner.lifecycleScope.launch { viewModel.state.collect { state -> when (state) { State.Waiting -> binding.progressBar.isVisible = false State.Loading -> binding.progressBar.isVisible = true State.Success -> binding.progressBar.isVisible = false is State.ResortLoaded -> { binding.progressBar.isVisible = false addResortsOnMap(state.data) } is State.RouteLoaded -> { binding.progressBar.isVisible = false roadsAdapter.setData(state.data) } is State.Error -> { binding.progressBar.isVisible = false Toast.makeText(requireContext(), state.message, Toast.LENGTH_SHORT).show() } is State.TokenError -> { Toast.makeText(requireContext(), state.message, Toast.LENGTH_SHORT).show() findNavController().navigate(R.id.login) } } } } }
方案2:为Loaded类添加类型标记
在泛型Loaded<T>中加入KClass<T>类型标记,通过类型标记安全判断数据类型,消除转换警告。
修改密封类State
import kotlin.reflect.KClass sealed class State { object Waiting : State() object Loading : State() object Success : State() data class Loaded<T>(val data: T, val type: KClass<T>) : State() data class Error(val message: String) : State() data class TokenError(val message: String) : State() }
ViewModel中发送状态时传入类型标记
// 发送Resort列表数据 _state.value = State.Loaded(resortsList, List::class as KClass<List<Resort>>) // 发送Route列表数据 _state.value = State.Loaded(routesList, List::class as KClass<List<Route>>)
Fragment中处理状态
override fun initFlow(){ viewLifecycleOwner.lifecycleScope.launch { viewModel.state.collect { state -> when (state) { State.Waiting -> binding.progressBar.isVisible = false State.Loading -> binding.progressBar.isVisible = true State.Success -> binding.progressBar.isVisible = false is State.Loaded<*> -> { binding.progressBar.isVisible = false when (state.type) { List::class -> { if (state.data is List<*> && state.data.all { it is Resort }) { @Suppress("UNCHECKED_CAST") // 已通过all校验,转换安全 val resorts = state.data as List<Resort> addResortsOnMap(resorts) } else if (state.data is List<*> && state.data.all { it is Route }) { @Suppress("UNCHECKED_CAST") val routes = state.data as List<Route> roadsAdapter.setData(routes) } } } } is State.Error -> { binding.progressBar.isVisible = false Toast.makeText(requireContext(), state.message, Toast.LENGTH_SHORT).show() } is State.TokenError -> { Toast.makeText(requireContext(), state.message, Toast.LENGTH_SHORT).show() findNavController().navigate(R.id.login) } } } } }
方案3:使用Reified扩展函数辅助类型判断
通过inline reified函数封装类型判断逻辑,简化代码,同时可安全抑制转换警告。
添加State扩展函数
inline fun <reified T> State.isLoadedOfType(): Boolean { return this is State.Loaded<*> && this.data is T } inline fun <reified T> State.getLoadedData(): T? { return if (this is State.Loaded<*> && this.data is T) { this.data as T } else null }
Fragment中处理状态
override fun initFlow(){ viewLifecycleOwner.lifecycleScope.launch { viewModel.state.collect { state -> when (state) { State.Waiting -> binding.progressBar.isVisible = false State.Loading -> binding.progressBar.isVisible = true State.Success -> binding.progressBar.isVisible = false is State.Loaded<*> -> { binding.progressBar.isVisible = false // 处理Resort列表 getLoadedData<List<Resort>>()?.takeIf { it.all { item -> item is Resort } }?.let { addResortsOnMap(it) } // 处理Route列表 getLoadedData<List<Route>>()?.takeIf { it.all { item -> item is Route } }?.let { roadsAdapter.setData(it) } } is State.Error -> { binding.progressBar.isVisible = false Toast.makeText(requireContext(), state.message, Toast.LENGTH_SHORT).show() } is State.TokenError -> { Toast.makeText(requireContext(), state.message, Toast.LENGTH_SHORT).show() findNavController().navigate(R.id.login) } } } } }
总结
- 方案1最适合数据类型明确的场景,代码简洁、类型完全安全,无任何警告。
- 方案2适合需要保留泛型灵活性的场景,通过类型标记实现安全转换。
- 方案3通过扩展函数简化类型判断,适合已有泛型结构不愿修改的情况。
内容的提问来源于stack exchange,提问作者Oleg Kim
相关产品推荐
相关产品推荐

