I use a custom settings activity for my preferences. One of them is a ListPreference
for choosing app language. So far I could implement the locale preferences, but to apply changes, I restart app. The problem is, it does not work for all Android Versions. Here is what I did:
import android.content.Context
import android.content.Intent
import android.graphics.Color
import android.os.Bundle
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.widget.Toolbar
import androidx.fragment.app.FragmentActivity
import androidx.preference.ListPreference
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
class SettingsActivity : FragmentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_settings)
val toolbar: Toolbar = findViewById(R.id.toolbar_settings)
toolbar.apply {
setNavigationIcon(R.drawable.ic_arrow)
setNavigationOnClickListener { finish() }
title = getString(R.string.settings)
setTitleTextColor(Color.WHITE)
}
supportFragmentManager.beginTransaction().replace(R.id.content, SettingsFragment())
.commit()
}
class SettingsFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
addPreferencesFromResource(R.xml.preferences)
val lang: ListPreference = findPreference("Language")!!
val language = when (LanguageSettings().getLang()) {
"en" -> getString(R.string.en_lang)
"de" -> getString(R.string.de_lang)
else -> getString(R.string.ru_lang)
}
lang.summary = "${getString(R.string.current_language)} $language"
val temp = lang.value
lang.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue ->
val dialogBuilder =
activity?.let { AlertDialog.Builder(it, R.style.AlertDialogLight) }
dialogBuilder!!.setMessage(getString(R.string.restart_txt))
.setPositiveButton(getString(R.string.restart)) { _, _ ->
activity?.let { LanguageSettings().setLocale(it, newValue.toString()) }
restartApp()
}
.setNegativeButton(getString(R.string.cancel)) { dialog, _ ->
lang.value = temp
dialog.cancel()
}
dialogBuilder.create().apply {
show()
}
true
}
}
private fun restartApp() {
val intent = activity!!.packageManager
.getLaunchIntentForPackage(activity!!.packageName)
intent!!.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
activity!!.finish()
startActivity(intent)
}
}
override fun attachBaseContext(newBase: Context) {
super.attachBaseContext(LanguageSettings().onAttach(newBase))
}
}
Below Lollipop, after restart the changes are applied, which means the app language is changed after restart. Also it works for android 10. But this does not work for Lollipop and Marshmallow (so far tested), but what only works, the language is changed only for SettingsActivity, other activities stay unchanged. May be there are also other versions between Marshmallow and Android X, for which it also works.
But nevertheless, I want to get it worked for all versions, my app supports SDK from 16 up to latest one.
My LanguageSettings.kt
class:
import android.content.Context
import android.content.res.Configuration
import android.os.Build
import android.util.Log
import androidx.preference.PreferenceManager
import java.util.*
@Suppress("DEPRECATION")
class LanguageSettings {
fun onAttach(context: Context): Context {
return setLocale(context, getPersistedData(context, getLang()!!)!!)
}
fun onAttach(context: Context, defaultLanguage: String): Context {
return setLocale(context, getPersistedData(context, defaultLanguage)!!)
}
fun getLang(): String? {
return when (Locale.getDefault().displayLanguage) {
"English" -> "en"
"Deutsch" -> "de"
"русский" -> "ru"
else -> "en"
}
}
fun setLocale(context: Context, language: String): Context {
persist(context, language)
val configuration: Configuration
val resources = context.resources
val locale =
Locale(language)
Locale.setDefault(locale)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
configuration = context.resources.configuration
configuration.setLocale(locale)
configuration.setLayoutDirection(locale)
return context.createConfigurationContext(configuration)
}
configuration = resources.configuration
configuration.locale = locale
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1)
configuration.setLayoutDirection(locale)
resources.updateConfiguration(configuration, resources.displayMetrics)
return context
}
private fun getPersistedData(context: Context, defaultLanguage: String): String? {
return PreferenceManager
.getDefaultSharedPreferences(context)
.getString("Language", defaultLanguage)
}
private fun persist(context: Context, language: String) {
PreferenceManager
.getDefaultSharedPreferences(context)
.edit()
.putString("Language", language)
.apply()
}
}
The problem: this works for Android 4.2-4.4 and above 7, but not for Android 5.0/5.1/6 (and may be for some versions also does not work, did not test for all versions so far, only for above mentioned versions).
Ans this my MyApp.kt
class that extends Application
is assigned in Manifest file (android:name=".MyApp"
):
class MyApp : Application() {
override fun attachBaseContext(base: Context) {
super.attachBaseContext(LanguageSettings().onAttach(base, "en"))
}
}
I found a solution after testing a lot of different cases:
private fun setLocale(context: Context, language: String): Context {
val configuration: Configuration
val resources = context.resources
val locale = when (language) {
"de" -> Locale("de", "DE")
"ru" -> Locale("ru", "RU")
"en" -> Locale("en", "US")
else -> Locale("en", "US")
}
Locale.setDefault(locale)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
configuration = context.resources.configuration
configuration.setLocale(locale)
configuration.setLayoutDirection(locale)
return context.createConfigurationContext(configuration)
}
configuration = resources.configuration
configuration.locale = locale
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1)
configuration.setLayoutDirection(locale)
resources.updateConfiguration(configuration, resources.displayMetrics)
return context
}
This works for me properly in Kotlin.