web-dev-qa-db-fra.com

Changer les paramètres régionaux ne fonctionne pas après la migration vers Androidx

J'ai un ancien projet qui prend en charge plusieurs langues. Je veux mettre à jour la bibliothèque de support et la plate-forme cible, Avant de migrer vers Androidx tout fonctionne bien mais maintenant changer de langue ne fonctionne pas!

J'utilise ce code pour modifier les paramètres régionaux par défaut de l'application

private static Context updateResources(Context context, String language)
{
    Locale locale = new Locale(language);
    Locale.setDefault(locale);

    Configuration configuration = context.getResources().getConfiguration();
    configuration.setLocale(locale);

    return context.createConfigurationContext(configuration);
}

Et appelez cette méthode sur chaque activité en remplaçant attachBaseContext comme ceci:

@Override
protected void attachBaseContext(Context newBase)
{
    SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
    String language = preferences.getString(SELECTED_LANGUAGE, "fa");
    super.attachBaseContext(updateResources(newBase, language));
}

J'essaie une autre méthode pour obtenir une chaîne et j'ai remarqué que ‍‍‍‍getActivity().getBaseContext().getString fonctionne et getActivity().getString ne fonctionne pas. Même le code suivant ne fonctionne pas et affiche toujours app_name Vlaue dans la ressource par défaut string.xml.

<TextView
    Android:id="@+id/textView"
    Android:layout_width="wrap_content"
    Android:layout_height="wrap_content"
    Android:text="@string/app_name"/>

Je partage un exemple de code dans https://github.com/Freydoonk/LanguageTest

getActivity()..getResources().getIdentifier ne fonctionne pas non plus et renvoie toujours 0!

37
Fred

Enfin, je trouve le problème dans mon application. Lors de la migration du projet vers Androidx les dépendances de mon projet ont changé comme ceci:

dependencies {
    implementation fileTree(include: ['*.jar'], dir: 'libs')
    implementation 'androidx.appcompat:appcompat:1.1.0-alpha03'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    implementation 'com.google.Android.material:material:1.1.0-alpha04'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0-alpha02'
} 

Comme on le voit, la version de androidx.appcompat:appcompat est 1.1.0-alpha03 lorsque je l'ai changé pour la dernière version stable, 1.0.2, mon problème est résolu et le changement de langue fonctionne correctement.

Je trouve la dernière version stable de la bibliothèque appcompat dans Maven Repository . Je change également d'autres bibliothèques pour la dernière version stable.

Maintenant, ma section des dépendances d'application est comme ci-dessous:

dependencies {
    implementation fileTree(include: ['*.jar'], dir: 'libs')
    implementation 'androidx.appcompat:appcompat:1.0.2'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    implementation 'com.google.Android.material:material:1.0.0'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
}
14
Fred

Fondamentalement, ce qui se passe en arrière-plan, c'est que pendant que vous avez correctement défini la configuration dans attachBaseContext, le AppCompatDelegateImpl va alors et remplace la configuration par une configuration complètement nouvelle sans paramètres régionaux:

 final Configuration conf = new Configuration();
 conf.uiMode = newNightMode | (conf.uiMode & ~Configuration.UI_MODE_NIGHT_MASK);

 try {
     ...
     ((Android.view.ContextThemeWrapper) mHost).applyOverrideConfiguration(conf);
     handled = true;
 } catch (IllegalStateException e) {
     ...
 }

Dans un commit non publié de Chris Banes cela a été corrigé: la nouvelle configuration est une copie complète de la configuration du contexte de base.

final Configuration conf = new Configuration(baseConfiguration);
conf.uiMode = newNightMode | (conf.uiMode & ~Configuration.UI_MODE_NIGHT_MASK);
try {
    ...
    ((Android.view.ContextThemeWrapper) mHost).applyOverrideConfiguration(conf);
    handled = true;
} catch (IllegalStateException e) {
    ...
}

Jusqu'à ce que cela soit publié, il est possible de faire exactement la même chose manuellement. Pour continuer à utiliser la version 1.1.0, ajoutez ceci sous votre attachBaseContext:

Solution Kotlin

override fun applyOverrideConfiguration(overrideConfiguration: Configuration?) {
    if (overrideConfiguration != null) {
        val uiMode = overrideConfiguration.uiMode
        overrideConfiguration.setTo(baseContext.resources.configuration)
        overrideConfiguration.uiMode = uiMode
    }
    super.applyOverrideConfiguration(overrideConfiguration)
}

Solution Java

@Override
public void applyOverrideConfiguration(Configuration overrideConfiguration) {
    if (overrideConfiguration != null) {
        int uiMode = overrideConfiguration.uiMode;
        overrideConfiguration.setTo(getBaseContext().getResources().getConfiguration());
        overrideConfiguration.uiMode = uiMode;
    }
    super.applyOverrideConfiguration(overrideConfiguration);
}

Ce code fait exactement la même chose que Configuration(baseConfiguration) fait sous le capot, mais parce que nous le faisons après le AppCompatDelegate a déjà défini le bon uiMode, nous devons nous assurer de prendre le uiMode remplacé après que nous l'avons corrigé afin de ne pas perdre le réglage du mode sombre/clair.

Veuillez noter que cela ne fonctionne que si vous ne spécifiez pas configChanges="uiMode" Dans votre manifeste . Si vous le faites, il y a encore un autre bug: à l'intérieur de onConfigurationChanged, le newConfig.uiMode Ne sera pas défini par AppCompatDelegateImpl's onConfigurationChanged. Cela peut également être résolu si vous copiez tout le code que AppCompatDelegateImpl utilise pour calculer le mode de nuit actuel dans votre code d'activité de base, puis le remplacez avant l'appel super.onConfigurationChanged. À Kotlin, cela ressemblerait à ceci:

private var activityHandlesUiMode = false
private var activityHandlesUiModeChecked = false

private val isActivityManifestHandlingUiMode: Boolean
    get() {
        if (!activityHandlesUiModeChecked) {
            val pm = packageManager ?: return false
            activityHandlesUiMode = try {
                val info = pm.getActivityInfo(ComponentName(this, javaClass), 0)
                info.configChanges and ActivityInfo.CONFIG_UI_MODE != 0
            } catch (e: PackageManager.NameNotFoundException) {
                false
            }
        }
        activityHandlesUiModeChecked = true
        return activityHandlesUiMode
    }

override fun onConfigurationChanged(newConfig: Configuration) {
    if (isActivityManifestHandlingUiMode) {
        val nightMode = if (delegate.localNightMode != AppCompatDelegate.MODE_NIGHT_UNSPECIFIED) 
            delegate.localNightMode
        else
            AppCompatDelegate.getDefaultNightMode()
        val configNightMode = when (nightMode) {
            AppCompatDelegate.MODE_NIGHT_YES -> Configuration.UI_MODE_NIGHT_YES
            AppCompatDelegate.MODE_NIGHT_NO -> Configuration.UI_MODE_NIGHT_NO
            else -> applicationContext.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
        }
        newConfig.uiMode = configNightMode or (newConfig.uiMode and Configuration.UI_MODE_NIGHT_MASK.inv())
    }
    super.onConfigurationChanged(newConfig)
}

MISE À JOUR 16 décembre:

La version récente de AppCompatDelegateImpl.Java a reçu de nombreux autres changements depuis la rédaction initiale de cet article. Le correctif de Chris Banes que je mentionne ci-dessus n'était apparemment pas suffisant pour se débarrasser complètement de tous les bogues liés à configChanges (vous pouvez voir ma solution ci-dessus pour configChanges="uiMode", Et un utilisateur l'a souligné dans le commente ici que dans son cas, la valeur orientation ne changeait pas dans onConfigurationChanged). La dernière implémentation s'appuie à la place sur une nouvelle méthode appelée generateConfigDelta pour créer l'instance de configuration correcte, de sorte que toute autre manière de gérer le mode nuit est en fait obsolète.

Personnellement, j'utilise ma solution depuis des mois sans aucun problème ni aucun utilisateur se plaignant, mais vous devez toujours être conscient que AppCompatDelegateImpl est toujours en cours de développement, veuillez donc utiliser ma solution à votre discrétion. Assurez-vous également de consulter les autres réponses qui suggèrent une rétrogradation vers les versions de bibliothèque antérieures, qui pourrait seraient plus appropriées dans votre situation.

67
0101100101

Il y a un problème dans les nouvelles bibliothèques de compatibilité des applications liées au mode nuit qui entraîne la substitution de la configuration sur Android 21 à 25. Cela peut être résolu en appliquant votre configuration lorsque cette fonction publique est appelée:

public void applyOverrideConfiguration (Configuration overrideConfiguration

Pour moi, cette petite astuce a fonctionné en copiant les paramètres de la configuration remplacée dans ma configuration, mais vous pouvez faire ce que vous voulez. Il est préférable de réappliquer votre logique de langage à la nouvelle configuration pour minimiser les erreurs

@Override
public void applyOverrideConfiguration(Configuration overrideConfiguration) {
    if (Build.VERSION.SDK_INT >= 21&& Build.VERSION.SDK_INT <= 25) {
        //Use you logic to update overrideConfiguration locale
        Locale locale = getLocale()//your own implementation here;
        overrideConfiguration.setLocale(locale);
    }
    super.applyOverrideConfiguration(overrideConfiguration);
}
8
Jack Lebbos

Enfin, j'ai obtenu une solution pour localiser, dans mon cas, le problème était en fait avec bundle apk car il a divisé les fichiers de localisation. Dans bundle apk par défaut, toutes les divisions seront générées. mais dans le bloc Android de votre build.gradle fichier, vous pouvez déclarer les divisions qui seront générées.

bundle {
            language {
                // Specifies that the app bundle should not support
                // configuration APKs for language resources. These
                // resources are instead packaged with each base and
                // dynamic feature APK.
                enableSplit = false
            }
        }

Après avoir ajouté ce code à bloc Android de build.gradle fichier mon problème est résolu.

2
Mansukh Ahir

Maintenant, il existe une version plus récente qui fonctionne également:

implementation 'androidx.appcompat:appcompat:1.1.0-alpha04'

Comme @Fred l'a mentionné appcompat:1.1.0-alpha03 a un problème bien qu'il ne soit pas mentionné sur leur journal des versions

1
Choletski

Eu le même bug sur androidx.appcompat:appcompat:1.1.0. Passé à androidx.appcompat:appcompat:1.1.0-rc01 et maintenant les langages changent le Android 5-6.

0
Spinner