I have one custom view holding another one. Hierarchy:
MyOuterView
->MyInnerView
MyInnerView
has an enum attribute like:
<attr name="myAttr" format="enum">
<enum name="foo" value="0"/>
<enum name="bar" value="1"/>
</attr>
So I can instantiate the component in MyOuterView
XML like:
<com.example.MyInnerView
....
app:myAttr="foo"/>
Which works of course.
The MyOuterView
offers an argument for customization itself. Based on this argument I want to set the argument of the MyInnerView
.
The wished behavior is that I can work with Data Binding like:
<com.example.MyInnerView
....
app:myAttr="@{data.getMyAttr()}"/>
where getMyAttr()
looks like:
public int getMyAttr() {
return myAttr; // returns 0 or 1
}
The result is a compile issue.
****/ data binding error ****msg:Cannot find the setter for attribute 'app:myAttr' with parameter type int on com.example.MyInnerView
So obviously I can't set the enum by value but only by name. Any idea besides creating the MyInnerView
programmatically? Please note that I can't change MyInnerView
.
I can't set the enum by value but only by name
That is not entirely true. You wouldn't be able to set the value with DataBinding even by name, the thing is that the value of an attribute defined in an xml is passed to the View
through the constructor. A View
could have both an attribute and a setter but that's not necessarily the case. For instance, is you set a value for android:text
of a TextView
, that value is set to com.android.internal.R.styleable.TextView_text
which is then retrieved from the AttributeSet
in the constructor. If you use DataBinding, setText()
is called, but these are 2 completely different things, the feature is the same but they're not related in the code.
Given that you can't modify MyInnerView
and that there is no setter you can call for myAttr
, your only option is to pass it through the constructor. DataBinding is simply not feasible, even with a BinderAdapter you wouldn't be able to set the value in the AttributeSet
because the View
is already instantiated at that point.
Option 1 - themes
Define a new attribute
<attr name="MyInnerViewAttrValue" format="integer" />
Then resolve this attribute with a style, for instance you could have
<style name="AppTheme.Foo">
<item name="MyInnerViewAttrValue">0</item>
</style>
<style name="AppTheme.Bar">
<item name="MyInnerViewAttrValue">1</item>
</style>
Set it in the layout xml
<com.example.MyInnerView
...
app:myAttr="?attr/MyInnerViewAttrValue" />
And then call setTheme(int)
in the Activity
before the views are instantiated.
Option 2 - custom BindingAdapter
If you wanted to set your value DataBinding (because you had a setter, or because you wanted to hack MyInnerView
with reflection) then you would need to create a custom BindingAdapter, for instance
@BindingAdapter("myAttrValue")
public static void setMyAttr(MyInnerView myInnerView, int value) {
switch (value) {
case 0:
myInnerView.foo();
break;
default:
myInnerView.bar();
}
}
Then in the layout xml
<com.example.lelloman.dummy.MyInnerView
...
myAttrValue="@{model.attr}" />
And give the int
value from your ViewModel
public class MyInnerViewModel {
public int getAttr() {
return 1234;
}
}
Unfortunately, you can use attribute name like app:myAttr="@{...}"
only if there is setter for that and this setter is mapped to attribute name or can be resolved by its name.
In your case, if this attribute is used only in View's constructor and view has not any public method for change this - you still have a few possible solution:
- extend View and implement logic to customize view like this attribute do (if possible)
- make BindingAdapter and use Reflection (if possible).
- create your own view by copy-paste and fix)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With