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()));
}
}
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.
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}"
).