How to Have Placeholders in Strings.Xml for Runtime Values

Is it possible to have placeholders in strings.xml for runtime values?

Formatting and Styling

Yes, see the following from String Resources: Formatting and Styling

If you need to format your strings using String.format(String, Object...), then you can do so by putting your format arguments in the string resource. For example, with the following resource:

<string name="welcome_messages">Hello, %1$s! You have %2$d new messages.</string>

In this example, the format string has two arguments: %1$s is a string and %2$d is a decimal number. You can format the string with arguments from your application like this:

Resources res = getResources();
String text = String.format(res.getString(R.string.welcome_messages), username, mailCount);

Basic Usage

Note that getString has an overload that uses the string as a format string:

String text = res.getString(R.string.welcome_messages, username, mailCount);

Plurals

If you need to handle plurals, use this:

<plurals name="welcome_messages">
<item quantity="one">Hello, %1$s! You have a new message.</item>
<item quantity="other">Hello, %1$s! You have %2$d new messages.</item>
</plurals>

The first mailCount param is used to decide which format to use (single or plural), the other params are your substitutions:

Resources res = getResources();
String text = res.getQuantityString(R.plurals.welcome_messages, mailCount, username, mailCount);

See String Resources: Plurals for more details.

Use Strings with placeholder in XML layout android

You can use something like this.
Write your string in your (strings.xml) with declaring a string variable (%1$s) inside it. For decimal, we use (%2$d).

<string name="my_string">My string name is %1$s</string>

And inside the android code (yourFile.java), use this string where you want it.

String.format(getResources().getString(R.string.my_string), stringName);

This is not a good answer but it may help you get some idea to get going.

Thanks.

Are parameters in strings.xml possible?

Yes, just format your strings in the standard String.format() way.

See the method Context.getString(int, Object...) and the Android or Java Formatter documentation.

In your case, the string definition would be:

<string name="timeFormat">%1$d minutes ago</string>

How to use sealed class for placeholder values in string resource

This is more a question about how to check for the type of a subclass of a sealed class (or sealed interface). Just to avoid any confusion, it should be made clear that these are Kotlin features and are not related to Jetpack Compose.

But yes, they can be used inside Composables as well or anywhere you want, really.

You would use a when (...) expression on the value of your sealed class to determine what to do based on the (sub)type of your sealed class (it works the same for sealed interfaces). Inside the when expression you then use the is operator to check for different subtypes.

val result = when (it) {
is Clothes.FixedSizeClothing -> {
// it.size and it.placeholder are accessible here due to Kotlin smart cast
// do something with them...
// last line will be returned as the result
}
is Clothes.MultiSizeClothing -> {
// it.sizes and it.placeholders are accessible here due to Kotlin smart cast
// do something with them...
// last line will be returned as the result
}

In situations when you don't need the result you just omit the val result = part. Note that the name result is arbitrary, pick whatever best describes the value you are creating.

The advantage of this type of the when expression is that it will give you a warning (and in future versions an error) if you forget one of the subtypes inside the when expression. This means the when expression is always exhaustive when there is no warning present, i.e. the Kotlin compiler checks at compile-time for all subtypes of the specific sealed class that are defined inside your whole codebase and makes sure that you are accounting for all of them inside the when expression.

For more on sealed classes inside a when expression see https://kotlinlang.org/docs/sealed-classes.html#sealed-classes-and-when-expression

In your case, you would do the same to generate the text value that you would then pass into the Text(text = ...) composable.

    Scaffold(
topBar = { ... },
content = { it ->
Row {
LazyColumn(
modifier = Modifier.padding(it)
) {
items(clothingItems) {
val text = when (it) {
is Clothes.FixedSizeClothing ->
stringResource(id = R.string.size, it.placeholder)
is Clothes.MultiSizeClothing ->
stringResource(id = R.string.sizes, it.placeholders[0], it.placeholders[1])
}
Text(text = text)
}
}
}
},
containerColor = MaterialTheme.colorScheme.background
)

I used the stringResource(@StringRes id: Int, vararg formatArgs: Any): String version of the call above to construct the final text. See here for other options available in Compose https://developer.android.com/jetpack/compose/resources

If you do want to store the presentation String inside your data classes as you are trying to do in your example you could store the resource id instead of the resolved String.
Also since you are using integer placeholders (%1$d) in your resource strings, the type of your size and sizes values can be Int. So all together that would be something like this

import androidx.annotation.StringRes

sealed class Clothes {
data class FixedSizeClothing(@StringRes val size: Int, val placeholder: Int): Clothes()

data class MultiSizeClothing(@StringRes val sizes: Int, val placeholders: List<Int>): Clothes()
}

And then when you define the items you would not call stringResource(id = ...) anymore and your size and sizes values are just integers.

val clothingItems = remember {
listOf(
Clothes.FixedSizeClothing(size = R.string.size, placeholder = 8),
Clothes.MultiSizeClothing(sizes = R.string.sizes, placeholders = listOf(0, 2))
)
}

The added benefit of this is that now you do not need a Composable context (or a Context or a Resources reference) to create instances of your Clothes sealed class.

Then when declaring the UI, you would do something like this

LazyColumn(
modifier = Modifier.padding(it)
) {
items(clothingItems) {
val text = when (it) {
is Clothes.FixedSizeClothing ->
stringResource(id = it.size, it.placeholder)
is Clothes.MultiSizeClothing ->
stringResource(id = it.sizes, it.placeholders[0], it.placeholders[1])
}
Text(text = text)
}
}

unit getString with dynamic placeholders

On mockito

`when`(getString(R.string.withPlaceHolder)).thenReturn("#$1s string with placeholder")

On class being tested dont use getString with second parameter. Instead use String.Format()

String.format(getString(R.string.withPlaceholder),"placeholder text here")

Assigning strings.xml resource values to string in MainActivity fails

Because you can't access the resources until the Activity is initialized- in onCreate, after calling super. Before then it isn't set up properly. The this pointer also won't work until the constructor has been called, and can't be used to statically initialize variables.

Basically, move all the code to onCreate.



Related Topics



Leave a reply



Submit