Open In App

Handle One Time Events in Android with Kotlin Channel

Last Updated : 10 Mar, 2022
Improve
Improve
Like Article
Like
Save
Share
Report

You must have used SingleLiveEvent class in android to get rid of multiple occurrences of SnackBars, Toasts, etc on changing device orientation (on rotating device)… If yes, then it’s time to say goodbye to SingleLiveEvent Class. We now have a better solution to it using Kotlin’s Channels. 

Before jumping to Kotlin’s Channels, Let’s have a look at SingleLiveEvent Class. This class is a kind of workaround that doesn’t come from any android or Kotlin libraries. We actually create this class manually in android by extending it from MutableLiveData that actually consumes multiple values being sent It’s aware of the view’s lifecycle & we can have only one observer at a time with this.

Note: Till yet we are using SingleLiveEvent class to fix these multiple appearances of Toasts, SnackBars, etc.

Snackbar shouldn’t pop up again on rotating device (Till yet we use SingleLiveEvent class to fix it. )

Kotlin’s Channels

A channel also looks similar to Flow or LiveData. we can send values from a channel inside a coroutine scope & like SingleLiveEvent we only receive one value at a time. A channel has a suspending send() function to send the value and a suspending receive(), consumeEach{ }  functions to receive the value. Now, with channels, we don’t need to create a custom SingleLiveEvent class & it works like a charm.

We have fixed reappearing of SnackBar using Kotlin’s Channel instead of SingleLiveEvent

Now let’s see the implementation part of it

This is a very basic app having a “Show Snackbar” button in MainActivity that ultimately pops up Snackbar on clicking of it.

MainViewModel:

Kotlin




class MainViewModel : ViewModel() {
 
    // Private channel of type Resource
    private val eventChannel = Channel<Resource>()
     
    // Receiving channel as a flow
    val eventFlow = eventChannel.receiveAsFlow()
 
 
    fun triggerEvent() = viewModelScope.launch {
        // We are not performing any network
        // call or local db operation here
        // We are just sending simple error
        // message to showcase you
        eventChannel.send(Resource.Error("Oops.. Something went wrong!"))
    }
 
    // This Resource sealed class helps us
    // in managing different states of
    // an event. Hence, ultimately helps
    // in updating UI states
    sealed class Resource {
        data class Error(val message: String) : Resource()
        data class Success(val data: String) : Resource()
        data class Loading(val loadingMsg: String) : Resource()
    }
}


 
 

We have declared a private channel object ‘eventChannel’of Resource type. (we declared it private because exposing this channel outside will be a bad practice as we can make mutations in it from outside classes). Now we have declared another immutable object of type Flow & we have assigned eventChannel to it by converting it to flow using receiveAsFlow() function. We have converted it to flow as we have more options to transform these received events. (we will expose this flow for outside classes as it is immutable).

 

Now we have function triggerEvent() which will be invoked on clicking of the ShowSnackBar button, this function will actually send the value wrapped in a resource class from the channel. (wrapping in resource class will help us identify the state of events {i.e. Success, Error, Loading, etc }).  See, here we are using viewModelScope (Coroutine scope) because send() is a suspend function.

 

MainActivity:

 

Kotlin




//-----------------Code inside MainActivity-------------------//
 
btnShowSnackbar.setOnClickListener {
     viewModel.triggerEvent()
   }
 
lifecycleScope.launchWhenStarted {
            viewModel.eventFlow.collect { event ->
                when(event) {
                    is MainViewModel.Resource.Error -> {
                        Snackbar.make(binding.root, event.message, Snackbar.LENGTH_LONG).show()
                    }
 
                    is MainViewModel.Resource.Success -> {
                    // Do something on success
                    }
                    is MainViewModel.Resource.Loading -> {
                    // Do something on loading
                    }
               }
        }
  }


 
 

In setOnClickListener body of showSnackbar button, we are calling triggerEvent() function which is present in our MainViewModel. On clicking the button, the channel will send the value & we have already converted our channel into a Kotlin Flow. So, we can collect this sent value in our MainActivity (we can also use channel’s consumeEach{} lambda function here if you don’t want to convert it to flow). Here we are using lifecycleScope (coroutine scope) as Flow’s collect function is also a suspending function. If you have noticed then we have sent value wrapped in Resource.Error from ViewModel. So, we will get that value in when’s Resource.Error block and here we are simply showing SnackBar. So, That’s how we can use Kotlin’s Channel to handle One-Time-Event in Android instead of Custom SingleLiveEvent class & can save creating one more extra class in our project.

 



Like Article
Suggest improvement
Previous
Next
Share your thoughts in the comments

Similar Reads