In my previous post (Dynamic handling of strings/text for Mobile apps – Part 1), I discussed some issues with hard coding labels and text. This post deals with solutions to those problems and involves a method that provides more flexibility by updating text dynamically.
Please note: This is not to be confused with the localization of the app. But, this approach can be used for the localization purpose also.
This approach involves maintaining a strings file on the server instead of hardcoding it in the app.
Following are the steps to follow for the implementation of this approach:
On Server Side:
– Maintain strings file XML / JSON on the server. Its better to use JSON if the person who will be maintaining the file is aware of JSON structure and can validate json after any change. Otherwise in a normal scenario, we can keep it in XML format for ease of update and maintenance even by a non-technical person.
– Maintain a version number of this string file into config / settings XML. In most applications, we have one config file from the server which downloads app related configuration. If your application does not follow the config file structure, you can maintain the app version and send it as a query parameter, along with other API response. This would be troublesome to handle if your app does not handle response at a single place. Its always better to introduce a config XML for the app which can maintain config related data for e.g. latest version of this strings file.
On App Side:
App architecture needs to be implemented as mentioned below to incorporate update of strings file from the server.
– Maintain the local version of strings in localizable.strings (iOS) file / strings.XML (android). This local version is still required in case app fails to retrieve strings from the server which can be one of the following reasons: invalid XML format, file not found, time out.
– App will always pull config file when the application enters the foreground state. Then app will check version number of strings file with local version number ( local version can be cached in NSUserDefaults for iOS or in SharedPreference for android). If version number from server is not equal to that of local, app will go ahead and download latest strings file from server and write it to user defaults or to file. Also, update the local version number for the strings file to that of the latest version number from the config file once the download of strings from the server is successful. Now, it is mandatory to write and store this strings file response from the server. You cannot just store it in memory and read from there, the reason being, strings file can be huge in size and can contain a lot of strings which results in more parsing time in case of XML format, so we don’t want to download strings file from the server every time. That is the reason we maintained the version number for it in the config file. Downloading config file each time should not affect the performance of app as config file contains fewer data as compared to strings file.
Also, convert this XML response to json and store it in the dictionary to keep it in memory.
It is recommended to store in a dictionary in order to achieve faster access and avoid frequent read from file / user defaults where the response is stored for future access. Also, it will be one-time conversion from XML to JSON at the time of download. Later on, it can be accessed simply as a key-value pair.
Note: App should download strings file in the background and should not block user as you already have a local version of strings so labels will never be empty. The app will reflect the changes whenever the user revisits the screen.
– Now, we already discussed how to fetch the latest version of strings file from the server. But, how to utilize it for displaying on labels?
– Write a utility function which returns value related to key passed to the function.
This function will first look into the local dictionary for the value. If a value is null or empty, look for the value corresponding to the key from localizable.strings(iOS) or strings(android) file. In this way, it will always return some value and should never be empty. For easiness of debugging, if the value is still not found, don’t return an empty string from the function. Instead, return the key itself as a value. This will help to identify that the value for the corresponding key is missing in the strings file on the server.
– Now, what if the next time, app comes to the foreground and there is no update of strings file from the server? In this case, the local dictionary will be populated with the last cached response from the server stored in user defaults / file. If the last cached data is not found, local dictionary will be null and by default, the utility function will pick a value from the bundled strings file.
– In this way, we can update strings to display on a label dynamically from server. Whenever there is a change in strings file, its version needs to be updated in config file else the app will not download the new file.
Now, on the developing end you will argue that with this approach, you cannot set the text directly into xib (iOS) / layout (android), and need to do it programmatically to always fetch from utility function with its associated key. To solve this problem, you can always subclass the label and override set text method to pull the value from utility method. Now, use this subclassed custom label on your xib file and set the key of the strings file as a text to it.
Sample Strings file:
<?xml version="1.0" encoding="UTF-8"?>
<login_description_key>Enter your six digit pin to login</from>
– This approach just needs a one-time setup. Post-setup, it’s very easy to maintain without any dependency on the developer and does not require app updates.
– On the UI side, try to maintain labels and view, dynamic in size so as to properly display dynamic text length.
– The same approach can be used for localization also. Different language files can be maintained on the server and downloaded when there is a change in version. Also, the local dictionary can be populated with the appropriate cached response based on the current locale of the device.
Follow me on Twitter