Creating Custom LiveData in Android: A Simple Guide

If you’re diving into Android development, you’ve probably heard of LiveData. But did you know you can create your own custom LiveData? This can be incredibly useful when you need more control over how data is fetched and updated. Let’s walk through the process in simple terms.

What is LiveData?

LiveData is a handy and powerful tool provided by Android. It’s like a container that holds data and is aware of the app’s lifecycle, meaning it knows when your app is in the foreground, background, or paused. This lifecycle awareness helps avoid common issues in app development, like memory leaks and crashes.

Why Create Custom LiveData?

While the standard LiveData is great, sometimes you need more control over how data is fetched or updated. Creating custom LiveData allows you to encapsulate complex logic and ensure that your data is managed efficiently.

Imagine you’re building an app that needs to display the current location of the user. You want to ensure the location updates only when necessary and that the app handles lifecycle changes gracefully. This is a perfect use case for custom LiveData.

Key Points to Remember

  1. Generic Type for LiveData:
    • LiveData should be generic to handle various data types like Int, String, or custom objects.
  2. Custom LiveData as Data Holder:
    • Custom LiveData should not only act as a data holder but should also be reactive to data changes.
  3. LiveData as Publisher-Subscriber Pattern:
    • LiveData operates on the publisher-subscriber (observer) pattern. The setValue or postValue methods are used to publish data updates.
  4. Implementing Observer for Automatic Updates:
    • Observers are necessary for automatic updates. Without observers, you would need to manually check for updates, which is inefficient.
  5. Multiple Observers for One LiveData:
    • A single LiveData instance can have multiple observers. All observers will be notified whenever the LiveData’s data changes.

How to Create Custom LiveData

Let’s create a custom LiveData to fetch and update the user’s current location:

  1. Create a Custom LiveData Class: Extend the LiveData class and override the onActive and onInactive methods.
  2. Fetch and Update Data: Implement the logic to fetch and update the user’s location.
  3. Observe the Custom LiveData: In your UI component, observe the custom LiveData to react to location updates.

Here’s some sample code to illustrate this:

MyLiveData.class

class MyLiveData<T> {
    private var dataHolder : T?= null
    private  val  arrayListOfObserver:ArrayList<(T?) -> Unit> = ArrayList()

    fun postValue(value : T){
        dataHolder = value
        arrayListOfObserver.forEach {
            it.invoke(dataHolder)
        }
    }

    fun getValue() = dataHolder

    fun addObserver(lifecycleOwner: LifecycleOwner, observer : (T?) -> Unit){
        arrayListOfObserver.add { observer }

    }

MainViewModel:

class MainViewModel : ViewModel() {
    private var counter : Int = 0
    val dataProvider : MutableLiveData<Int> = MutableLiveData()
    val myDataProvider : MyLiveData<Int> = MyLiveData()

    fun runCounter() {
        viewModelScope.launch {
            while (counter < 5) {
                counter += 1
                myDataProvider.postValue(counter)
                delay(1000) // Delay for 1 second
            }
        }
    }

}

MainActivity:

class MainActivity : AppCompatActivity() {
    private lateinit var mainViewModel: MainViewModel
   private var myOwnLiveDataTextView:TextView?=null


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        myOwnLiveDataTextView= findViewById(R.id.myOwnLiveDataTextView)

        mainViewModel = ViewModelProvider(this)[MainViewModel::class.java]

     
        mainViewModel.myDataProvider.addObserver(this, {
            it?.run {
                myOwnLiveDataTextView!!.text = this.toString()
            }
        })

        mainViewModel.runCounter()

    }
}

How to Make Your Custom LiveData Lifecycle-Aware

In Android development, managing data and ensuring that it only updates the UI when it should is crucial for a smooth user experience. Android’s LiveData is a popular tool for this, as it’s lifecycle-aware, meaning it only updates the UI when it’s in an active state. But what if you need a custom LiveData solution? In this article, we’ll walk you through making your own custom LiveData lifecycle-aware.

Understanding the Basics

Before diving in, let’s understand what lifecycle-aware means. In Android, lifecycle-aware components can automatically handle changes based on the lifecycle state of the app components (like Activities or Fragments). This means they can avoid unnecessary updates or memory leaks by only performing actions when the component is in an appropriate state (e.g., STARTED or RESUMED).

Steps to Make Custom LiveData Lifecycle-Aware

1. Add Lifecycle Management

To make your LiveData aware of lifecycle changes, you need to use Android’s LifecycleObserver. This helps your LiveData keep track of the lifecycle state of observers and ensures they only receive updates when their lifecycle is active

import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.OnLifecycleEvent

2. Create an Observer Wrapper

You’ll need an inner class that will act as a wrapper for your observers. This class, called ObserverWrapper, will implement LifecycleObserver to handle different lifecycle events like ON_START and ON_DESTROY. This wrapper helps manage when to notify observers based on their lifecycle state.

private inner class LifeCycleObserverWrapper(
    val lifecycleOwner: LifecycleOwner,
    val observer : (T?) -> Unit
) : LifecycleObserver {

    @OnLifecycleEvent(Lifecycle.Event.ON_START)
    fun doOnStart(){
        updateValue(observer)
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
    fun doOnResume(){
        updateValue(observer)
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
    fun doOnDestroy(){
        removeObserver(observer)
    }
}

3. Update the addObserver Method

Modify your addObserver method to store each observer with its corresponding ObserverWrapper. This way, you keep track of which observers are associated with which lifecycle states. If the lifecycle of the observer is currently active, you immediately notify it of any updates.

fun addObserver(lifecycleOwner: LifecycleOwner, observer : (T?) -> Unit){
    LifeCycleObserverWrapper(lifecycleOwner, observer)
        .apply {
            this.lifecycleOwner.lifecycle.addObserver(this)
            hashMapOfObservers[observer] = this
        }
}

4. Refine the postValue Method

Ensure your postValue method only sends updates to observers whose lifecycle is at least STARTED. This prevents unnecessary updates when observers are not in an active state, which can help avoid performance issues and memory leaks.

fun postValue(value : T){
    dataHolder = value
    hashMapOfObservers.values.forEach {
        if(it.lifecycleOwner.lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)){
            it.observer.invoke(dataHolder)
        }
    }
}

5. Handle Observer Removal

When an observer’s lifecycle reaches the DESTROYED state, you should remove it from the list of observers to prevent memory leaks. This ensures that you’re not holding onto references to observers that are no longer active.

fun removeObserver(observer: (T?) -> Unit){
    hashMapOfObservers[observer]?.run {
        this.lifecycleOwner.lifecycle.removeObserver(this)
    }
}

Conclusion

Creating custom LiveData gives you the flexibility to handle complex data-fetching logic while benefiting from lifecycle awareness. By encapsulating your logic in custom LiveData, you can create more robust, responsive, and user-friendly apps. So next time you need more control over your data, consider creating custom LiveData!

Get the complete Source Code here

You may also like...

0 Comments

No Comment.