Home » Android » android – Reference one string from another string in strings.xml?

android – Reference one string from another string in strings.xml?

Posted by: admin March 10, 2020 Leave a comment

Questions:

I would like to reference a string from another string in my strings.xml file, like below (specifically note the end of the “message_text” string content):

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="button_text">Add item</string>
    <string name="message_text">You don't have any items yet! Add one by pressing the \'@string/button_text\' button.</string>
</resources>

I’ve tried the above syntax but then the text prints out the “@string/button_text” as clear text. Not what I want. I would like the message text to print “You don’t have any items yet! Add one by pressing the ‘Add item’ button.”

Is there any known way to achieve what I want?

RATIONALE:
My application has a list of items, but when that list is empty I show a “@android:id/empty” TextView instead. The text in that TextView is to inform the user how to add a new item. I would like to make my layout fool-proof to changes (yes, I’m the fool in question 🙂

How to&Answers:

A nice way to insert a frequently used string (e.g. app name) in xml without using Java code:
source

    <?xml version="1.0" encoding="utf-8"?>
    <!DOCTYPE resources [
      <!ENTITY appname "MyAppName">
      <!ENTITY author "MrGreen">
    ]>

<resources>
    <string name="app_name">&appname;</string>
    <string name="description">The &appname; app was created by &author;</string>
</resources>

UPDATE:

You can even define your entity globaly e.g:

res/raw/entities.ent:

<!ENTITY appname "MyAppName">
<!ENTITY author "MrGreen">

res/values/string.xml:

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE resources [
    <!ENTITY % ents SYSTEM "./res/raw/entities.ent">
    %ents;   
]>

<resources>
    <string name="app_name">&appname;</string>
    <string name="description">The &appname; app was created by &author;</string>
</resources>

Answer:

It is possible to reference one within another as long as your entire string consists of the reference name. For example this will work:

<string name="app_name">My App</string>
<string name="activity_title">@string/app_name</string>
<string name="message_title">@string/app_name</string>

It is even more useful for setting default values:

<string name="string1">String 1</string>
<string name="string2">String 2</string>
<string name="string3">String 3</string>
<string name="string_default">@string/string1</string>

Now you can use string_default everywhere in your code and you can easily change the default at any time.

Answer:

I think you can’t. But you can “format” a string as you like:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="button_text">Add item</string>
    <string name="message_text">You don't have any items yet! Add one by pressing the %1$s button.</string>
</resources>

In the code:

Resources res = getResources();
String text = String.format(res.getString(R.string.message_text),
                            res.getString(R.string.button_text));

Answer:

In Android you can’t concatenate Strings inside xml

Following is not supported

<string name="string_default">@string/string1 TEST</string>

Check this link below to know how to achieve it

How to concatenate multiple strings in android XML?

Answer:

I created simple gradle plugin which allows you to refer one string from another. You can refer strings which are defined in another file, for example in different build variant or library. Cons of this approach – IDE refactor won’t find such references.

Use {{string_name}} syntax to refer a string:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="super">Super</string>
    <string name="app_name">My {{super}} App</string>
    <string name="app_description">Name of my application is: {{app_name}}</string>
</resources>

To integrate the plugin, just add next code into you app or library module level build.gradle file

buildscript {
  repositories {
    maven {
      url "https://plugins.gradle.org/m2/"
    }
  }
  dependencies {
    classpath "gradle.plugin.android-text-resolver:buildSrc:1.2.0"
  }
}

apply plugin: "com.icesmith.androidtextresolver"

UPDATE:
The library doesn’t work with Android gradle plugin version 3.0 and above because the new version of the plugin uses aapt2 which packs resources into .flat binary format, so packed resources are unavailable for the library. As a temporary solution you can disable aapt2 by setting android.enableAapt2=false in your gradle.properties file.

Answer:

You could use your own logic, that resolves the nested strings recursively.

/**
 * Regex that matches a resource string such as <code>@string/a-b_c1</code>.
 */
private static final String REGEX_RESOURCE_STRING = "@string/([A-Za-z0-9-_]*)";

/** Name of the resource type "string" as in <code>@string/...</code> */
private static final String DEF_TYPE_STRING = "string";

/**
 * Recursively replaces resources such as <code>@string/abc</code> with
 * their localized values from the app's resource strings (e.g.
 * <code>strings.xml</code>) within a <code>source</code> string.
 * 
 * Also works recursively, that is, when a resource contains another
 * resource that contains another resource, etc.
 * 
 * @param source
 * @return <code>source</code> with replaced resources (if they exist)
 */
public static String replaceResourceStrings(Context context, String source) {
    // Recursively resolve strings
    Pattern p = Pattern.compile(REGEX_RESOURCE_STRING);
    Matcher m = p.matcher(source);
    StringBuffer sb = new StringBuffer();
    while (m.find()) {
        String stringFromResources = getStringByName(context, m.group(1));
        if (stringFromResources == null) {
            Log.w(Constants.LOG,
                    "No String resource found for ID \"" + m.group(1)
                            + "\" while inserting resources");
            /*
             * No need to try to load from defaults, android is trying that
             * for us. If we're here, the resource does not exist. Just
             * return its ID.
             */
            stringFromResources = m.group(1);
        }
        m.appendReplacement(sb, // Recurse
                replaceResourceStrings(context, stringFromResources));
    }
    m.appendTail(sb);
    return sb.toString();
}

/**
 * Returns the string value of a string resource (e.g. defined in
 * <code>values.xml</code>).
 * 
 * @param name
 * @return the value of the string resource or <code>null</code> if no
 *         resource found for id
 */
public static String getStringByName(Context context, String name) {
    int resourceId = getResourceId(context, DEF_TYPE_STRING, name);
    if (resourceId != 0) {
        return context.getString(resourceId);
    } else {
        return null;
    }
}

/**
 * Finds the numeric id of a string resource (e.g. defined in
 * <code>values.xml</code>).
 * 
 * @param defType
 *            Optional default resource type to find, if "type/" is not
 *            included in the name. Can be null to require an explicit type.
 * 
 * @param name
 *            the name of the desired resource
 * @return the associated resource identifier. Returns 0 if no such resource
 *         was found. (0 is not a valid resource ID.)
 */
private static int getResourceId(Context context, String defType,
        String name) {
    return context.getResources().getIdentifier(name, defType,
            context.getPackageName());
}

From an Activity, for example, call it like so

replaceResourceStrings(this, getString(R.string.message_text));

Answer:

I’m aware that this is an older post, but I wanted to share the quick ‘n dirty solution that I’ve come up with for a project of mine. It only works for TextViews but could
be adapted to other widgets as well. Note that it requires the link to be enclosed in square brackets (e.g. [@string/foo]).

public class RefResolvingTextView extends TextView
{
    // ...

    @Override
    public void setText(CharSequence text, BufferType type)
    {
        final StringBuilder sb = new StringBuilder(text);
        final String defPackage = getContext().getApplicationContext().
                getPackageName();

        int beg;

        while((beg = sb.indexOf("[@string/")) != -1)
        {
            int end = sb.indexOf("]", beg);
            String name = sb.substring(beg + 2, end);
            int resId = getResources().getIdentifier(name, null, defPackage);
            if(resId == 0)
            {
                throw new IllegalArgumentException(
                        "Failed to resolve link to @" + name);
            }

            sb.replace(beg, end + 1, getContext().getString(resId));
        }

        super.setText(sb, type);
    }
}

The downside of this approach is that setText() converts the CharSequence to a
String, which is an issue if you pass things like a SpannableString; for my
project this wasn’t an issue since I only used it for TextViews that I didn’t need
to access from my Activity.

Answer:

In addition to the above answer by Francesco Laurita https://stackoverflow.com/a/39870268/9400836

It seems there is a compile error “&entity; was referenced, but not declared” which can be solved by referencing the external declaration like this

res/raw/entities.ent

<!ENTITY appname "My App Name">

res/values/strings.xml

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE resources [
    <!ENTITY appname SYSTEM "/raw/entities.ent">
]>
<resources>
    <string name="app_name">&appname;</string>
</resources

Although it compiles and runs it has an empty value. Maybe someone knows how to solve this. I would have posted a comment but minimum reputation is 50.

Answer:

You could use string placeholders (%s) and replace them using java at run-time

<resources>
<string name="button_text">Add item</string>
<string name="message_text">Custom text %s </string>
</resources>

and in java

String final = String.format(getString(R.string.message_text),getString(R.string.button_text));

and then set it to the place where it uses the string

Answer:

With the new data binding you can concatenate and do much more in your xml.

for example if you got message1 and message2 you can:

android:text="@{@string/message1 + ': ' + @string/message2}"

you can even import some text utils and call String.format and friends.

unfortunately if you want to reuse it in several places it can get messy, you don’t want this code pieces everywhere. and you can’t define them in xml in one place (not that I know of)
so for that you can create a class that will encapsulate those compositions:

public final class StringCompositions {
    public static final String completeMessage = getString(R.string.message1) + ": " + getString(R.string.message2);
}

then you can use it instead (you will need to import the class with data binding)

android:text="@{StringCompositions.completeMessage}"

Answer:

I’ve created a small library that allows you to resolve these placeholders at buildtime, so you won’t have to add any Java/Kotlin code to achieve what you want.

Based on your example, you’d have to set up your strings like this:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="button_text">Add item</string>
    <string name="template_message_text">You don't have any items yet! Add one by pressing the ${button_text} button.</string>
</resources>

And then the plugin will take care of generating the following:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="message_text">You don't have any items yet! Add one by pressing the Add item button.</string>
</resources>

It works also for localized and flavor strings, also the generated strings will keep themselves updated whenever you make changes to either your templates or its values.

More info here: https://github.com/LikeTheSalad/android-string-reference