Zbarcea Christian
Zbarcea Christian

Reputation: 9548

Android application context configuration doesn't gets updated with the new Locale

I need to access application's localized strings when I'm not using Context related classes (Activites, Fragments, Services, Views, etc) in ViewModel or custom built classes where I keep all my business logic. For example, posting a String to the UI with the help of a LiveData:

toastMessageLiveData.value = buildLastLoginToastMessage()

fun buildLastLoginToastMessage() {
   val lastLoginDiff = (System.currentMillis() - getLastLogin()) / DateUtils.HOURS_IN_MILLIS

   return MyApp.instance.getString(R.string.last_login_txt, lastLoginDiff)
}

Now in the Activity I'm observing the LiveData's change and I'm showing a Toast message.

This works fine, but the problem comes if the user changes the application's language. The layouts are updated and I can see that the new locale's language is used, but the MyApp.instance.getString(R.string....) function is still using the old locale. If I force-kill the app and I restart it, it's working because the Application's attachBaseContext is called again and the new Locale is applied. However, I don't want to force kill the app, I need a solution to update the application's configuration.

Utility function to create or update the Context

fun buildLocalizedContext(context: Context): Context {
   // From preferences, load the saved Language and Country codes
   val language = getSavedLanguage()
   val country = getSavedCountry()

   // Create the new locale
   val locale = Locale(language, country)
   Locale.setDefault(locale)

   val resources = context.resources
   val configuration = Configuration(resources.configuration)

   // Create a new configration or update the existing one if the API is less than 17
   if (Build.VERSION.SDK_INT >= 17) {
      configuration.setLocale(locale)
      return context.createConfigurationContext(configuration)
   } else {
      configuration.locale = locale
      resources.updateConfiguration(configuration, resources.getDisplayMetrics())
   }

   return context
}

When the app starts, apply the new Locale

class MyApp: Application {
    override fun attachBaseContext(base: Context) {
        super.attachBaseContext(LocaleHelper.instance.buildLocalizedContext(base))
    }
}

The BaseActivity class which is being used by the other activites:

class BaseActivity: AppCompatActivity {
    override fun attachBaseContext(newBase: Context) {
        super.attachBaseContext(LocaleHelper.instance.buildLocalizedContext(newBase))
    }
}

This is what I've managed to make so far, however this doesn't work either:

fun changeLocale(locale: Locale, app: Application) {
    Locale.setDefault(locale)

    val resources = app.resources
    val configuration = resources.configuration

    if (Build.VERSION.SDK_INT >= 17) {
        configuration.setLocale(locale)
    }
    else {
        configuration.locale = locale
        resources.updateConfiguration(configuration, resources.displayMetrics)
    }
}

The above code snippet doesn't work, it doesn't changes the Application's Locale, only if I force restart the application.

Upvotes: 18

Views: 4333

Answers (1)

Harry
Harry

Reputation: 386

As you have experienced, language and several other configurations get applied only to view contexts, not application context. You seem to be in a strange situation. You may need to revisit your architecture. Generally you want to get the string inside your view and pass it to your ViewModel for processing. However if you want to make it work the way it is, maybe you can store a WeakReference to a View inside your application object and use that to get localized strings. You can also hold hard reference to the view, but then you would have to be sure to clear it in order to avoid memory leak. The best solution would be revising your architecture, but this is a ok solution as well.

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        (application as App).activity = WeakReference<Activity>(this)
        }
}

class App : Application {
var activity : WeakReference<Activity>? = null
}

Upvotes: 2

Related Questions