You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

重构:如何通过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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 03:05:12