如何在ViewModel中处理异步请求的Result<List<Author>>结果并在Fragment展示
处理Result<List>的ViewModel实现方案
翻译后的仓库代码示例
// 异步获取作者列表,返回Result类型,可能成功或失败 suspend fun getAuthors(): Result<List<Author>> { randomDelay() // 模拟网络/本地加载延迟 return randomResult( listOf( Author( authorIds[0], "作者一号" ), Author( authorIds[1], "作者二号" ), ) ) }
// 模拟随机延迟,时长在100ms到1000ms之间 private suspend fun randomDelay() { delay((100L..1000L).random()) } // 生成随机结果:5%概率返回失败,95%概率返回成功数据 private fun <T> randomResult(data: T): Result<T> = if ((0..100).random() < 5) { Result.failure(RuntimeException()) } else { Result.success(data) }
核心实现思路
通过密封类定义UI状态,ViewModel用协程调用仓库方法并处理Result的成功/失败分支,将状态通过StateFlow暴露给Fragment,Fragment根据状态更新ListView和错误文本的显示,重点关注失败场景的错误提示与UI切换。
步骤1:定义UI状态密封类
用密封类明确区分加载、成功、失败三种状态,确保UI状态的完整性:
sealed class AuthorsUiState { object Loading : AuthorsUiState() // 加载中 data class Success(val authors: List<Author>) : AuthorsUiState() // 加载成功 data class Error(val message: String) : AuthorsUiState() // 加载失败 }
步骤2:ViewModel处理Result并更新状态
使用viewModelScope启动协程,通过Result.fold()方法分别处理成功和失败分支,将结果转化为UI状态:
class AuthorsViewModel(private val authorRepository: AuthorRepository) : ViewModel() { // 内部可变状态,对外暴露不可变的StateFlow private val _uiState = MutableStateFlow<AuthorsUiState>(AuthorsUiState.Loading) val uiState: StateFlow<AuthorsUiState> = _uiState.asStateFlow() init { loadAuthors() // 初始化时自动加载数据 } private fun loadAuthors() { viewModelScope.launch { val result = authorRepository.getAuthors() result.fold( onSuccess = { authors -> // 成功时更新为Success状态,传递作者列表 _uiState.value = AuthorsUiState.Success(authors) }, onFailure = { exception -> // 失败时根据异常类型生成友好提示 val errorMsg = when (exception) { is RuntimeException -> "加载作者列表失败,请稍后重试" else -> "未知错误:${exception.message ?: "无详细信息"}" } _uiState.value = AuthorsUiState.Error(errorMsg) } ) } } // 可选:提供重试方法,供Fragment的重试按钮调用 fun retryLoadAuthors() { _uiState.value = AuthorsUiState.Loading loadAuthors() } }
步骤3:Fragment观察状态并更新UI
在Fragment中使用lifecycleScope和repeatOnLifecycle安全观察状态,根据不同状态控制ListView和错误TextView的显示:
class AuthorsFragment : Fragment() { private lateinit var viewModel: AuthorsViewModel private lateinit var authorsListView: ListView private lateinit var errorTextView: TextView private lateinit var authorsAdapter: AuthorsAdapter override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { val view = inflater.inflate(R.layout.fragment_authors, container, false) authorsListView = view.findViewById(R.id.authors_list) errorTextView = view.findViewById(R.id.error_text) // 初始化适配器,默认空列表 authorsAdapter = AuthorsAdapter(requireContext(), emptyList()) authorsListView.adapter = authorsAdapter return view } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) viewModel = ViewModelProvider(this)[AuthorsViewModel::class.java] lifecycleScope.launch { // 在STARTED状态下收集状态,避免内存泄漏 repeatOnLifecycle(Lifecycle.State.STARTED) { viewModel.uiState.collect { state -> when (state) { AuthorsUiState.Loading -> { // 加载中:隐藏列表和错误文本,可显示进度条 authorsListView.visibility = View.GONE errorTextView.visibility = View.GONE // progressBar.visibility = View.VISIBLE } is AuthorsUiState.Success -> { // 成功:显示列表,隐藏错误文本和进度条 authorsListView.visibility = View.VISIBLE errorTextView.visibility = View.GONE // progressBar.visibility = View.GONE authorsAdapter.updateData(state.authors) } is AuthorsUiState.Error -> { // 失败:隐藏列表和进度条,显示错误文本 authorsListView.visibility = View.GONE errorTextView.visibility = View.VISIBLE // progressBar.visibility = View.GONE errorTextView.text = state.message } } } } } // 可选:给错误文本添加点击重试 errorTextView.setOnClickListener { viewModel.retryLoadAuthors() } } }
步骤4:ListView适配器实现
简单的BaseAdapter,支持更新数据:
class AuthorsAdapter( private val context: Context, private var authors: List<Author> ) : BaseAdapter() { private val inflater = LayoutInflater.from(context) // 更新数据并通知列表刷新 fun updateData(newAuthors: List<Author>) { authors = newAuthors notifyDataSetChanged() } override fun getCount(): Int = authors.size override fun getItem(position: Int): Author = authors[position] override fun getItemId(position: Int): Long = authors[position].authorId override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View { val view = convertView ?: inflater.inflate(android.R.layout.simple_list_item_1, parent, false) val textView = view.findViewById<TextView>(android.R.id.text1) textView.text = authors[position].name return view } }
失败场景处理要点
- 异常分类处理:根据不同异常类型返回针对性的错误提示,比如网络异常提示“网络连接失败”,本地数据异常提示“本地数据加载失败”,提升用户体验。
- 重试机制:提供重试入口(如错误文本点击重试),让用户可以主动触发重新加载。
- 状态可视化:加载中显示进度条,失败时明确展示错误信息,避免用户困惑。
- 错误信息本地化:将错误提示字符串放入
strings.xml,方便多语言适配。
内容的提问来源于stack exchange,提问作者Student4543
相关产品推荐
相关产品推荐

