web-dev-qa-db-fra.com

Pourquoi Fragment ne conserve-t-il pas cet état lorsque l'écran est pivoté?

J'ai eu du mal à faire en sorte que certaines sous-classes DialogPreference personnalisées à l'intérieur d'un PreferenceFragment restent visibles lors de la rotation de l'écran. Je n'éprouve pas ce problème lors de l'utilisation de PreferenceActivity. Je ne sais donc pas s'il s'agit d'un bogue d'Android ou d'un problème de code, mais j'aimerais que quelqu'un confirme s'il a la même expérience.

Pour tester cela, créez d'abord un écran de préférences contenant au moins une DialogPreference (peu importe la sous-classe). Puis affichez-le dans une PreferenceActivity. Lorsque vous exécutez votre application, appuyez sur DialogPreference pour afficher la boîte de dialogue. Ensuite, faites pivoter l'écran pour que l'orientation change. Le dialogue reste-t-il visible?

Ensuite, essayez la même chose, mais avec un PreferenceFragment pour afficher vos préférences au lieu d'un PreferenceActivity. Encore une fois, la boîte de dialogue reste-t-elle visible lorsque vous faites pivoter l'écran?

Jusqu'ici, j'ai constaté que la boîte de dialogue resterait visible si vous utilisiez un objet PreferenceActivity, mais pas si vous utilisiez un PreferenceFragment. En regardant le code source pour DialogPreference , il semble que le comportement correct est que la boîte de dialogue reste visible, car isDialogShowing est l’information d’état qui est enregistrée lorsque onSaveInstanceState() est appelé à la réorientation de l’écran. Par conséquent, je pense qu'un bogue pourrait empêcher le PreferenceFragment (et tout ce qu'il contient) de restaurer ces informations d'état.

S'il s'agit d'un bogue Android, les implications sont considérables, car toute personne utilisant PreferenceFragment ne peut pas enregistrer et restaurer les informations d'état.

Quelqu'un peut-il s'il vous plaît confirmer? Si ce n'est pas un bug, que se passe-t-il?

27

Enfin trouvé une solution à ce problème. Il s'avère que ce n'est pas un bug, mais un problème/oubli dans la documentation du développeur Android.

Vous voyez, je suivais le tutoriel PreferenceFragment ici . Cet article vous recommande de procéder comme suit pour instancier votre PreferenceFragment dans une activité:

public class SettingsActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Display the fragment as the main content.
        getFragmentManager().beginTransaction()
                .replace(Android.R.id.content, new SettingsFragment())
                .commit();
    }
} 

Le problème avec ceci est que lorsque vous modifiez l'orientation de l'écran (ou toute autre action qui détruit et recrée l'activité), votre PreferenceFragment est créé deux fois , ce qui lui fait perdre son état. 

La création de first se fera via l'appel de l'activité à super.onCreate() (ci-dessus), qui appellera la méthode onActivityCreated() pour votre méthode PreferenceFragment () et la méthode onRestoreInstanceState() pour chacune des préférences qu'elle contient. Ceux-ci vont restaurer avec succès l'état de tout.

Mais une fois que l'appel à super.onCreate() est revenu, vous pouvez voir que la méthode onCreate() créera ensuite le PreferenceFragment a second time. Comme il est créé inutilement à nouveau (et cette fois, sans information d'état!), Tout l'état qui a été restauré avec succès sera complètement ignoré/perdu. Cela explique pourquoi une référence Dialog pouvant apparaître au moment où l'activité est détruite ne sera plus visible une fois l'activité recréée.

Alors, quelle est la solution? Bien, ajoutez simplement un petit chèque pour déterminer si le PreferenceFragment a déjà été créé, comme ceci:

public class SettingsActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        Fragment existingFragment = getFragmentManager().findFragmentById(Android.R.id.content);
        if (existingFragment == null || !existingFragment.getClass().equals(SettingsFragment.class))
        {
            // Display the fragment as the main content.
            getFragmentManager().beginTransaction()
                .replace(Android.R.id.content, new SettingsFragment())
                .commit();
        }
    }
}

Ou bien, vous pouvez simplement vérifier si onCreate() est censé restaurer l'état ou non, comme suit:

public class SettingsActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        if (savedInstanceState == null)
        {
            // Display the fragment as the main content.
            getFragmentManager().beginTransaction()
                .replace(Android.R.id.content, new SettingsFragment())
                .commit();
        }
    }
}

Je pense donc que la leçon à retenir ici est que onCreate() a un double rôle: il peut configurer une activité pour la première fois ou restaurer à partir d'un état antérieur.

La réponse ici m'a amené à réaliser cette solution.

49

J'ai effectivement eu ce problème moi-même. Il y a un bogue dans lequel la variable DialogFragment ne restaure pas l'état car elle est nulle ou du moins cela m'est arrivé.

En utilisant plusieurs sources, j'ai finalement réussi à trouver une solution. Demandez à votre dialogue d'étendre cette BaseDialogFragment:

import Android.app.Dialog;
import Android.os.Bundle;
import Android.util.Log;
import Android.view.LayoutInflater;
import Android.view.View;
import Android.view.ViewGroup;
import Android.support.v4.app.DialogFragment;

import com.actionbarsherlock.app.SherlockDialogFragment;

public class BaseDialogFragment extends DialogFragment {

    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        if (savedInstanceState == null || savedInstanceState.isEmpty())
            savedInstanceState = WorkaroundSavedState.savedInstanceState;

        setRetainInstance(true);
        Log.d("TAG", "saved instance state oncreate: "
                + WorkaroundSavedState.savedInstanceState);
        super.onCreate(savedInstanceState);
    }

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState)
    {
        if (savedInstanceState == null || savedInstanceState.isEmpty())
            savedInstanceState = WorkaroundSavedState.savedInstanceState;
        Log.d("TAG", "saved instance state oncretaedialog: "
                + WorkaroundSavedState.savedInstanceState);

        return super.onCreateDialog(savedInstanceState);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        if (savedInstanceState == null || savedInstanceState.isEmpty())
            savedInstanceState = WorkaroundSavedState.savedInstanceState;

        Log.d("TAG", "saved instance state oncretaeview: "
                + WorkaroundSavedState.savedInstanceState);

        return super.onCreateView(inflater, container, savedInstanceState);
    }

    @Override
    public void onDestroyView() // necessary for restoring the dialog
    {
        if (getDialog() != null && getRetainInstance())
            getDialog().setOnDismissListener(null);

        super.onDestroyView();
    }

    @Override
    public void onSaveInstanceState(Bundle outState)
    {
        // ...

        super.onSaveInstanceState(outState);
        WorkaroundSavedState.savedInstanceState = outState;
        Log.d("TAG", "saved instance state onsaveins: "
                + WorkaroundSavedState.savedInstanceState);

    }

    @Override
    public void onDestroy()
    {
        WorkaroundSavedState.savedInstanceState = null;
        super.onDestroy();
    }

    /**
     * Static class that stores the state of the task across orientation
     * changes. There is a bug in the compatibility library, at least as of the
     * 4th revision, that causes the save state to be null in the dialog's
     * onRestoreInstanceState.
     */
    public static final class WorkaroundSavedState {
        public static Bundle savedInstanceState;
    }
}

Notez que dans les sous-classes dont les méthodes ont un paramètre savedInstanceState, vous devrez peut-être appeler super avec WorkaroundSavedState.savedInstanceState. Et lorsque vous restaurez l’état (c’est-à-dire dans onCreate(), ignorez simplement savedInstanceState et utilisez plutôt WorkaroundSavedState.savedInstanceState. Le titulaire statique n’est pas la solution la plus propre, mais il fonctionne. Assurez-vous de le définir sur null dans votre onDestroy().

Dans tous les cas, ma DialogFragment ne disparaît pas lorsque je fais pivoter l'écran (et c'est sans configChanges). Faites-moi savoir si ce code traite de votre problème et si ce n'est pas le cas, je jetterai un œil à ce qui se passe. Notez également que je n'ai pas testé cela dans PreferenceFragment, mais plutôt dans d'autres Fragments de la classe de compatibilité ou de ActionBarSherlock.

0
Oleg Vaskevich