在 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(LiveData)
- 需要防重复:方案2(Event Wrapper)
-
线程安全
- 确保Toast在主线程显示(
postValue
或runOnUiThread
)
- 确保Toast在主线程显示(
-
架构原则
- ViewModel 不应直接持有 Context
- 通过观察者模式实现解耦
-
错误处理
kotlincatch (e: IOException) { _toastMessage.postValue("网络异常") } catch (e: IllegalStateException) { _toastMessage.postValue("数据格式错误") }
根据您的架构复杂度和需求选择合适方案即可。