me.at.coding
me.at.coding

Reputation: 17654

Two-way databinding with observable instance fields on the View?

When using Android two-way databinding, do I have to use static BindingAdapters on the View or is it somehow possible to simply use observable instance fields? In the documentation I always see observable fields only on the ViewModels, not on the View. I tried implementing observable fields on my View with

var myValue: String = ""
@Bindable get(): String {
    return field
}
set(value: String) {
    field=value
    setText(value)
    notifyPropertyChanged(BR.myValue) // my View implements the Observable interface
}

but when I compile this (with ./gradlew assembleDebug --stacktrace to get the details), it fails with

ERROR: Cannot find a getter for <com.example.test.MyAutoCompleteTextView app:myValue> 
that accepts parameter type 'java.lang.String'

If a binding adapter provides the getter, check that the adapter is annotated correctly
and that the parameter type matches. 

So, isn't it possible to use observable fields on the View side of two-way databinding like it's possible on the ViewModel? The reason I want to use observable fields instead of static BindingAdapters is that my View has some more complex logic/state than I can handle in the BindingAdapter (well, from the static BindingAdapter I could just call through to myViewInstance.myValue, but somehow that feels wrong to me)

Update

I built a minimum (not) working example, available on Github By default it uses one-way binding, which works fine. Changing

app:myValue="@{viewModel.realValue}"

to

app:myValue="@={viewModel.realValue}"

in activity_main.xml will lead to not very informative compilation errors. Use ./gradlew assembleDebug --stacktraceto get a long output which includes

ERROR: Cannot find a getter for 
<com.example.test.MyAutoCompleteTextView app:myValue> 
that accepts parameter type 'java.lang.String'

Can anyone have a look at this and let me know what I am doing wrong?

Upvotes: 2

Views: 1397

Answers (1)

Archita Singh
Archita Singh

Reputation: 21

When using Android two-way databinding, do I have to use static BindingAdapters on the View or is it somehow possible to simply use observable instance fields?

I think it's pretty much possible for you to do that with the Observable instance fields. However if this logic is being reused somewhere else as well, I would personally prefer to use BindingAdapters.

In your Observable class, modify your code to this:

@get:Bindable
var myValue: String = ""
    set(value) {
        field = value 
        notifyPropertyChanged(BR.myValue) // my View implements the Observable interface
    }

I checked out your repo and realized that you are using MutableLiveData now, so you don't need the above code in that case. I also couldn't find any BindingAdapter there. Make sure your code BindingAdapter looks somewhat like this:

@BindingAdapter("myValue")
@JvmStatic
fun TextView.setMyValue(realValue: String) {
    val finalValue : String = realValue.dataManipulation() // Do your data manipulation here
    setText(finalValue) // Make sure it's a string
}

And then finally your TextView in XML should look like this:

<com.example.test.MyAutoCompleteTextView
    ...
    app:myValue="@{viewModel.realValue}"
    ... />

Let me know if this worked.



EDIT

So what we are looking for is basically Bindable variables in BaseObservable class. Let's focus on three things: your XML, your activity/fragment and your BindingModel class.

Your BindingModel class basically extends your BaseObservable class.

class BindingModel: BaseObservable(){
    @get:Bindable
    var myValue: String = ""
        set(value) {
            var finalValue = manipulateData(value) //Write your logic here
            field = finalValue
            notifyPropertyChanged(BR.myValue) // my View implements the Observable interface
    }
...
}

In your Activity/Fragment, create a variable of type BindingModel. Observe your LiveData and update the variable.

class MainActivity : AppCompatActivity() {
...
val model = BindingModel()
...
override fun onCreate(savedInstanceState: Bundle?) {
    ...
    binding.lifecycleOwner = this // Specify the current activity as the lifecycle owner.
    binding.model = model
    viewModel.realValue.observe(this, Observer {
        model.myValue = it
    })
} // End of onCreate()
...
} // End of Activity code

Moving on to your XML, you need to declare the model data binding variable there.

...
<data>
...
<variable
    name="model" //Make sure the name is same
    type="com.example.test.<path-to-your-file>.BindingModel" />
</data>
...
<com.example.test.MyAutoCompleteTextView
...
android:text="@{model.realValue}" //Don't forget this
... />

And voila! It should work. Let me know if it doesn't.

Upvotes: 1

Related Questions