Android Kotlin ViewModel 错误处理:最佳 Toast 提示方案详解

在 Android Kotlin 的 ViewModel 中显示 Toast 提示需要特殊处理,因为 ViewModel 不应该直接持有 Context(避免内存泄漏),且 UI 操作(如 Toast)必须在主线程执行。以下是几种标准解决方案:


方案1:通过 LiveData 传递消息(推荐)

kotlin 复制代码
class MyViewModel : ViewModel() {
    // 定义消息事件(使用SingleLiveEvent避免重复触发)
    private val _toastMessage = MutableLiveData<String>()
    val toastMessage: LiveData<String> get() = _toastMessage

    fun fetchData() {
        viewModelScope.launch {
            try {
                // 模拟网络请求
                val result = apiService.getData()
                // ...处理数据
            } catch (e: Exception) {
                _toastMessage.postValue("加载失败: ${e.message}")
            }
        }
    }
}

// Activity/Fragment 中观察
class MyActivity : AppCompatActivity() {
    private val viewModel by viewModels<MyViewModel>()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // 观察Toast消息
        viewModel.toastMessage.observe(this) { message ->
            Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
        }
    }
}

方案2:使用 Event Wrapper(避免重复触发)

kotlin 复制代码
// 定义事件包装类
class Event<T>(private val content: T) {
    private var hasBeenHandled = false

    fun getContentIfNotHandled(): T? {
        return if (hasBeenHandled) null else {
            hasBeenHandled = true
            content
        }
    }
}

// ViewModel 中使用
class MyViewModel : ViewModel() {
    private val _toastEvent = MutableLiveData<Event<String>>()
    val toastEvent: LiveData<Event<String>> get() = _toastEvent

    fun fetchData() {
        viewModelScope.launch {
            try {
                // ...业务逻辑
            } catch (e: Exception) {
                _toastEvent.postValue(Event("错误: ${e.message}"))
            }
        }
    }
}

// Activity/Fragment 中观察
viewModel.toastEvent.observe(this) { event ->
    event.getContentIfNotHandled()?.let { message ->
        Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
    }
}

方案3:通过 Application Context(谨慎使用)

kotlin 复制代码
class MyViewModel(application: Application) : AndroidViewModel(application) {
    private val appContext = application.applicationContext

    fun showError() {
        // 注意:必须在主线程调用
        Handler(Looper.getMainLooper()).post {
            Toast.makeText(appContext, "出错啦!", Toast.LENGTH_SHORT).show()
        }
    }
}

⚠️ 此方案虽简便,但过度使用可能导致Toast与当前界面上下文不匹配


方案4:通过接口回调(适合复杂场景)

kotlin 复制代码
interface ToastListener {
    fun showToast(message: String)
}

class MyViewModel(private val toastListener: ToastListener) : ViewModel() {
    fun fetchData() {
        viewModelScope.launch {
            try {
                // ...业务逻辑
            } catch (e: Exception) {
                toastListener.showToast(e.message ?: "未知错误")
            }
        }
    }
}

// Activity中实现接口
class MyActivity : AppCompatActivity(), ToastListener {
    private val viewModel by viewModels<MyViewModel> { 
        MyViewModelFactory(this) 
    }

    override fun showToast(message: String) {
        runOnUiThread {
            Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
        }
    }
}

// 自定义Factory
class MyViewModelFactory(private val listener: ToastListener) : ViewModelProvider.Factory {
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        return MyViewModel(listener) as T
    }
}

最佳实践总结

  1. 推荐方案

    • 简单场景:方案1(LiveData)
    • 需要防重复:方案2(Event Wrapper)
  2. 线程安全

    • 确保Toast在主线程显示(postValuerunOnUiThread
  3. 架构原则

    • ViewModel 不应直接持有 Context
    • 通过观察者模式实现解耦
  4. 错误处理

    kotlin 复制代码
    catch (e: IOException) {
        _toastMessage.postValue("网络异常")
    } catch (e: IllegalStateException) {
        _toastMessage.postValue("数据格式错误")
    }

根据您的架构复杂度和需求选择合适方案即可。

相关推荐
赏金术士2 小时前
Kotlin 从入门到进阶 之作用域函数 & 优雅写法(五)
android·开发语言·kotlin
Ehtan_Zheng4 小时前
Android Compose 动画实践:内容切换与页面转场
android
Crystal3284 小时前
【终极指南】前端方面解决 uni-app APP 端 SSE 流式请求被缓冲拦截、无法实时渲染的问题
android·前端·ai编程
陆业聪5 小时前
技术选型决策树:什么团队、什么项目该选什么框架 | 跨平台框架深度对决(4)
android·架构设计
赏金术士6 小时前
Kotlin 从入门到进阶 之协程 Flow 模块(九)
开发语言·kotlin·php
JohnnyDeng946 小时前
Kotlin 协程原理与 Android 中的最佳实践
android·kotlin·协程
Aleyn7 小时前
用 KSP 给 Navigation 3 加一层「跨模块路由」:nav3-helper 设计与使用
android·android jetpack·composer
GeekBug7 小时前
Claude Code 如何帮我写 80% 的 Android 样板代码
android·claude
dora7 小时前
手把手带你实现一个Android抽卡集图鉴功能
android
海雅达手持终端PDA7 小时前
海雅达Model 10X—高通6490工业三防平板,生产制造仓储管理应用
android·物联网·能源·制造·信息与通信·交通物流·平板