Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How Do We Get SpannedString Objects From String Resources Using Data Binding?

Florina Muntenescu wrote up a cool post about using <annotation> in string resources for being able to have flexible markup that you can process in your app using custom spans. I am trying to leverage it in data binding, but I cannot quite figure out how to get a SpannedString edition of the string resource from data binding.

In my layout, I have app:thingy="@{@string/my_annotated_string}" as an attribute on a TextView. I have a binding adapter set up to handle thingy attributes. However, the data binding system seems to insist that my value is a String.

I have tried:

@BindingAdapter("thingy")
@JvmStatic
fun handleThingy(textView: TextView, thingy: SpannedString) { /* stuff goes here */ }

and:

@BindingAdapter("thingy")
@JvmStatic
fun handleThingy(textView: TextView, thingy: Spanned) { /* stuff goes here */ }

and:

@BindingAdapter("thingy")
@JvmStatic
fun handleThingy(textView: TextView, @StringRes thingy: Int) { /* stuff goes here */ }

In all cases, I get Cannot find the setter for attribute 'app:thingy' with parameter type java.lang.String on android.widget.TextView build errors.

If I use String or CharSequence for the thingy parameter type, it builds, but then I get passed a String and I do not have my annotation spans from the string resource.

So, how can I either:

  • Get the SpannedString corresponding to my string resource (i.e., what you get from getText() instead of getString()), or
  • Get the string resource ID of my string resource, so I can call getText() myself to get my SpannedString
like image 909
CommonsWare Avatar asked Nov 01 '18 18:11

CommonsWare


Video Answer


2 Answers

As an expression, @string/my_annotated_string evaluates to a string. Eventhough it resembles a string resource reference in XML, it's actually only a String value.

It would be nice to have a @text/my_annotated_string version as well, but as of the documentation this is not available.

Instead you'd have to use the actual resource within your binding expression:

app:thingy="@{string.my_annotated_string}"
app:thingy="@{context.getText(string.my_annotated_string)}"

This is assuming the import of the string class:

<import type="path.to.R.string"/>
like image 109
tynn Avatar answered Oct 01 '22 20:10

tynn


Here is a maybe slightly less icky way:

Define the annotated string.

<string name="my_annotated_string">A <annotation font="title_emphasis">cool</annotation> annotation <annotation font="title_emphasis">thingy</annotation>.</string>

Place a reference to that string resource into a TypedArray:

<resources>
    <array name="annotated_text">
        <item>@string/my_annotated_string</item>
    </array>
</resources>

Reference the TypedArray in the layout:

<TextView
    android:id="@+id/textView"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:thingy="@{@typedArray/annotated_text}" />

Finally, set a BindingAdapter to capture the SpannedString with the annotations:

@BindingAdapter("thingy")
public static void setThingy(TextView textView, TypedArray strings) {
    SpannedString ss = (SpannedString) strings.getText(0);
    Object spans[] = ss.getSpans(0, ss.length(), Object.class);
}

Although a little involved, this works. If there are multiple strings, the array can be expanded.

like image 24
Cheticamp Avatar answered Oct 01 '22 22:10

Cheticamp