LiveData: postValue() vs setValue()

Google introduced LiveData as a component in its Android architecture component. In a brief description LiveData is:

LiveData is an observable data class. Unlike a regular observable, LiveData is life-cycle aware.

Let take a look at the documentation:

This class is designed to hold individual data fields of ViewModel, but can also be used for sharing data between different modules in your application in a decoupled fashion.

It means that we are able to use LiveData to allow call service to control the UI in the corresponding activity. In practice we place MutableLiveData object inside the Service class, and exposes it to the ServiceBinder, where the activity will obtain the immutable version of it.

Then when we want to update the UI, just simply push new values to the MutableLiveData from the Service and the Activity should respond correspondingly. Or? Let take a look at a basic sample:

Activity

class LiveDataActivityTest : AppCompatActivity(), View.OnClickListener {
    private lateinit var livedataService: LiveDataServiceTest
    private lateinit var data: LiveData
    private var isBound: Boolean = false

    private val connection = object : ServiceConnection {
        override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
            val binder = service as LiveDataServiceTest.ServiceBinder
            livedataService = binder.getService()
            data = binder.getData()
            isBound = true
            Timber.d("Service connected.")

            data.observe(this@LiveDataActivityTest, Observer {
                it?.let { value ->
                    Timber.d("received progress: $value")                                      
                    ObjectAnimator.ofInt(progressBar, "progress", value * 10).start()
                }
            })
        }

        override fun onServiceDisconnected(name: ComponentName?) {
            isBound = false
            Timber.d("Service disconnected.")
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_livedata_service_test)

        val intent = Intent(this, LiveDataServiceTest::class.java)
        bindService(intent, connection, Context.BIND_AUTO_CREATE)

        btnStart.setOnClickListener(this)
    }

    override fun onClick(p: View?) {
        if (isBound) {
            livedataService.updateProgress()
        }
    }
}

Service

class LiveDataServiceTest : Service() {
    private val binder = ServiceBinder()
    private val data = MutableLiveData()

    inner class ServiceBinder : Binder() {
        fun getService(): LiveDataServiceTest = this@LiveDataServiceTest
        fun getData(): LiveData = this@LiveDataServiceTest.data
    }

    override fun onBind(intent: Intent?): IBinder = binder
    fun updateProgress() {
        for (i in 1..10) {
            Timber.d("post progress: $i")
            data.postValue(i)
            Thread.sleep(200)
        }
    }
}

It is not our expectation because the progress is updated incorrectly.

post progress: 1
post progress: 2
...
post progress: 10
received progress: 10

Two ways of mutating values:

If we take a look at the source code of LiveData, we can see two methods: postsValue() and setValue(), which are both protected. The only different of MutableLiveData is that it exposes these two methods, allowing users to mutate values inside.

From the method name we can infer something: postValue() will probably take effect later, while setValue() will update the value directly. This is documented as well. setValue() is straightforward, so let’s look at postValue().

  • The documentation mentions that if we all setValue() right after postValue(), the value will be set first and will later be overridden by the value in postValue(). This verifies our assumption earlier.
  • The last sentence contains more information:
    • If you called this method multiple times before a main thread executed a posted task, only the last value would be dispatched

If we call postValue multiple times, possibly in different methods, and the post values is not executed yet, the value will be dispatched once only.

This was actually different to my own assumption previously. I thought that if I post different values, even though the later values will override the earlier ones, the earlier ones should still be dispatched. But Android use a different approach here. Let’s look at the source code:

private static final Object NOT_SET = new Object();

protected void postValue(T value) {
    boolean postTask;
    synchronized (mDataLock) {
        postTask = mPendingData == NOT_SET;
        mPendingData = value;
    }
    if (!postTask) {
        return;
    }
    ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);
}

private final Runnable mPostValueRunnable = new Runnable() {
    @Override
    public void run() {
        Object newValue;
        synchronized (mDataLock) {
            newValue = mPendingData;
            mPendingData = NOT_SET;
        }
        //noinspection unchecked
        setValue((T) newValue);
    }
};

So we have synchronized here, the reading and writing operation of mPendingData are both locked so that at most one thread can access it.
The NOT_SET value declared as a static variable, to be used to check whether user has posted value to be updated later. Whenever we call postValue(), the internal mPendingData will be updated, but the task will not be posted twice, since mPendingData was not NOT_SET.

The Runnable is the one posted to the main thread. It simply takes the current value of mPendingData, and resetting it to NOT_SET.

The code is simple, and we don’t see any queueing or replay operations here. LiveData only keep the latest value posted, and updates it when main thread is available.

We think that the reason behind this is that: Android assumes that the data we posted is stateless, and no order should be guaranteed between multiple values (posted in a short time). Based on this assumption, it will only fire one task to update the UI, in order to keep the resource usage low.

Now changing the update value method in service to setValue()

post progress: 1
received progress: 1
post progress: 2
received progress: 2
...
post progress: 10
received progress: 10

Conclusion

In voice call feature, we need to update the UI frequently, and the data behind UI is definitely stateful: e.g., Calling (the phone shall ring), Connecting, Connected (shall display the call duration), Disconnected, Closed (shall close the activity). And most of the time data comes from different sources: from WebRTC and from the Server.

LiveData seems not to be the best choice, if we do want every change to be displayed on the screen, and order should be maintained. But compared to sending intents around and writing a lot of boilerplate code to register actions?

Therefore since we are still using it, we need to figure out a way to make sure our UI is updated correctly: use setvalue(). As documented, setValue is guaranteed to dispatch values, although it might be overridden later. What we did in practice is that, if we can confirm we are in the main thread, we set the value directly instead of posting it again. Otherwise, we post the value via postValue().

If you have any idea regarding to the usage of LiveData, please share and I’m really happy to discuss about it!

Runnable vs () -> Unit

In Java, whenever you want to wrap some actions and execute them later, we will probably use Runnable implementation. The run() method of the  Runnable interface takes in no parameters and returns nothing. Many methods will take in Runnable as a callback or as a way to execute actions later.

In Kotlin, the more idiomatic way is to use an anonymous function, i.e, a lambda expression. In this case the run() is () -> Unit. If you forgot the syntax, it has the same semantic as { print("Hello world") }

But not for their usages

Kotlin is designed to simplify code writing, they make it simpler by calling methods that take in a SAM (Single Abstract Method). For example, the Android Handler class has post() method, which requires a Runnable, so in Kotlin, you can simply write:

val handler = Handler()
handler.post {
    Log.d(TAG, "action")
}

Suppose now you want to remove the runnable, you may want to call the removeCallbacks() method, like this:

handler.removeCallbacks {
    Log.d(TAG, "action")
}

Well, this is NOT going to work. To remove callbacks, Handler actually compares the references of the Runnable objects, and only the same reference that we passed into the post() method will be removed. In this case, two lambda expressions actually create two different objects. Although their contents are exactly the same, the posted Runnable will still be run.

Take a look at the decompiled bytecode (as in Tools -> Kotlin -> Show Kotlin Bytecode):

Handler handler = new Handler();
handler.post((Runnable)(new Runnable() {
   public final void run() {
      Log.d(TestActivity.this.TAG, "action");
   }
}));
handler.removeCallbacks((Runnable)(new Runnable() {
   public final void run() {
      Log.d(TestActivity.this.TAG, "action");
   }
}));

As we guessed, the two Runnable objects were created, that definitely does not work.

How to improve it?

In Kotlin, we are able to store any function inside a variable. This is true for lambda as well. Therefore, a natural way to improve the code above is:

val lambda = { Log.d(TAG, "action") }
val handler = Handler()
handler.post(lambda)
handler.removeCallbacks(lambda)

Now we store lambda expression as a variable and pass it to both post() and removeCallbacks() method.

However, this is still NOT achieve what we want. One thing we should keep in mind that Runnable is NOT () -> Unit. The IDE warned us about Type mismatch syntax error.

What is the solution?

We will create a Runnable object in this case. Object expression object: Runnable { ... } is fine. But in this case SAM conversion is good enough:

val runnable = Runnable { Log.d(TAG, "action") }
val handler = Handler()
handler.post(runnable)
handler.removeCallbacks(runnable)

We can check decompiled code to be assured:

Runnable runnable = (Runnable)(new Runnable() {
   public final void run() {
      Log.d(TestActivity.this.TAG, "action");
   }
});
Handler handler = new Handler();
handler.post(runnable);
handler.removeCallbacks(runnable);

Problem solved 😀

So please take extra attention when you want to enjoy smooth development brought by Kotlin. Sometimes it is not free. As the compiler to tell you what it gives, you can always inspect it.