- What is it
- How to use
- Use case examples
- Adding it to your project
- Running it manually
- Donations
- License
Android Stem is a Gradle plugin that resolves placeholders of XML strings referenced into other XML strings at build time. You won't have to write any Java or Kotlin code into your project to make it work, and you will still be able to access to the 'resolved' strings the same way as with any other manually added string to your XML files.
In other words, if you're looking to do something like this:
<resources>
<string name="app_name">My App Name</string>
<string name="welcome_message">Welcome to ${app_name}</string>
</resources>
<!-- Auto generated during compilation -->
<resources>
<string name="welcome_message">Welcome to My App Name</string>
</resources>
Without having to write any Java or Kotlin code, then Android Stem might help you.
All you have to do is to define string templates inside your XML values' files, the file to add these templates to can be any file inside your values folders, not necessarily the "strings.xml" file but any other XML file within the same directory will work too.
In order to create a template all you need to do is to add references to other strings in the form
of "placeholders" into the string you want to use as template, the placeholder format is ${another_string_name}
where
"another_string_name" will be the name of any other string you have in your project or in a library of yours that
serves as a "Stem provider" one, more on it below.
Following our example above for our "my_message" template, let's say that we have another string in our project named "app_name" (which content is "My app name") and we want to place it inside our "my_message" template, we can do it like so:
<string name="my_message">Welcome to ${app_name}</string>
A template can contain from one to any amount of placeholders. Any string within your values' folder (even other templates) can be referenced inside a placeholder. And that's it, we've defined a template. Meaning that when we compile, we'll get as a result the following "resolved" string:
<!-- Auto generated during compilation -->
<string name="my_message">Welcome to My app name</string>
The process that resolves the string templates will run during your app's compilation process, based on that, there's many ways of running it, some of those could be:
- By pressing on the "play" button of Android Studio:
- Or, by pressing on the "make" button on Android Studio:
- Or, if you prefer command line, then you can run it by calling the build command:
./gradlew build
or the assemble command:./gradlew assemble
or by calling the specific task to resolve the strings which has the following format:./gradlew resolve[BUILD_VARIANT]Placeholders
more info on this command below under "Running it manually" .
After the task has run, now you will be able to access to the "resolved" strings where you'll see that your placeholders have been replaced by the actual referenced values.
The resolved strings go into your app's build
folder, specifically under the build/generated/resolved
path. That's
where Android Stem places them into when it is run.
The following cases are supported:
- Regular main strings.
- Localized (language-specific) strings.
- Flavor specific strings.
- Flavor specific with localized strings.
Both values and templates can be overridden for a different language and also for a different flavor. So if for example you have templates in your project which contain the app name placeholder (e.g. ${app_name}) then if you need to create a flavor with a different app name value, you just have to override the 'app_name' string inside the flavor's 'values' folder and that's it, now for this flavor you'll get all the old strings but with the new app_name.
Same for languages, based on the example above, if you need to translate your 'my_message' string to spanish for example, you just have to override the template 'my_message', inside the 'values-es' folder, and you'll get the translated 'my_message' string in the 'resolved.xml' file inside the 'values-es' folder.
Stem should work out of the box as you'd expect it to, however, depending on each case, some projects
might have special needs for which some parts of how Stem works might need to be adjusted to meet those needs.
Stem can be configured in your build.gradle
file where Stem is applied, as shown below along with the currently
available configurable parameters.
// build.gradle file where "com.likethesalad.stem" or "com.likethesalad.stem-library" is applied.
androidStem {
// Even though Stem resolves your templates no matter their localization, it searches for templates amongst your
// string resources within the folder "values" only by default (which will be taken as reference for other languages
// when resolving them).
// This is because, ideally, if you have a string that will need a ${placeholder} in it, it should have it
// inside any of that same string's different languages, including the default one ("values").
// So in order to avoid checking for the same strings across different languages looking for ${placeholders}, which could mean
// an expensive processing operation depending on the project, Stem by default only checks the default
// strings when looking for templates, and then based on what it finds within the default strings, it applies
// the placeholder resolving process to all other languages.
//
// However, in some cases some projects might have a string withing the "values" folder that has no ${placeholders}
// in it, but its translations might have them instead (check issue #21 for context). For those cases, you
// could enable this flag so that Stem looks for templates within string resources within the "values" dir
// and also within any language-specific values folders as well.
// Please bear in mind that this might cause performance penalties.
includeLocalizedOnlyTemplates = false // disabled by default
}
Here's a couple of examples for some of the use cases supported by Android Stem
Within our app/main/res/values
folder, we have the following file:
<!--strings.xml-->
<resources>
<string name="app_name">Test</string>
<string name="welcome_message">Welcome to ${app_name}</string>
</resources>
After building our project we get:
<!--Auto generated during compilation-->
<resources>
<string name="welcome_message">Welcome to Test</string>
</resources>
Within our app/main/res/values
folder, we have the following files:
<!--strings.xml-->
<resources>
<string name="app_name">Test</string>
<string name="welcome_message">Welcome to ${app_name}</string>
<string name="app_version_name">The version for ${app_name} is ${my_version}</string>
</resources>
<!--my_configs.xml-->
<resources>
<string name="my_version">1.0.0</string>
</resources>
After building our project we get:
<!--Auto generated during compilation-->
<resources>
<string name="app_version_name">The version for Test is 1.0.0</string>
<string name="welcome_message">Welcome to Test</string>
</resources>
So no matter which file contains a template or a value used in a template, as long as it's within your app's values folders, then the plugin will find it.
Within our app/main/res/values
folder, we have the following file:
<!--strings.xml-->
<resources>
<string name="app_name">Test</string>
<string name="welcome_message">Welcome to ${app_name}</string>
</resources>
Then, Within our app/main/res/values-es
folder, we have the following file:
<!--any_file.xml-->
<resources>
<string name="welcome_message">Bienvenido a ${app_name}</string>
</resources>
After building our project, what we get for our default values
folder is:
<!--Auto generated during compilation-->
<resources>
<string name="welcome_message">Welcome to Test</string>
</resources>
And then what we get for our spanish values-es
folder is:
<!--Auto generated during compilation-->
<resources>
<string name="welcome_message">Bienvenido a Test</string>
</resources>
Let's say we've defined a flavor in our project, named demo
, then:
Within our app/main/res/values
folder, we have the following files:
<!--strings.xml-->
<resources>
<string name="app_name">Test</string>
<string name="welcome_message">Welcome to ${app_name}</string>
<string name="app_version_name">The version for ${app_name} is ${my_version}</string>
</resources>
<!--my_configs.xml-->
<resources>
<string name="my_version">1.0.0</string>
</resources>
And for our app/demo/res/values
folder we add the following file:
<!--any_file.xml-->
<resources>
<string name="app_name">Demo app</string>
</resources>
After building the demo
variant of our project, we'll get for such variant:
<!--Auto generated during compilation-->
<resources>
<string name="app_version_name">The version for Demo app is 1.0.0</string>
<string name="welcome_message">Welcome to Demo app</string>
</resources>
So we see that the app_name
value has been overridden by the demo's app_name, this doesn't only happen for values but
also for templates, we can also override templates within our demo's resources.
Those were some of the use cases that you can achieve using Android Stem, there's more of them such as overriding flavors' multi languages from the base values folder and also working with multi-dimension flavors. You can play around with it, it all should work the way you'd expect it to work.
We're going to need to modify two build.gradle
files in our project in order to make
Android Stem work, those are:
- Root's
build.gradle
- App's
build.gradle
- Android libraries
build.gradle
(Optional - If you want to define templates in your own android libraries)
To get a better idea of where you can find these files, take a look at this Android Studio screenshot below:
- The number 1 selection is to make sure that you've selected the "Project"
view in order to see the
build.gradle
files as shown on this image. - The number 2 selection represents your App's build.gradle file, and it should look similar for Android libraries, if you happen to have any in your project.
- The number 3 selection represents your Root's build.gradle file.
In your App's build.gradle
file you have to add the following line after the android's app plugin com.android.application
one:
id "com.likethesalad.stem" version "2.11.0"
Example:
// App's build.gradle file
plugins {
id "com.android.application"
id "com.likethesalad.stem" version "2.11.0"
}
android {
//...
}
If you have parts of your project split into multiple android libraries where you'd like to define templates, you can do so by applying a "producer" version of Stem into them like so:
// Android library's build.gradle file
plugins {
id "com.android.library"
id "com.likethesalad.stem-library" version "2.11.0"
}
android {
//...
}
Please bear in mind that the "producer" plugin doesn't resolve templates, it only provides them along with values needed to resolve those, so that the "consumer" (application) can use them later on when building the final app.
After your changes to the build.gradle files are done, you should see the above message in Android Studio that has a "Sync Now" button. You have to click on that button for the changes to take effect. After the sync is done, you'll be ready to go! you can start adding your string templates to your app's resources.
If you want to just run the gradle task that resolves
the templates without having to build your project, you can do so
by running: resolve[BUILD_VARIANT]Placeholders
depending
on your build configuration. For example, to run it for the debug variant,
you'll have to run: resolveDebugPlaceholders
, or if you have flavors
set up in your application, e.g. say you have 'demo' as a flavor defined,
then you can run resolveDemoDebugPlaceholders
to generate the strings
for the demo flavor on the debug variant and so on.
If this plugin is useful for you, and if it's within your possibilities, please consider making a one-off donation that will help keeping the development of new features and bug support. And if you can't make a donation right now, you could also support this plugin by sharing it with your dev friends and colleagues!
<string>"Thanks for your support, ${your_beautiful_name}!"</string>
MIT License
Copyright (c) 2019 LikeTheSalad.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.