Observe event only once using SingleLiveEvent

Have you tried to show Snackbar in your View when you had some important message to show ? and on that have you ever tried to use LiveData to observe the data for message in Snackbar?

Lot of developers use it by observing the livedata in the Snackbar and displaying it to the view. But there is a problem, you want the data to only be displayed in the Snackbar once and consumed only when it is required and observing it continuously doesn't suit the play here.

In the following code,

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val viewModel = ViewModelProviders.of(this).get(MainViewModel::class.java)
        viewModel.onDataUpload("Hey")
        viewModel.getUploadData().observe(this, Observer {
            //Snackbar
        })

    }
}

Here, getUploadData has a return type of LiveData<String>, so the data is consumed continuasly but we want to just get the data only once.

So, What is the Solution here ?

For these type of task, SingleLiveEvent class came for the resuce. It is nothing but an extension of MutableLiveData class but it emits the data only once whenever required.

We need to create a class file called SingleLiveEvent in our project and the SingleLiveEvent class ooks like,

class SingleLiveEvent<T> : MutableLiveData<T>() {

    private val mPending = 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, object : Observer<T> {
            override fun onChanged(t: T?) {
                if (mPending.compareAndSet(true, false)) {
                    observer.onChanged(t)
                }
            }
        })
    }

    @MainThread
    override fun setValue(t: T?) {
        mPending.set(true)
        super.setValue(t)
    }

    /**
     * Used for cases where T is Void, to make calls cleaner.
     */
    @MainThread
    fun call() {
        setValue(null)
    }

    companion object {

        private val TAG = "SingleLiveEvent"
    }
}

and to use it in ViewModel you just need to use it similarly how we use LiveData,

private val uploadData = SingleLiveEvent<String>()
fun getUploadData(): SingleLiveEvent<String> {
    return uploadData
}

and in the Activity/Fragment file we will use similarly how we used in the above code,

viewModel.getUploadData().observe(this, Observer {
    //Snackbar
})

The above code will only emit the data only once whenever it is required and then will stop observing.

This is how you can use SingleLiveEvent to solve our purpose for not using LiveData to emit data for Snackbar or any such kind of task.

Team Mindorks :)