Fragment android - Comment enregistrer les états des vues dans un fragment lorsqu'un autre fragment est poussé dessus

137

Dans Android, un fragment (par exemple FragA) est ajouté à la backstack et un autre fragment (par exemple FragB) arrive en haut. Maintenant, en frappant en arrière FragAvient au sommet et le onCreateView()est appelé. Maintenant, j'avais FragAdans un état particulier avant d' FragBêtre poussé dessus.

Ma question est de savoir comment puis-je restaurer FragAson état antérieur? Existe-t-il un moyen de sauvegarder l'état (comme par exemple dans un bundle) et si oui, quelle méthode dois-je remplacer?

pankajagarwal
la source

Réponses:

98

Dans l' exemple de FragmentList du guide de fragment, vous pouvez trouver:

@Override
public void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    outState.putInt("curChoice", mCurCheckPosition);
}

Que vous pouvez utiliser plus tard comme ceci:

@Override
public void onActivityCreated(Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    if (savedInstanceState != null) {
        // Restore last state for checked position.
        mCurCheckPosition = savedInstanceState.getInt("curChoice", 0);
    }
}

Je suis un débutant dans Fragments mais cela semble être une solution à votre problème;) OnActivityCreated est invoqué après le retour du fragment de la pile arrière.

ania
la source
18
Je ne pouvais pas faire fonctionner cela. SaveInstanceState était toujours nul. J'ajoute un fragment via une mise en page xml. J'ai dû changer le mCurCheckPosition en statique, puis cela fonctionne, mais se sent piraté.
scottyab
57
il n'appelle pas onSaveInstanceState - pourquoi le ferait-il? Donc, cette approche ne fonctionne pas.
minuit
10
Cette approche fonctionnera-t-elle vraiment au cas où nous voudrions conserver l'état de fragment tout en revenant d'un autre fragment dans la même activité? onSaveInstanceState () est appelé uniquement sur les événements Activity onPause / onStop. Selon les documentations: "Tout comme une activité, vous pouvez conserver l'état d'un fragment à l'aide d'un Bundle, au cas où le processus de l'activité serait tué et que vous deviez restaurer l'état du fragment lorsque l'activité est recréée. Vous pouvez enregistrer l'état pendant le rappel onSaveInstanceState () du fragment et le restaurer pendant soit onCreate (), onCreateView () ou onActivityCreated (). "
Paramvir Singh
26
Pour mémoire, cette approche est erronée et ne devrait pas connaître les votes positifs dont elle dispose. onSaveInstanceStaten'est appelée que lorsque son activité correspondante s'arrête également.
Martin Konecny
16
onSaveInstanceState () est appelé uniquement lorsque des modifications de configuration se sont produites et que l'activité est détruite, cette réponse est fausse
Tadas Valaitis
83

Les fragments ne onSaveInstanceState(Bundle outState)seront jamais appelés à moins que l'activité du fragment ne l'appelle sur lui-même et les fragments attachés. Ainsi, cette méthode ne sera pas appelée jusqu'à ce que quelque chose (généralement une rotation) force l'activité SaveInstanceStateet la restaure plus tard. Mais si vous n'avez qu'une seule activité et un grand ensemble de fragments à l'intérieur (avec une utilisation intensive de replace) et que l'application ne s'exécute que dans une seule activité d'orientation, elle onSaveInstanceState(Bundle outState)peut ne pas être appelée pendant longtemps.

Je connais trois solutions de contournement possibles.

La première:

utilisez les arguments de fragment pour contenir des données importantes:

public class FragmentA extends Fragment {
    private static final String PERSISTENT_VARIABLE_BUNDLE_KEY = "persistentVariable";

    private EditText persistentVariableEdit;

    public FragmentA() {
        setArguments(new Bundle());
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_a, null);

        persistentVariableEdit = (EditText) view.findViewById(R.id.editText);

        TextView proofTextView = (TextView) view.findViewById(R.id.textView);

        Bundle mySavedInstanceState = getArguments();
        String persistentVariable = mySavedInstanceState.getString(PERSISTENT_VARIABLE_BUNDLE_KEY);

        proofTextView.setText(persistentVariable);


        view.findViewById(R.id.btnPushFragmentB).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                getFragmentManager()
                        .beginTransaction()
                        .replace(R.id.frameLayout, new FragmentB())
                        .addToBackStack(null)
                        .commit();
            }
        });

        return view;
    }

    @Override
    public void onPause() {
        super.onPause();
        String persistentVariable = persistentVariableEdit.getText().toString();

        getArguments().putString(PERSISTENT_VARIABLE_BUNDLE_KEY, persistentVariable);
    }
}

La seconde, mais de manière moins pédant - variables de maintien dans singletons

Le troisième - ne pas replace()fragmenter mais add()/ show()/ hide()eux à la place.

Fyodor Volchyok
la source
3
La meilleure solution quand Fragment.onSaveInstanceState()on n'a jamais été appelé. Enregistrez simplement vos propres données dans l'argument, y compris les éléments de la vue liste ou simplement leurs identifiants (si vous avez un autre gestionnaire de données centralisé). Pas besoin d'enregistrer la position de la vue de liste - qui a été enregistrée et restaurée automatiquement.
John Pang
J'ai essayé d'utiliser votre exemple dans mon application, mais ceci: String persistentVariable = mySavedInstanceState.getString(PERSISTENT_VARIABLE_BUNDLE_KEY);est toujours null. Quel est le problème?
vendredi
Utiliser les fragments getArguments()est DEFINITIVEMENT la voie à suivre, y compris les fragments imbriqués dans un ViewPager. J'utilise 1 activité et échangez de nombreux fragments in / out, et cela fonctionne parfaitement. Voici un test simple pour vous permettre de vérifier toute solution proposée: 1) passer du fragment A au fragment B; 2) changer l'orientation de l'appareil deux fois; 3) appuyez sur le bouton de retour de l'appareil.
Andy H.
J'ai essayé la première approche mais cela n'a pas fonctionné pour moi. getArguments () renvoie toujours null. Cela a également du sens car le fragment est remplacé et dans onCreate () vous définissez un nouveau Bundle afin que l'ancien Bundle soit perdu. Qu'est-ce que je manque et ai-je tort?
Zvi
@Zvi, j'ai écrit ce code il y a 1,5 ans et je ne me souviens pas de tous les détails, mais si je me souviens bien, replace ne recrée pas les fragments, fragment recréé uniquement si vous avez créé une nouvelle instance à partir de votre code. Dans ce cas, évidemment, le constructeur a appelé et setArguments(new Bundle());écrasé l'ancien Bundle. Assurez-vous donc d'avoir créé le fragment une seule fois, puis utilisez cette instance au lieu d'en créer une à chaque fois.
Fyodor Volchyok
20

Notez simplement que si vous travaillez avec des fragments à l'aide de ViewPager, c'est assez facile. Il vous suffit d'appeler cette méthode: setOffscreenPageLimit().

Accordign aux docs:

Définissez le nombre de pages à conserver de chaque côté de la page actuelle dans la hiérarchie d'affichage à l'état inactif. Les pages au-delà de cette limite seront recréées à partir de l'adaptateur si nécessaire.

Problème similaire ici

veille
la source
5
Ceci est différent. setOffScreenPageLimit agit comme un cache (c'est-à-dire combien de pages le ViewPager doit gérer à un moment donné) mais n'est pas utilisé pour sauvegarder l'état d'un fragment.
Renaud Mathieu
Dans mon cas, cela fonctionnait avec setOffscreenPageLimit () - bien que les fragments aient été détruits, l'état de la vue a été enregistré et restauré.
Davincho
Merci, m'a aidé aussi.
Elijah
Des années plus tard et cela est toujours aussi pertinent. Bien que cela ne réponde pas vraiment à la question, cela résout un problème
Supreme Dolphin
19

Gonflez simplement votre View pour une fois.

suit Exemple:

public class AFragment extends Fragment {

private View mRootView;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    if(mRootView==null){
        mRootView = inflater.inflate(R.id.fragment_a, container, false);
        //......
    }
    return mRootView;
}

}

Lym Zoy
la source
devrait également conserver les fragments existants dans un tableau ou quelque chose comme ça
Amir
J'ai entendu dire que garder une référence à rootView d'un fragment est une mauvaise pratique - pourrait entraîner des fuites [citation nécessaire]?
giraffe.guru
1
@ giraffe.guru se Fragmentréfère à sa vue racine ne fera pas cela. Alors que la référence par certains éléments GC-root sera, comme la propriété statique globale, une variable de thread non-ui. Une Fragmentinstance n'est pas GC-root, elle peut donc être récupérée. Il en sera de même pour sa vue racine.
Lym Zoy
Vous même ma journée.
Vijendra patidar le
Doux et simple.
Rupam Das le
8

J'ai travaillé avec un problème très similaire à celui-ci. Comme je savais que je reviendrais fréquemment à un fragment précédent, j'ai vérifié si le fragment .isAdded()était vrai, et si oui, plutôt que de faire un, transaction.replace()je fais juste un transaction.show(). Cela empêche le fragment d'être recréé s'il est déjà sur la pile - aucune sauvegarde d'état n'est nécessaire.

Fragment target = <my fragment>;
FragmentTransaction transaction = getFragmentManager().beginTransaction();
transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
if(target.isAdded()) {
    transaction.show(target);
} else {
    transaction.addToBackStack(button_id + "stack_item");
    transaction.replace(R.id.page_fragment, target);
}
transaction.commit();

Une autre chose à garder à l'esprit est que, bien que cela préserve l'ordre naturel des fragments eux-mêmes, vous devrez peut-être gérer l'activité elle-même détruite et recréée lors du changement d'orientation (de configuration). Pour contourner ce problème dans AndroidManifest.xml pour votre nœud:

android:configChanges="orientation|screenSize"

Dans Android 3.0 et supérieur, le screenSizeest apparemment requis.

Bonne chance

rmirabelle
la source
transaction.addToBackStack (button_id + "stack_item"); // que fait cette ligne. Qu'est-ce que button_id ici?
raghu_3
button_id est juste une variable composée. L'argument de chaîne passé à addToBackStack n'est qu'un nom facultatif pour l'état de la backstack - vous pouvez le définir sur null si vous ne gérez qu'un seul backstack.
rmirabelle
, j'ai un problème similaire à celui-ci avec des fragments, pouvez-vous s'il vous plaît le regarder- stackoverflow.com/questions/22468977/…
raghu_3
1
N'ajoutez jamais android:configChanges=everythinYouCanThinkOf|moreThingsYouFoundOnTheInternetsvotre manifeste. Apprenez plutôt à enregistrer et à restaurer l'état, par exemple à partir d'ici: speakerdeck.com/cyrilmottier
Marcin Koziński
Et puis une fois que vous avez appris à quel point l'état de sauvegarde est incroyablement lourd et que la solution présentée résout le problème le plus proprement dans votre situation particulière, allez-y et utilisez-la, sans vous soucier des
votes négatifs
5

La meilleure solution que j'ai trouvée est ci-dessous:

onSavedInstanceState (): toujours appelé à l'intérieur du fragment lorsque l'activité va s'arrêter (déplacer l'activité de l'une à l'autre ou modifier la configuration). Donc, si nous appelons plusieurs fragments sur la même activité, nous devons utiliser l'approche suivante:

Utilisez OnDestroyView () du fragment et enregistrez l'objet entier dans cette méthode. Then OnActivityCreated (): Vérifiez que si l'objet est nul ou non (car cette méthode appelle à chaque fois). Maintenant, restaurez l'état d'un objet ici.

Cela fonctionne toujours!

Aman Goel
la source
4

si vous gérez les changements de configuration dans votre activité de fragment spécifiée dans le manifeste Android comme ceci

<activity
    android:name=".courses.posts.EditPostActivity"
    android:configChanges="keyboardHidden|orientation"
    android:screenOrientation="unspecified" />

alors le onSaveInstanceStatedu fragment ne sera pas invoqué et l' savedInstanceStateobjet sera toujours nul.

Brook Oldre
la source
1

je ne pense pas que ce onSaveInstanceStatesoit une bonne solution. il ne sert qu'à une activité qui avait été déstockée.

Depuis Android 3.0, le Fragmen a été gestionnaire par FragmentManager, la condition est: une activité de cartographie de nombreux fragments, lorsque le fragment est ajouté (pas remplacé: il sera recréé) dans backStack, la vue sera déstockée. une fois de retour au dernier, il s'affichera comme avant.

Je pense donc que le fragmentManger et la transaction sont assez bons pour le gérer.

plus petit
la source
0

J'ai utilisé une approche hybride pour les fragments contenant une vue de liste. Cela semble être performant puisque je ne remplace pas le fragment actuel, mais ajoute plutôt le nouveau fragment et cache le fragment actuel. J'ai la méthode suivante dans l'activité qui héberge mes fragments:

public void addFragment(Fragment currentFragment, Fragment targetFragment, String tag) {
    FragmentManager fragmentManager = getSupportFragmentManager();
    FragmentTransaction transaction = fragmentManager.beginTransaction();
    transaction.setCustomAnimations(0,0,0,0);
    transaction.hide(currentFragment);
    // use a fragment tag, so that later on we can find the currently displayed fragment
    transaction.add(R.id.frame_layout, targetFragment, tag)
            .addToBackStack(tag)
            .commit();
}

J'utilise cette méthode dans mon fragment (contenant la vue de liste) chaque fois qu'un élément de liste est cliqué / tapé (et donc j'ai besoin de lancer / afficher le fragment de détails):

FragmentManager fragmentManager = getActivity().getSupportFragmentManager();
SearchFragment currentFragment = (SearchFragment) fragmentManager.findFragmentByTag(getFragmentTags()[0]);
DetailsFragment detailsFragment = DetailsFragment.newInstance("some object containing some details");
((MainActivity) getActivity()).addFragment(currentFragment, detailsFragment, "Details");

getFragmentTags()renvoie un tableau de chaînes que j'utilise comme balises pour différents fragments lorsque j'ajoute un nouveau fragment (voir transaction.addméthode dans la addFragmentméthode ci-dessus).

Dans le fragment contenant la vue liste, je fais ceci dans sa méthode onPause ():

@Override
public void onPause() {
    // keep the list view's state in memory ("save" it) 
    // before adding a new fragment or replacing current fragment with a new one
    ListView lv =  (ListView) getActivity().findViewById(R.id.listView);
    mListViewState = lv.onSaveInstanceState();
    super.onPause();
}

Puis dans onCreateView du fragment (en fait dans une méthode qui est invoquée dans onCreateView), je restaure l'état:

// Restore previous state (including selected item index and scroll position)
if(mListViewState != null) {
    Log.d(TAG, "Restoring the listview's state.");
    lv.onRestoreInstanceState(mListViewState);
}
Javad Sadeqzadeh
la source
0

En fin de compte, après avoir essayé plusieurs de ces solutions compliquées, car je n'avais besoin que de sauvegarder / restaurer une seule valeur dans mon Fragment (le contenu d'un EditText), et bien que ce ne soit peut-être pas la solution la plus élégante, créer une SharedPreference et stocker mon état là a travaillé pour moi

VMMF
la source
0

Un moyen simple de conserver les valeurs des champs dans différents fragments d'une activité

Créer les instances de fragments et ajouter au lieu de remplacer et supprimer

    FragA  fa= new FragA();
    FragB  fb= new FragB();
    FragC  fc= new FragB();
    fragmentManager = getSupportFragmentManager();
    fragmentTransaction = fragmentManager.beginTransaction();
    fragmentTransaction.add(R.id.fragmnt_container, fa);
    fragmentTransaction.add(R.id.fragmnt_container, fb);
    fragmentTransaction.add(R.id.fragmnt_container, fc);
    fragmentTransaction.show(fa);
    fragmentTransaction.hide(fb);
    fragmentTransaction.hide(fc);
    fragmentTransaction.commit();

Ensuite, affichez et masquez simplement les fragments au lieu de les ajouter et de les supprimer à nouveau

    fragmentTransaction = fragmentManager.beginTransaction();
    fragmentTransaction.hide(fa);
    fragmentTransaction.show(fb);
    fragmentTransaction.hide(fc);
    fragmentTransaction.commit()

;

Sreejesh K Nair
la source
-1
private ViewPager viewPager;
viewPager = (ViewPager) findViewById(R.id.pager);
mAdapter = new TabsPagerAdapter(getSupportFragmentManager());
viewPager.setAdapter(mAdapter);
viewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {

        @Override
        public void onPageSelected(int position) {
            // on changing the page
            // make respected tab selected
            actionBar.setSelectedNavigationItem(position);
        }

        @Override
        public void onPageScrolled(int arg0, float arg1, int arg2) {
        }

        @Override
        public void onPageScrollStateChanged(int arg0) {
        }
    });
}

@Override
public void onTabReselected(Tab tab, FragmentTransaction ft) {
}

@Override
public void onTabSelected(Tab tab, FragmentTransaction ft) {
    // on tab selected
    // show respected fragment view
    viewPager.setCurrentItem(tab.getPosition());
}

@Override
public void onTabUnselected(Tab tab, FragmentTransaction ft) {
}
Sathish Kumar
la source
5
Veuillez envisager d'inclure des informations sur votre réponse, plutôt que de simplement publier un code. Nous essayons non seulement de fournir des «correctifs», mais d'aider les gens à apprendre. Vous devez expliquer ce qui n'allait pas dans le code d'origine, ce que vous avez fait différemment et pourquoi vos modifications ont fonctionné.
Andrew Barber