269

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 :-)

dbm
  • 10,376
  • 6
  • 44
  • 56
  • 3
    [This answer](http://stackoverflow.com/a/24903097/4348328) to another similar question worked for me. No java necessary, but it only works within the same resource file. – mpkuth Aug 10 '16 at 21:51

11 Answers11

313

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>
Joseph Garrone
  • 1,662
  • 19
  • 20
Beeing Jk
  • 3,796
  • 1
  • 18
  • 29
  • 11
    this is the correct answer to the OP. This solution has other great benefits: **(1)** you can define the DTD in an external file and references it from any resource file to apply the substitution everywhere in your resources. **(2)** android studio let you rename/references/refactor the defined entities. Though , it does not autocomplete names while writing. (androidstudio 2.2.2) – Mauro Panzeri Nov 11 '16 at 10:17
  • @MauroPanzeri can you plz provide examples? – usernotnull Dec 14 '16 at 15:06
  • 3
    @RJFares You can go 2 ways: **(a)** "including" a whole entity file EG: look at [this answer](http://stackoverflow.com/a/31346100/4170781). OR **(b)**: including every single entity defined in an external DTD as shown [here](http://www.w3schools.com/xml/xml_dtd_entities.asp). Note that neither one is perfect because with (a) you will loose the "refactoring" cabilities and (b) is very verbose – Mauro Panzeri Dec 15 '16 at 17:36
  • Does this technique works with translators? Especially, the one that offered by Google? I'm getting an error in Android Studio as of v2.2.3 when using this – mr5 Mar 31 '17 at 06:04
  • 5
    Careful if you use this in a Android Library Project - you cannot overwrite it in your app. – zyamys May 31 '17 at 18:55
  • 2
    @MauroPanzeri didn't manage to reference external entity file. Do you mind to post an example? Thanks. – Ghedeon Jun 23 '17 at 10:39
  • @Ghedeon: for a working example [look here](https://stackoverflow.com/a/31346100) you just need to pay attention to the relative paths. but you'll lose the refactoring ability and i've not checked what happens with androidstudio's GUI for translations. – Mauro Panzeri Jul 06 '17 at 13:27
  • @MauroPanzeri gradle gives an error when using the SYSTEM keyword to reference a external entity, so i think they are not supported on android xml files. The error is (in spanish, sorry): `Error:(1, 3) Error: El contenido de los elementos debe constar de marcadores o datos de carácter con un formato correcto.` – Alvaro Gutierrez Perez Jul 15 '17 at 15:19
  • 1
    Ok, I managed to get it working with external entities references, using a mix of both methods that @MauroPanzeri wrote about in his comment. This is the way I got it working: ``` %app_name; ]>``` (sorry, line feeds not allowed here). Refactor ability is still maintained but only on the same file, the declaration of the entity in the external file is not recognized when refactoring. The path must be relative to the project root for gradle to take it (in the editor you will get a file not found error). – Alvaro Gutierrez Perez Jul 15 '17 at 15:36
  • A example based on this answer of referencing external entities: https://stackoverflow.com/a/46017525/3731798 – Joseph Garrone Sep 02 '17 at 19:41
  • 1
    has anyone been able to make it work with Android Plugin for Gradle 3.0.0 and Gradle 4.1 or later? I was working on Migrating my Android project and that suddenly stopped working? The error I get is 'The Entity 'something' was referenced, but not declared' – display name Nov 09 '17 at 23:18
  • 7
    is it possible to change the entity value when defining flavors in the gradle file? – Myoch Nov 19 '17 at 18:32
  • @JamesBond where you able to find any solution to make this work with Android Plugin for Gradle 3.0.0 and Gradle 4.1. If not, do you have any other suggestions?. I am still facing the same error - The Entity 'something' was referenced, but not declared' during migration. I had to copy all entities in all strings.xml to make it work. – vineeth Jan 17 '18 at 05:57
  • 2
    even I'm getting the same error `something' was referenced, but not declared`. Anyone found any solution? – Sarath Kn Feb 09 '18 at 07:11
  • 2
    Same problem : The entity "appname" was referenced, but not declared. – maudem Jun 18 '18 at 22:07
  • 15
    External entities are not supported by Android Studio anymore, since a bug discussed here: https://stackoverflow.com/a/51330953/8154765 – Davide Cannizzo Nov 03 '18 at 18:06
  • How about referencing integers from within a string? For instance, in _integers.xml_, `30`. And in _strings.xml_, `Your message needs to be at least @integer/min_len long`. I do not want to hard code this value because I know I will change it in the future. – TheRealChx101 Mar 23 '19 at 05:10
  • This causes a org.xml.sax.SAXException: Scanner State 24 not Recognized for me, Android Studio 3.6.1 – Jeff Padgett Apr 15 '20 at 16:38
  • 1
    Does anyone know if/how it's possible to get our custom entities recognised in a `<![CDATA[ ... ]]>` string resource? – ban-geoengineering Apr 24 '20 at 09:31
  • This gives me error on play store build. Locally it works fine but when app is uploaded to play store it doesn't pick the reference. – Harminder Singh Sep 01 '22 at 06:57
  • external dtd not works! – Fang Nov 02 '22 at 13:14
200

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.

Samuel Katz
  • 24,066
  • 8
  • 71
  • 57
Barry Fruitman
  • 12,316
  • 13
  • 72
  • 135
  • Which also answers the question: how do I refer to the app_name string in an activity title. :) – Stephen Hosking Nov 01 '12 at 05:16
  • 61
    This should not read "as long as you reference the entire string" (which you always to by definition) but "as long as the referring string resource only consists of the reference name". – sschuberth Nov 20 '12 at 14:57
  • 69
    This is not really a reference, this is just an alias, which doesn't solve the problem of composing strings. – Eric Woodruff Dec 23 '13 at 06:00
  • 4
    This is litterally useless, I can't think of any case where this can be useful. Just reference the actual string instead of the "alias" – Zam Sunk Nov 16 '17 at 07:41
  • 4
    This doesn't answer the question, they wanted one string embedded in another. – intrepidis Nov 19 '17 at 07:40
  • What if app_name looked somethin link My App %s$1 Can I reference to it and also pass a stringvalue to substitute %s$1 with? (Programatically similar to getString(R.String.app_name, "replacement");) – MahNas92 Jul 07 '21 at 12:13
102

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));
wojciii
  • 4,253
  • 1
  • 30
  • 39
Francesco Laurita
  • 23,434
  • 8
  • 55
  • 63
  • 5
    I was actually hoping for a "non-logic" solution. Nevertheless a fair enough answer (and a the sheer speed of it grants you a green check mark :-) – dbm Jan 20 '11 at 11:04
  • 39
    `String text = res.getString(R.string.message_text, res.getString(R.string.button_text));` is a little bit cleaner. – Andrey Novikov Jan 20 '11 at 11:23
  • This seems like the cleanest solution. Everything else is messy. – lenooh Jan 31 '22 at 10:33
35

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?

Community
  • 1
  • 1
Mayank Mehta
  • 689
  • 1
  • 8
  • 12
16

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.

Valeriy Katkov
  • 33,616
  • 20
  • 100
  • 123
7

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));
schnatterer
  • 7,525
  • 7
  • 61
  • 80
3

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.

jclehner
  • 1,440
  • 10
  • 18
  • This is the closest thing to an answer on this question. I think we need to look into hooking into the layout xml parsing. – Eric Woodruff Dec 23 '13 at 06:04
2

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

Ismail Iqbal
  • 2,774
  • 1
  • 25
  • 46
2

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}"
ndori
  • 1,934
  • 1
  • 18
  • 23
2

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.

pumnao
  • 560
  • 7
  • 13
0

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="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:

<!-- Auto generated during compilation -->
<?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-stem

César Muñoz
  • 535
  • 1
  • 6
  • 10
  • 2
    I tried your library. After following the steps you have given. It keeps giving build errors. Your library doesn't work at all – Vadiraj Purohit Dec 03 '19 at 17:00
  • I see, sorry to hear that. If you like, please create an issue here: https://github.com/LikeTheSalad/android-string-reference/issues with the logs and I can address it asap in case that it's a feature problem, or if it's a configuration issue I can also help you there. – César Muñoz Dec 04 '19 at 10:22