# Android (Kotlin) : Locale Helper

> If get to know something new by reading my articles, don't forget to endorse me on [LinkedIn](https://www.linkedin.com/in/rommansabbir/)

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

`Android` by default uses the `Locale`of 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.

```kotlin
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`.

```kotlin
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`.

```kotlin
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.

```kotlin
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](https://gist.github.com/rommansabbir/aa254d0aa17770a4f10d6025952cc0e9))

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