Search code examples
javaandroidobservableandroid-databindingandroid-livedata

Observable DataBinding ObservableInt vs. LiveData with different results On Confguration change


I am testing the behavior of DataBinding observable when it comes to use a LiveData vs. ObservableInt.

I have simple layout with a button that triggers a counter for both LiveData & ObservableInt variables that I store in the ViewModel, I update their values using BindingAdapter

Both LiveData & ObservableInt variables count up normally; but when there is a device configuration change (screen rotation for my test), the ObservableInt countinues count up with button hits, although the LiveData dismisses one or two counts.

Below gif will illustrate more

My question is how to solve this problem? I mainly need a LiveData for further Transformations

Layout

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <data>

        <variable
            name="viewmodel"
            type="com.example.android.databindingobservableintlivedata.MyViewModel" />

    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:orientation="vertical"
        tools:context=".MainActivity">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center">

            <TextView
                android:id="@+id/observableint_label"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="ObservableInt: " />

            <TextView
                android:id="@+id/observableint_count"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="0"
                app:textValueForObservableInt="@{viewmodel.countObservableInt}" />
        </LinearLayout>

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="8dp"
            android:gravity="center">

            <TextView
                android:id="@+id/livedata_label"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="LiveData: " />

            <TextView
                android:id="@+id/livedata_count"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="0"
                app:textValueForLiveData="@{viewmodel.countLiveData}" />
        </LinearLayout>

        <Button
            android:id="@+id/btn_count"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:layout_marginTop="16dp"
            android:onClick="@{() -> viewmodel.onCount()}"
            android:text="Count" />


    </LinearLayout>
</layout>

Activity

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        ActivityMainBinding mBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
        MyViewModel viewModel = ViewModelProviders.of(this).get(MyViewModel.class);

        mBinding.setViewmodel(viewModel);
        mBinding.setLifecycleOwner(this); 

    }
}

ViewModel

public class MyViewModel extends ViewModel {

    public MutableLiveData<Integer> countLiveData = new MutableLiveData<>(0);
    public ObservableInt countObservableInt = new ObservableInt(0);

    public void onCount() {
        countObservableInt.set(countObservableInt.get() + 1);
        int value = countLiveData.getValue() == null ? 0 : countLiveData.getValue();
        countLiveData.postValue(value + 1);
    }

}

BindingAdapters

public class BindingAdapters {

    @BindingAdapter("textValueForObservableInt")
    public static void bindObservableIntInText(TextView text, ObservableInt value) {
        text.setText(String.valueOf(value.get()));
    }

    @BindingAdapter("textValueForLiveData")
    public static void bindLiveDataIntegerInText(TextView text, MutableLiveData<Integer> value) {
        text.setText(String.valueOf(value.getValue()));
    }

}

Solution

  • Post value of LiveData:

    Basically this method indicates that, any value given to LiveData can be/should be called from background thread (other than main thread) would reflect on main thread with updated value.

    Meaning that, if you've got two consecutive calls to LiveData about postValue() then only last value would be dispatched!

    In your case, countLiveData.postValue(value + 1) this line gets impacted on post-increment of value if there're too frequent calls to this method, which in case only gets updated for last value but not on consecutive ones.

    Set Value of LiveData:

    This method requires that call must be made from Main thread, resulting UI gets reflected/updated every time no matter how many calls you've made. None of intermediate calls get discarded.

    TL;DR

    If you've to update/set value from background thread then use postValue() method otherwise use setValue() when on Main thread.


    It takes a while for device rotation when I have continuous hits on the button, and suddenly rotate the device.

    Reason is that when you call setValue() on main thread before rotating device, each calls must update the UI consecutively, resulting delay on rotation stuff (Holds the UI updates before configuration change happen). That's why it lags/delay on rotation before all setValue() call completes UI updates (this line in your particular case: app:textValueForLiveData="@{viewmodel.countLiveData}").