#android #locale
#Android #локаль
Вопрос:
Мне нужно получить доступ к локализованным строкам приложения, когда я не использую Context
связанные классы (Activites, Fragments, Services, Views и т.д.) В ViewModel
или специально созданных классах, где я сохраняю всю свою бизнес-логику. Например, отправка String
в UI
с помощью 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)
}
Теперь в Activity
я наблюдаю за LiveData
изменением, и я показываю всплывающее сообщение.
Это работает нормально, но проблема возникает, если пользователь меняет язык приложения. Макеты обновлены, и я вижу, что используется язык новой локали, но MyApp.instance.getString(R.string....)
функция по-прежнему использует старую локаль. Если я принудительно завершу работу приложения и перезапущу его, оно будет работать, потому что приложение attachBaseContext
вызывается снова и применяется новая локаль. Однако я не хочу принудительно завершать работу приложения, мне нужно решение для обновления конфигурации приложения.
Служебная функция для создания или обновления 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
}
При запуске приложения примените новую локаль
class MyApp: Application {
override fun attachBaseContext(base: Context) {
super.attachBaseContext(LocaleHelper.instance.buildLocalizedContext(base))
}
}
BaseActivity
Класс, который используется другими активностями:
class BaseActivity: AppCompatActivity {
override fun attachBaseContext(newBase: Context) {
super.attachBaseContext(LocaleHelper.instance.buildLocalizedContext(newBase))
}
}
Это то, что мне удалось сделать до сих пор, однако это тоже не работает:
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)
}
}
Приведенный выше фрагмент кода не работает, он не изменяет локаль приложения, только если я принудительно перезапущу приложение.
Комментарии:
1. «Это работает нормально, но проблема возникает, если пользователь меняет язык приложения» — у приложений на самом деле нет языков. Ваш
setLocale()
подход всегда был чем-то вроде взлома с неполной поддержкой в Android. При этом, есть ли какое-либо решение, которое предполагает, что вы обращаетесь кApplication
не напрямую, а через некоторыеContextWrapper
, где вы можете изменить язык, используемыйContextWrapper
?2. @Commons — Одним из решений было бы отправить идентификаторы строковых ресурсов в
Live Data
и получить строки изActivity
илиFragment
, но это нарушает принцип MVVM, перенося бизнес-логику в пользовательский интерфейс. В приведенном выше примере вы можете видеть, что бизнес-логика вычисления последнего входа в систему и построения строки выполнена вViewModel
. Проблема возникает при вызовеMyApp.instance.getString(R.string....)
функции внутриViewModel
, потому чтоLocale
внутриMyApp.instance.configuration.locale
не обновляется с новой локализацией, только если я перезапускаю приложение.3. Я хочу сказать, что вместо вызова
MyApp.instance.getString()
, возможно, вы можете использовать что-то другое, чемMyApp.instance
(например,ContextWrapper
aroundMyApp.instance
), которое по-прежнему является глобальным по объему, но может быть заменено во время выполнения. Или, возможно, строка создается путем вызова функции, в которой вызывающий объект предоставляетActivity
.4. @CommonsWare — спасибо за ваши быстрые ответы. Если бы вы могли привести мне пример, как я мог бы использовать или
ContextWrapper
обойтиMyApp.instance
, или как я мог бы получить доступ кActivity
изViewModel
, не нарушаяMVVM
принципа.5. Что касается последнего,
fun buildLastLoginToastMessage(ctxt: Context)
в приведенном выше фрагменте кода, где эта функция вызывается вашим действием или фрагментом и передается в действии, и вы используетеctxt
для своегоgetString()
вызова. Что касается первого, я просто предлагаю путь для вашего собственного исследования. Лично я бы никогда не написал приложение, которое использовалоsetLocale()
.
Ответ №1:
Как вы уже испытали, язык и несколько других конфигураций применяются только к контекстам просмотра, а не к контексту приложения. Похоже, вы находитесь в странной ситуации. Возможно, вам потребуется пересмотреть свою архитектуру. Обычно вы хотите получить строку внутри вашего представления и передать ее в вашу ViewModel для обработки. Однако, если вы хотите, чтобы это работало так, как есть, возможно, вы можете сохранить слабую ссылку на представление внутри вашего объекта приложения и использовать это для получения локализованных строк. Вы также можете сохранить жесткую ссылку на представление, но тогда вам нужно будет обязательно очистить ее, чтобы избежать утечки памяти. Лучшим решением было бы пересмотреть вашу архитектуру, но это тоже подходящее решение.
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
(application as App).activity = WeakReference<Activity>(this)
}
}
class App : Application {
var activity : WeakReference<Activity>? = null
}