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在主线程显示(postValue 或 runOnUiThread)
  3. 架构原则

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

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

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

相关推荐
传奇开心果编程3 小时前
【Flutter入门练中学】第11课:状态管理进阶与声明式路由
android·学习·flutter·ui·ios
JasonSJX3 小时前
四端自建播放器怎么落地:Android、iOS、tvOS、Tizen 的 DRM 播放 SDK 技术盘点
android·ios·音视频·视频防录屏·加密保护课程·直播安全
驰骋工作流4 小时前
工作流引擎四大流程模块功能点统计:769 项能力清单梳理低代码工作流引擎表单
android·低代码·rxjava
恋猫de小郭4 小时前
Flutter 多窗口重要优化合并,多窗口性能和实用性大幅提升
android·前端·flutter
AirDroid_cn4 小时前
实时定位孩子手机超便捷!iPhone家长同步收提醒,省心又踏实
android
hai_android5 小时前
LruCache 图片浏览器内存缓存
android·java·kotlin
今天要早睡_5 小时前
C++入门到精通:类和对象(下)全方位解析
android·c++
Godikov9 小时前
在 Android 上跑 Kafka Consumer:一个实时定位数据接入的踩坑记录
android
mmsx10 小时前
Android 自定义 View 三关:触摸分发、绘制与坐标错位
android·kotlin
JMchen10 小时前
自定义View在复杂业务场景中的实战
android·kotlin·canvas