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
How to Pass a Value from One Activity to Another in Android
Disable Scrolling in All Mobile Devices
Android Deprecated Apache Module (Httpclient, Httpresponse, etc.)
Wait Firebase Async Retrieve Data in Android
Difference and Uses of Oncreate(), Oncreateview() and Onactivitycreated() in Fragments
How to Check If Location Services Are Enabled
Android Material and Appcompat Manifest Merger Failed
Android - How to Disable the Click of Home Button
How to Solve Java.Lang.Outofmemoryerror Trouble in Android
Trying to Attach a File from Sd Card to Email
How to Pass a Variable from Activity to Fragment, and Pass It Back
How Set Maximum Date in Datepicker Dialog in Android
How to Connect to More Than One Firebase Database from an Android App
...Have You Declared This Activity in Your Androidmanifest.Xml