61

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?

Khemraj Sharma
  • 57,232
  • 27
  • 203
  • 212
Dmitry Zaytsev
  • 23,650
  • 14
  • 92
  • 146

8 Answers8

32

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.

Jim Pekarek
  • 7,270
  • 6
  • 33
  • 34
31

As of June 2016 this is possible in XML:

android:text= "@{String.format(@string/my_format_string, myPojo.resourceId)}"
Kaskasi
  • 1,330
  • 1
  • 14
  • 15
  • I have no way to try it right now, so just to confirm - will it insert the string for given resourceId or id itself as `int`? – Dmitry Zaytsev Jun 27 '16 at 11:16
  • 2
    It returns the resourceId – Robust Aug 11 '17 at 07:02
  • Oddly, it doesn't seem to work (silent compilation failure) when I'm using one of my own methods, instead of `String.format`, even though it compiles fine with that same method if I hardcode an integer in place of `@string/my_format_string`. I ended up moving the string substitution to Kotlin code, and just pass in the completed string. – big_m Aug 18 '20 at 14:20
28

You can use:

android:text='@{(id > 0) ? context.getString(id) : ""}'
g00glen00b
  • 41,995
  • 13
  • 95
  • 133
Dalvinder Singh
  • 2,129
  • 4
  • 21
  • 20
  • and as ipcjs said above, context is there, there is no need to import, and if resourc-string needs to be refenced, use '@string/flight_departures' within databinding like normal, see https://developer.android.com/topic/libraries/data-binding/expressions – arberg Apr 15 '19 at 14:45
  • I thing this is the best answer for this, since we can get the context directly in the xml is much easier than create an entire binding adapter, we can just have a LiveData in our viewModel and that's all – Francisco Mendoza Sep 26 '22 at 22:36
13

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.

Dmitry Zaytsev
  • 23,650
  • 14
  • 92
  • 146
7

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);
Khemraj Sharma
  • 57,232
  • 27
  • 203
  • 212
3

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}" />
Sefa Keleş
  • 81
  • 1
  • 6
3

you can use context inside your XML

 <TextView
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 android:text= "@{context.getString(myPojo.resourceId)}"
   />
Momen Zaqout
  • 1,508
  • 1
  • 16
  • 17
1

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}"/>
Yazazzello
  • 6,053
  • 1
  • 19
  • 21