Android (Kotlin) : Locale Helper

Android (Kotlin) : Locale Helper

How to implement app-level localization in Android OS.

ยท

3 min read

If get to know something new by reading my articles, don't forget to endorse me on LinkedIn

In this article, we are going to talk about how to change the language programmatically in Android OS.

Android by default uses the Localeof the device to select the appropriate language-dependent resources. And most of the time this behavior is enough for common applications.

However, there are cases where you would want to change the language of your app and the UI of the app. As a result, LocaleHelperKt has emerged.

The appropriate way of changing the locale of the application

There are some difficulties that you have to overcome to change the language programmatically.

  • Our application will not remember your language change after it is closed or recreated during the configuration change.

  • We should update the currently visible UI properly according to the selected language.

Coding part

Let's create a new interface called LocaleHelperKt which contains 3 public APIs.

interface LocaleHelperKt {
    fun onAttach(defaultLanguage: String? = null): Context
    fun getCurrentLocale(): String
    fun setCurrentLocale(language: String): Context
}
  • onAttach("bn") to be called from the *Activity class.

  • getCurrentLocale() returned the current locale from the cache (SharedPref).

  • setCurrentLocale("bn")to set a new locale for this application.

Now, let's create an abstract class called BaseLocaleHelper which wraps some internal implementation of SET/GET of the current locale to/from SharedPref and update locale configuration and return the updated Context.

abstract class BaseLocaleHelper(internal val context: Context) :
    LocaleHelperKt {
    companion object {
        private const val SELECTED_LANGUAGE = "LocaleHelperKt_SelectedLanguage"
    }

    internal fun getPersistedLocale(defaultLocale: String): String {
        return cacheStorage.getString(SELECTED_LANGUAGE, defaultLocale) ?: defaultLocale
    }

    private val cacheStorage: SharedPreferences by lazy {
        PreferenceManager.getDefaultSharedPreferences(
            context
        )
    }

    private fun updateResources(context: Context, language: String): Context {
        val locale = Locale(language)
        Locale.setDefault(locale)
        val configuration = context.resources.configuration
        configuration.setLocale(locale)
        return context.createConfigurationContext(configuration)
    }

    private fun baseSetLocale(context: Context, newLocale: Locale): Context {
        var tmpContext = context
        val res = tmpContext.resources
        val configuration = res.configuration
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            configuration.setLocale(newLocale)
            val localeList = LocaleList(newLocale)
            LocaleList.setDefault(localeList)
            configuration.setLocales(localeList)
            tmpContext = tmpContext.createConfigurationContext(configuration)
        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
            configuration.setLocale(newLocale)
            res.updateConfiguration(configuration, res.displayMetrics)
        } else {
            configuration.locale = newLocale
            res.updateConfiguration(configuration, res.displayMetrics)
        }
        return tmpContext
    }

    internal fun setLocale(context: Context, newLocale: String): Context {
        cacheStorage.edit().apply {
            putString(SELECTED_LANGUAGE, newLocale)
            apply()
        }
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            return updateResources(context, newLocale)
        }
        val locale = Locale(newLocale)
        return baseSetLocale(context, locale)
    }
}
  • getPersistedLocale("bn") to cached locale from SharedPref.

  • updateResources(context, "bn") update locale configuration and returns updated Context.

  • baseSetLocale(context, "bn") to set new Locale according to API level and returns updated Context.

  • setLocale(context, "bn") to cache new Locale to SharedPref and update the Locale Configuration and return the new Context.

Add a new class called DefaultLocaleHelper to provide an implementation of LocaleHelperKt.

class DefaultLocaleHelper private constructor(context: Context) : BaseLocaleHelper(context) {
    companion object {
        @Volatile
        private var instance: LocaleHelperKt? = null
        private var LOCK: Any = Any()

        fun getInstance(context: Context): LocaleHelperKt {
            synchronized(LOCK) {
                if (instance == null) instance = DefaultLocaleHelper(context)
                return instance!!
            }
        }
    }

    override fun setCurrentLocale(language: String): Context {
        return setLocale(context, language)
    }

    override fun onAttach(defaultLanguage: String?): Context {
        val lang = getPersistedData(defaultLanguage ?: Locale.getDefault().language)
        return setLocale(context, lang)
    }

    override fun getCurrentLocale(): String =
        getPersistedLocale(Locale.getDefault().language)
}
  • getInstance(context) provides an Instance of LocaleHelperKt and the API implements Synchronization.

  • instance object marked as Volatile.

Override our *Activity's attachBaseContext() API.

class BaseActivity : ComponentActivity(){
    override fun attachBaseContext(newBase: Context?) {
super.attachBaseContext(DefaultLocaleHelper.getInstance(newBase!!).onAttach())
    }
}

Remember:

  • To update Texts Locale to the UI, we can just update the text or any other language-dependent resources one by one.

  • Or we can we call activity.recreate() to restart the currently loaded activity. Then the activity will reload the resources with the correct locale. However, if you select this option your users will notice the effect because it will close your application and you get a black screen for a very small amount of time and then recreate your activity again in meanwhile.

Full Code

Gist (https://gist.github.com/rommansabbir/aa254d0aa17770a4f10d6025952cc0e9)

That's it for today. Happy Coding...

ย