DataBinding: How to get resource by dynamic id?
AndroidAndroid DatabindingAndroid Problem Overview
I know that it is possible to reference resources in layout by their resource id:
android:text="@{@string/resourceName}"
However, I would like to reference resource by id which is known only at runtime. As a simple example, imagine we have such model:
public class MyPOJO {
public final int resourceId = R.string.helloWorld;
}
And now I need to use this value as a value in a format string. Let's call it
<string name="myFormatString">Value is: %s</string>
The most straightforward approach does not work:
android:text="@{@string/myFormatString(myPojo.resourceId)}"
This will just put integer value into placeholder (also it proves that I initialized my POJO correctly, so I'm not providing whole layout here).
I also tried using @BindingConversion
, but it did not worked (which is actually expected, but I tried anyway) - int
was still assigned to placeholder and binding method was not called.
How can I explicitly get resource by it's id in DataBinding library?
Android Solutions
Solution 1 - Android
Another solution is to create a custom @BindingAdapter
for it.
@BindingAdapter({"format", "argId"})
public static void setFormattedText(TextView textView, String format, int argId){
if(argId == 0) return;
textView.setText(String.format(format, textView.getResources().getString(argId)));
}
And then just provide the variables separately.
<TextView
app:format="@{@string/myFormatString}"
app:argId="@{myPojo.resourceId}"
You could use an array if you need multiple arguments, but in my case, one was sufficient.
Solution 2 - Android
As of June 2016 this is possible in XML:
android:text= "@{String.format(@string/my_format_string, myPojo.resourceId)}"
Solution 3 - Android
You can use:
android:text='@{(id > 0) ? context.getString(id) : ""}'
Solution 4 - Android
I ended up creating my own method:
public class BindingUtils {
public static String string(int resourceId) {
return MyApplication
.getApplication()
.getResources()
.getString(resourceId);
}
}
Declaring an import for it:
<data>
<import type="com.example.BindingUtils" />
...
</data>
And just calling it during binding:
android:text="@{@string/myFormatString(BindingUtils.string(myPojo.resourceId))}"
Would be nice to have out-of-the-box method for that. DataBinding is sitll in Beta - so maybe it will come in future.
Solution 5 - Android
Another solution if you already have Context
defined in your xml then you will not need to import String
class.
android:text="@{@string/myFormatString(context.getString(pojo.res))}"
will work for
<string name="myFormatString">Value is: %s</string>
If you don't have context in your xml. then follow this
<data>
<variable
name="context"
type="abc.UserActivity"/>
<variable
name="pojo"
type="abc.MyPOJO"/>
</data>
and in your Activity
ActivityUserBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_user);
binding.setPojo(new MyPOJO());
binding.setContext(this);
Solution 6 - Android
You can make use of automatic method selection described in the official documentation of binding adapters. The following description is taken from that document:
> For an attribute named example
, the library automatically tries to find the method setExample(arg)
that accepts compatible types as the argument. The namespace of the attribute isn't considered, only the attribute name and type are used when searching for a method.
>
> For example, given the android:text="@{user.name}"
expression, the library looks for a setText(arg)
method that accepts the type returned by user.getName()
. If the return type of user.getName()
is String
, the library looks for a setText()
method that accepts a String
argument. If the expression returns an int
instead, the library searches for a setText()
method that accepts an int
argument. The expression must return the correct type, you can cast the return value if necessary.
With that in mind, you can implement your own binding adapter that accepts the ID of a string resource as an int
argument.
@BindingAdapter("android:text")
fun setText(view: TextView, @StringRes resId: Int) {
if (resId == 0) {
view.text = null
} else {
view.setText(resId)
}
}
This will allow you to use the standard android:text
attribute to reference a string by its resource ID as well as its value.
<TextView
android:id="@+id/text_view_id"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{myPojo.resourceId}" />
Solution 7 - Android
Kotlin version:
@BindingAdapter("template", "resId")
fun TextView.setFormattedText(template: String, resId: Int) {
if (template.isEmpty() || resId == 0) return
text = template.format(resources.getString(resId))
}
in xml
<TextView
app:template="@{@string/myFormatString}"
app:resId="@{viewModel.resourceId}"/>
Solution 8 - Android
you can use context inside your XML
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text= "@{context.getString(myPojo.resourceId)}"
/>