Two-way data binding for android:enabled attribute
Asked Answered
O

2

7

I'm trying to figure out the two-way data binding library in Android. I want to enable/disable the LinearLayout (and the RelativeLayout inside) by changing the android:enabled attribute in the xml.

The XML part looks like this:

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_gravity="center_vertical"
    android:layout_margin="5dp"
    android:gravity="center"
    android:enabled="@={viewModel.asd}"
    ndroid:onClick="@{()-> viewModel.doSomething()}"
    android:background="@drawable/shortcut_button_label_selector"
    android:orientation="horizontal">

    <RelativeLayout
        android:layout_width="40dp"
        android:layout_height="match_parent"
        android:enabled="@={viewModel.asd}"
        android:background="@drawable/shortcut_button_icon_selector">

Now, the reason I want to do this is because of these two selectors (shortcut_button_label_selector and shortcut_button_icon_selector), they look like this:

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

    <item
        android:state_enabled="false"
        android:drawable="@color/grey_300"></item>
    <item
        android:state_enabled="true"
        android:drawable="@color/menubar_background"></item>

</selector>

I want to be able to dynamically change the background based on the enabled attribute on the View. The reason why I've chosen the enabled attribute is that I got the onClick event on that the LinearLayout and I need to disable it (making it unclickable); I had the same issue with the android:clickable attribute.

Thing is when I try to compile it I'm getting the error

java.lang.RuntimeException: Found data binding errors. ****/ data binding error ****msg:Cannot find the getter for attribute 'android:enabled' with value type boolean on android.widget.LinearLayout.

The viewModel.asd is just a public boolean / ObservableBoolean, I've tried both.

Can anyone explain what's going on and why am I getting the error? I can see the isEnabled / setEnabled methods in the View class (that Layouts extend).

Is there any way I can move on with my approach or do I have to change it entirely?

//EDIT: I might not need a two-way binding.. I don't know anymore // EDIT continued: It might have to do something with my ViewModel inheritance:

The layout file has a viewModel of type a.b.MainViewModel, but the field asd is int the a.b.BaseViewModel (MainViewModel extends BaseViewModel). Now what I'd want it to so is if I update the asd field from any other view extending the BaseViewModel it would automatically update the enabled attribute..

It doesn't work with Strings either.. I guess it's the inheritance thing

Kind regards, Marcin

Old answered 18/3, 2017 at 18:28 Comment(1)
Is asd a static field?Camus
E
5

There could be multiple reasons for your error. The first is that 2-way data binding doesn't work with the android:enabled attribute. This is because there is no callback to tell data binding that the property has changed.

2-way data binding works with most attributes in which the user enters data, so it is easiest to think of it as getting the data from the user to the model.

Another reason why you might receive an error like that is the model may not have a bindable property. You should use either an Observable:

public class ViewModel extends BaseObservable {
    private boolean asd = true;

    @Bindable
    public boolean getAsd() { return asd; }

    public void setAsd(boolean asd) {
        this.asd = asd;
        notifyPropertyChanged(this, BR.asd);
    }
}

or use ObservableFields:

public class ViewModel {
    public final ObservableBoolean asd = new ObservableField(true);
}

Without this, one way data binding will only work the first time -- if you change the ViewModel, the UI won't update.

It doesn't appear that you need 2-way data binding for the android:enabled attribute. One way data should work fine to change the state of the selector.

Earley answered 19/3, 2017 at 0:48 Comment(2)
Wouldn't it be possible to hijack the drawable state to get the enabled state change information for the 2-way binding?Camus
It is not a matter of getting the enabled state, it is a matter of being able to be notified when the enabled state changes. There are no external notifications for when enabled changes, so 2-way data binding doesn't work by default. If you add your own (create a listener and override something that allows the change), then you can create a 2-way expression with an additional BindingAdapter for the listener. See medium.com/google-developers/…Earley
O
0

So, I've tried to narrow down the reason of the problem and only after I added the edits to my former post I realized it might be indeed problem with the inheritance.

Basically, what I have is the binding for the main Activity (referred to as MainActivity) and separate bindings for the fragments being replaced inside that activity. Both of the binded viewmodels are extending the BaseViewModel that holds that boolean. The main activity binding has to update on every fragment update (say I have to hide/show something depending on the fragment I'm actually in).

Now, what I expected to happen was that if I set the boolean in the MainActivityViewModel (that would actually set the value in the base class AND replace "that" field in another ViewModel extending the base the former would update... Silly me, I realized I have to update the boolean in the MainViewModel directly. It's the basic Java concept and has nothing to do with Android..

One thing that I don't know how to explain, though, is that this boolean was meant to be returned from java.util.Set#contains method. I have a ObservableField observableSet (in the MainViewModel now) and the method isInTheSet(Object) returning set.contains(Object).

When I executed the method in the xml file (viewModel.isInTheSet(Object)) it didn't update the view when I changed the ObservableField value. However, when I changed the xml to use viewModel.observableSet.contains(Object) it worked.. Can anyone explain this?

Sorry for creating mass confusion here, I had a long day at work yesterday.

//EDIT: And yes, I didn't need the two-way binding..

Marcin

Old answered 19/3, 2017 at 10:25 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.