Search code examples
androidandroid-studioandroid-databindingandroid-architecture-components

LiveData doesn't notify UI about changes when used with DataBinding


I want to change UI as LiveData changes when used with DataBinding. But as the LiveData is changing it is not reflecting changes in UI.

MainViewModel.java

public class MainViewModel extends AndroidViewModel {
    /**
     * Boolean variable to see whether user is ClockedIn or ClockedOut
     */
    public MutableLiveData<Boolean> isClockedIn;

    public MainViewModel(Application application) {
        super(application);
        isClockedIn = new MutableLiveData<>();
        //...
        calculateClockedInOrNot(PreferencesManager.getString(mContext,PreferencesManager.CLOCK_STATUS_KEY));
    }

    /**
     * An utility function to calculate whether user is clocked in or clocked out,
     * this function is also called within this ViewModel after an api hit
     *
     * if string == "CLOCKED_OUT" or string == "mobileEventType.CLOCK_OUT" => user is clocked out
     * else user is clocked in
     */
    private void calculateClockedInOrNot(String string) {
        if (string.equals("CLOCKED_OUT") || string.equals("mobileEventType.CLOCK_OUT")) {
            //user is clocked out
            isClockedIn.setValue(false);
        } else {
            //user is clocked in
            isClockedIn.setValue(true);
        }
    }
    //getter function
    public MutableLiveData<Boolean> isClockedIn() {
        return isClockedIn;
    }
    //other function left for brevity
}

MainActivity.java

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // Obtain the ViewModel component.
    mainViewModel = ViewModelProviders.of(this).get(MainViewModel.class);
    // Inflate view and obtain an instance of the binding class.
    ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
    // Assign the component to a property in the binding class.
    binding.setData(mainViewModel);
}

activity_main.xml

<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:bind="http://schemas.android.com/apk/res-auto"

xmlns:tools="http://schemas.android.com/tools">

<data>

    <variable
        name="data"
        type="com.app.bidunity.viewmodel.MainViewModel" />
</data>

<LinearLayout
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

        <ImageView
            android:id="@+id/imageView"
            android:layout_width="wrap_content"
            android:layout_height="@dimen/img_hight_location"
            android:adjustViewBounds="true"
            android:src="@{data.clockedIn ? @drawable/ic_location_on : @drawable/ic_location_off}"/>
        <!--Other view left for brevity-->
 </LinearLayout>
</layout>

build.gradle

apply plugin: 'com.android.application'

android {
    compileSdkVersion 28
    defaultConfig {
        applicationId "com.app.bidunity"
        minSdkVersion 17
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        vectorDrawables.useSupportLibrary = true
    }
buildTypes {
    release {
        minifyEnabled false
        proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
    }
}
dataBinding {
    enabled = true
}
compileOptions {
    sourceCompatibility = '1.8'
    targetCompatibility = '1.8'
}


}

dependencies {
def arch_version = "2.0.0-rc01"

implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.appcompat:appcompat:1.0.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
//material library
implementation 'com.google.android.material:material:1.0.0'
// ViewModel and LiveData
implementation "androidx.lifecycle:lifecycle-extensions:$arch_version"

testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.1.0-alpha4'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0-alpha4'
}

I am using Android studio 3.3 Canary 11 with gradle 4.10.1 and onclick with databinding is working fine within viewmodel, but even after changing isClockedIn the ImageView is not changing it's drawable. I have tried everything, I even debugged to see if isClockedIn is changing or not in middle of the run, and yes it's value is changing


Solution

  • You need to make your isClockedIn variable Observable Field,

    So replace it from

    public boolean isClockedIn = false;
    

    to

    public ObservableBoolean isClockedIn = new ObservableBoolean();
    

    Note : also make sure that xml has the same variable name as in your ViewModel

    Refer here

    Edit:

    If you don't want to make ObservableField, then it'll take some other changes.

    Inspite of ObservableField, you'll need to make your observable extend BaseObservable and in your livedata you'll required to call notifyPropertyChanged(BR.isClockedIn); after boolean value change.


    If you want to use LiveData as observer, first is to provide your binding a life cycle

    // Specify the current activity as the lifecycle owner (in your activity/fragment).
    binding.setLifecycleOwner(this);
    

    In your ViewModel, take LiveData object to observe change and apply Transformation on it:

    MutableLiveData<Boolean> dataToBeObserve = new MutableLiveData<>();
    LiveData booleanDataChange = Transformations.map(dataToBeObserve, dataToBeObserve-> dataToBeObserve.value);
    

    Now, use this booleanDataChange to xml and change your MutableLiveData value to observe data change event.

    Note: You can use same LiveData for change events, just need to split defination to constructor call.