Dynamically change bundled strings

How to Restring your app

Image by TopGuitar

Providing string resources as XML files is how we manage static texts of an application in Android. There’s a problem with this approach: every time we want to change a text we have to release a new version or wait for the next release time. That’s why in some projects we decide to implement a system which allows us to change this static bundled text dynamically and from server-side. But this is not that easy(or let’s say clean) in Android. Let’s see how we can do it.

Here I will explain a simple implementation very briefly in first section which as I said is not easy and clean. Then I will introduce my alternative way in second section. if you’re too lazy to read the article completely, just jump to the second section.


1. A simple implementation

For a simple implementation, we should at least do these steps:

  1. Implement a repository of new strings: here we should load strings from server-side( and cache them probably) and provide a nice interface to other components to search for new strings.
  2. We have to change the all cases where we use bundled string resources to display a text in Java/Kotlin code:

So instead of these lines:

1) String msg = Context.getString(R.string.message);
2) MyTextView.setText(R.string.title);

We should write something like this:

1) String msg = StringsRepository.getString(R.string.message);
2) MyTextView.setText(StringsRepository.getString(R.string.title));

So we should have(or inject) StringsRepository in kinda everywhere.

3. We have to change the strings used in XML layout files. This part is kinda tricky, because we can just use static resources in our XML files. So we have two options:

  • Instead of setting strings in XML files, set them in Java/Kotlin code one-by-one. So probably after inflating each layout we will have bunch of view.setText() lines.
View view = LayoutInflater.from(context).inflate(R.layout.mylayout, parent, null);
((TextView)view.findViewById(R.id.text1)).setText(...);
((TextView)view.findViewById(R.id.text2)).setText(...);
...
  • As an alternative, we can write a method which gets a root layout as an input and traverse the tree of views and tries to change their text. This method can be like a helper class which can be used everywhere, but still will need to have access to StringsRepository instance.

4. We can also create some custom views which extend the standard views and can handle getting the strings from repository internally. So as an example, instead of TextView we will have CustomTextView which when we call .setText(int strResId) it will try to get this string from StringsRepository instead of Context. CustomTextView needs to have access to StringsRepository and we need to use this new component everywhere instead of TextView.

public class CustomTextView extends AppCompatTextView {
private StringsRepository stringsRepository;
  @Override
public void setText(int strResId) {
setText(stringsRepository.getString(strResId));
}
}

I didn’t want to go to more details, but as you can see even this simple implementation is not that clean as we expect, and we need to apply a lot of changes. Here I also just assumed that we have only TextViews for simplicity, but in a real application it’s not the case. We will have more view to change(EditText, Toolbar, …) and each has more attributes(hint, title, …).


2. Use Restring library

As an alternative to the messy implementation we mentioned, we can use Restring library which makes overriding the static string resources really easy. Here is the steps to implement it:

1. Add dependency:

implementation 'com.ice.restring:restring:1.0.0'

2. Initialize:
We can do this in Application class.

Restring.init(context);

3. Inject into context:
if there’s a BaseActivity it can be added there, otherwise it should be added to all of the activities!

@Override
protected void attachBaseContext(Context newBase) {
super.attachBaseContext(Restring.wrapContext(newBase));
}

4. Load and set the new strings:

Whenever/however/wherever the new strings are loaded, we can set them into Restring like this:

Restring.setStrings(language, newStrings);

5. Done!

That’s it! now all the static strings will be overridden by new strings. For more configurations you can check Restring Github page.


3. Adding a new language support to your app

With Restring, for adding a new language to your application, we don’t have to release a new app version. Just send the string translations from server-side for the new language to app, and app will set them as strings of the new language and that’s it.

Let’s say we gonna add German language. Let’s assume we have an API which returns the list of string translations for all supported languages. So in server-side we add the new language. So we will have:

[
"en": [
"home_title": "Welcome!",
"button_cancel": "Cancel",
...
],
  "de": [
"home_title": "Willkommen!",
"button_cancel": "Stornieren",
...
]
]

Now that we have strings for German language in API response we can simply do this:

Map<String, Map<String, String>> langStrings = ... //parse the json
for(Map.Entry<String, Map<String, String>> lang: langStrings) {
Restring.setStrings(lang.getKey(), lang.getValue());
}

Now if we change our Android device language, Restring will start using the new language strings.

Note: Restring uses current locale strings. So if we call Locale.setDefault(…) to set a new locale, Restring will also uses the new locale to display the texts.


Conclusion

Changing the static bundled texts from server-side is a painful task. Restring is created to make this easier. I hope it can help and get better!

You can check Restring Github page for more details about configuration.

Like what you read? Give Hamid Gharehdaghi a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.