Reputation: 2571
I'm trying to use live data with data binding for TextInputLayout
using a class like this:
class MutableLiveDataWithErrorText<T> : MutableLiveData<T>() {
val errorText = MutableLiveData<String>().apply { value = "" }
}
Now, when trying to use it for error text in the xml,
<layout>
<data>
<!-- ... -->
<variable
name="target"
type="com.my.app.MutableLiveDataWithErrorText<String>" />
</data>
<com.google.android.material.textfield.TextInputLayout
app:errorEnabled="true"
app:errorText="@{target.errorText}">
<!-- ... -->
</com.google.android.material.textfield.TextInputLayout>
</layout>
I get this error:
Cannot find getter 'getErrorText' for type String.
I tried creating a BindingAdapter to get around this:
@BindingAdapter("errorTextLive")
fun setErrorTextLive(
view: TextInputLayout,
liveDataWithErrorText: MutableLiveDataWithErrorText<String>
) {
if (liveDataWithErrorText.errorText.value.isNullOrEmpty().not()) {
view.error = liveDataWithErrorText.errorText.value
}
}
with xml assignment changed to:
app:errorTextLive="@{target}"
which makes the compilation succeed, but changes to target.errorText
are no longer observed, instead it observes changes in target
, updating errorText
only when target
's value changes.
Is there a way to make the it observe target.errorText
?
Upvotes: 0
Views: 944
Reputation: 5371
It's bad pattern to pass view model as set of fields instead of one composite object. If you need to pass at least two variable to data-binding, you need to create view model with this fields - it help you to make changes more flexible.
For example, you can define view model like this one:
class SimpleViewModel : ViewModel() {
/**
* Expose MutableLiveData to enable two way data binding
*/
val textData = MutableLiveData<String>().apply { value = "" }
/**
* Expose LiveData for read only fields
*/
val errorText = Transformations.map(textData, ::validateInput)
/**
* Validate input on the fly
*/
private fun validateInput(input: String): String? = when {
input.isBlank() -> "Input is blank!"
else -> null
}
}
on layout side it very closer to your variant:
<layout>
<data>
<variable
name="vm"
type="com.example.SimpleViewModel" />
</data>
<android.support.design.widget.TextInputLayout
app:errorEnabled="true"
app:errorText="@{vm.errorText}">
<android.support.design.widget.TextInputEditText
android:text="@={vm.textData}"/>
</android.support.design.widget.TextInputLayout>
</layout>
NOTE: it is not necessary SimpleViewModel
to extend ViewModel
, but it allow your data to survive configuration changes out of the box
Upvotes: 1
Reputation:
Is there a way to make the it observe target.errorText?
I don't think so. The problem is, that the databinding library is resolving target
inside target.errorText
first and sees that it is of type MutableLiveData<String>
, it then automatically gets the value of target
, which is of type String and then tries to call getErrorText() on that String object, which leads to the error you see.
I had a similar use-case and I resorted to creating the following class:
class <T> ValidatableValue {
val liveData = MutableLiveData<T>() // The actual value.
// Other helper livedatas and functions.
val isValid = MutableLiveData<Boolean>()
val errorMessage = MutableLiveData<String>()
fun validate() { ... }
}
I can then use all these LiveData objects in the databinding layout.
Upvotes: 1