The following is a common solution to show Toast message from ViewModel.
Create a MutableLiveData to hold the toast message. To avoid MutableLiveData
to be triggered multiple times, we use SingleLiveEvent instead.
class MainViewModel(): ViewModel() { internal val toastMessage = SingleLiveEvent<Int>() fun testToast() { toastMessage.value = R.string.hello }}
SingleLiveEvent.kt
(Source)
class SingleLiveEvent<T>() : MutableLiveData<T>() { private val pending = AtomicBoolean(false) @MainThread override fun observe(owner: LifecycleOwner, observer: Observer<T>) { if (hasActiveObservers()) { Log.w(TAG, "Multiple observers registered but only one will be notified of changes.") } // Observe the internal MutableLiveData super.observe(owner, Observer<T> { t -> if (pending.compareAndSet(true, false)) { observer.onChanged(t) } }) } @MainThread override fun setValue(t: T?) { pending.set(true) super.setValue(t) } /** * Used for cases where T is Void, to make calls cleaner. */ @MainThread fun call() { value = null } companion object { private const val TAG = "SingleLiveEvent" }}
Create an observer for toastMessage
in Activity
class, probably at onCreate
.
viewModel.toastMessage.observe(this, Observer { res -> if (res != null) { val message = getString(res) Toast.makeText(context, message, Toast.LENGTH_LONG).show() }})
NOTE: SingleLiveEvent
only allow one observer.
The above solution is quite limited as we can only pass String resources
. We can solve this limitation by implementing a ResourceString
sealed classes which accept String resources
, String
and String resources
with support arguments.
sealed class ResourceString { abstract fun format(context: Context): String}class IdResourceString(val id: Int): ResourceString() { override fun format(context: Context): String { return context.getString(id) }}class TextResourceString(val text: String): ResourceString() { override fun format(context: Context): String { return text }}class FormatResourceString(val id: Int, val values: Array<Any>): ResourceString() { override fun format(context: Context): String { return context.getString(id, *values) }}
Activity
viewModel.toastMessage.observe(this, Observer { res -> if (res != null) { val message = res.format(context) Toast.makeText(context, message, Toast.LENGTH_LONG).show() }})
ViewModel
class MainViewModel(): ViewModel() { internal val toastMessage = SingleLiveEvent<ResourceString>() fun testToastWithResourceStringId() { toastMessage.value = IdResourceString(R.string.hello) } fun testToastWithString() { toastMessage.value = TextResourceString("Hello") } fun testToastWithResourceStringIdAndParameter() { // Hello, %1$s! You have %2$d new messages. toastMessage.value = FormatResourceString(R.string.hello_args, arrayOf("Desmond", 5)) }}