Une fois pour toutes, comment sauvegarder correctement l'état d'instance de Fragments dans la pile arrière?

J'ai trouvé de nombreuses instances d'une question similaire sur SO, mais aucune réponse répond mal à mes besoins.

J'ai des configurations différentes pour le portrait et le paysage et j'utilise la pile arrière, ce qui m'empêche d'utiliser setRetainState() et des astuces en utilisant des routines de changement de configuration.

  • Quel est l'avantage de l'utilisation de SQLite plutôt que du fichier?
  • Messages perdus sur XMPP sur périphérique déconnecté
  • Enregistrement de données sur le stockage externe
  • Changer le texte Marche / Arrêt d'un bouton de basculement Android
  • Processus Android Daemon
  • Ne peut pas se débarrasser de l'erreur "/ usr / bin / ld: ne peut pas trouver -ncurses"
  • Je montre certaines informations à l'utilisateur dans TextViews, qui ne sont pas enregistrées dans le gestionnaire par défaut. Lors de l'écriture de ma demande uniquement en utilisant les activités, les éléments suivants ont bien fonctionné:

     TextView vstup; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.whatever); vstup = (TextView)findViewById(R.id.whatever); /* (...) */ } @Override public void onSaveInstanceState(Bundle state) { super.onSaveInstanceState(state); state.putCharSequence(App.VSTUP, vstup.getText()); } @Override public void onRestoreInstanceState(Bundle state) { super.onRestoreInstanceState(state); vstup.setText(state.getCharSequence(App.VSTUP)); } 

    Avec Fragment s, cela ne fonctionne que dans des situations très spécifiques. Plus précisément, ce qui se déplace horriblement est de remplacer un fragment, de le placer dans la pile arrière, puis de faire tourner l'écran pendant que le nouveau fragment est affiché. D'après ce que j'ai compris, l'ancien fragment ne reçoit pas un appel à onSaveInstanceState() lorsqu'il est remplacé, mais reste en quelque sorte lié à l' Activity et cette méthode est appelée plus tard quand sa View n'existe plus, alors je TextView les résultats de mon TextView s Dans une NullPointerException .

    En outre, j'ai constaté que garder la référence à mes TextViews n'est pas une bonne idée avec Fragment s, même s'il était correct avec Activity . Dans ce cas, onSaveInstanceState() enregistre effectivement l'état, mais le problème réapparaît si je tourne l'écran deux fois lorsque le fragment est masqué, car onCreateView() n'est pas appelé dans la nouvelle instance.

    J'ai pensé à enregistrer l'état dans onDestroyView() dans un élément de membre de classe Bundle type (il s'agit en fait de plus de données, pas seulement d'un TextView ) et de l'enregistrer dans onSaveInstanceState() mais il existe d'autres inconvénients. Principalement, si le fragment est actuellement affiché, l'ordre d'appel des deux fonctions est inversé, alors je devrais tenir compte de deux situations différentes. Il doit y avoir une solution plus propre et correcte!

  • JavaScript s'arrête dans l'onglet Android Chrome inactif
  • Est-il bon d'appeler findViewById chaque fois dans le cycle de vie de l'activité chaque fois que cela est requis?
  • WebView shouldOverrideUrlLoading () n'est pas appelé à des liens non valides
  • Android utilisant les autorisations Super User? Permettant l'accès
  • NullPointerException dans handleOnGetSentenceSuggestionsMultiple (SpellCheckerSession)
  • Définissez un style personnalisé (thème) sur un composant de Switch Android
  • 6 Solutions collect form web for “Une fois pour toutes, comment sauvegarder correctement l'état d'instance de Fragments dans la pile arrière?”

    Pour enregistrer correctement l'état d'instance de Fragment vous devez effectuer les opérations suivantes:

    1. Dans le fragment, enregistrez l'état de l'instance en annulant onSaveInstanceState() et rétablissez sur onActivityCreated() :

     @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); ... if (savedInstanceState != null) { //Restore the fragment's state here } } ... @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); //Save the fragment's state here } 

    2. Et point important , dans l'activité, vous devez enregistrer l'instance du fragment dans onSaveInstanceState() et restaurer dans onCreate() .

     public void onCreate(Bundle savedInstanceState) { ... if (savedInstanceState != null) { //Restore the fragment's instance mContent = getSupportFragmentManager().getFragment(savedInstanceState, "myFragmentName"); ... } ... } @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); //Save the fragment's instance getSupportFragmentManager().putFragment(outState, "myFragmentName", mContent); } 

    J'espère que cela t'aides.

    C'est comme ça que j'utilise en ce moment … c'est très compliqué, mais au moins il gère toutes les situations possibles. Dans le cas où quelqu'un est intéressé.

     public final class MyFragment extends Fragment { private TextView vstup; private Bundle savedState = null; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View v = inflater.inflate(R.layout.whatever, null); vstup = (TextView)v.findViewById(R.id.whatever); /* (...) */ /* If the Fragment was destroyed inbetween (screen rotation), we need to recover the savedState first */ /* However, if it was not, it stays in the instance from the last onDestroyView() and we don't want to overwrite it */ if(savedInstanceState != null && savedState == null) { savedState = savedInstanceState.getBundle(App.STAV); } if(savedState != null) { vstup.setText(savedState.getCharSequence(App.VSTUP)); } savedState = null; return v; } @Override public void onDestroyView() { super.onDestroyView(); savedState = saveState(); /* vstup defined here for sure */ vstup = null; } private Bundle saveState() { /* called either from onDestroyView() or onSaveInstanceState() */ Bundle state = new Bundle(); state.putCharSequence(App.VSTUP, vstup.getText()); return state; } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); /* If onDestroyView() is called first, we can use the previously savedState but we can't call saveState() anymore */ /* If onSaveInstanceState() is called first, we don't have savedState, so we need to call saveState() */ /* => (?:) operator inevitable! */ outState.putBundle(App.STAV, (savedState != null) ? savedState : saveState()); } /* (...) */ } 

    Alternativement , il est toujours possible de garder les données affichées dans les View passives dans les variables et d'utiliser la View s uniquement pour les afficher, en maintenant les deux choses en synchro. Cependant, je ne considère pas la dernière partie très propre.

    Sur la dernière bibliothèque de support, aucune des solutions discutées ici n'est nécessaire. Vous pouvez jouer avec les fragments de votre Activity comme vous le souhaitez en utilisant FragmentTransaction . Assurez-vous simplement que vos fragments peuvent être identifiés avec un identifiant ou une étiquette.

    Les fragments seront restaurés automatiquement tant que vous n'essayez pas de les recréer sur chaque appel à onCreate() . Au lieu de cela, vous devez vérifier si savedInstanceState n'est pas nul et recherchez les anciennes références aux fragments créés dans ce cas.

    Voici un exemple:

     @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (savedInstanceState == null) { myFragment = MyFragment.newInstance(); getSupportFragmentManager() .beginTransaction() .add(R.id.my_container, myFragment, MY_FRAGMENT_TAG) .commit(); } else { myFragment = (MyFragment) getSupportFragmentManager() .findFragmentByTag(MY_FRAGMENT_TAG); } ... } 

    Notez cependant qu'il existe actuellement un bug lors de la restauration de l'état caché d'un fragment. Si vous cachez des fragments dans votre activité, vous devrez restaurer cet état manuellement dans ce cas.

    Je veux juste donner la solution avec laquelle j'ai abordé tous les cas présentés dans cette publication que j'ai dérivée de Vasek et de devconsole. Cette solution gère également le cas spécial lorsque le téléphone tourne plus d'une fois alors que les fragments ne sont pas visibles.

    Voici si je stocke le paquet pour une utilisation ultérieure puisque onCreate et onSaveInstanceState sont les seuls appels qui sont réalisés lorsque le fragment n'est pas visible

     MyObject myObject; private Bundle savedState = null; private boolean createdStateInDestroyView; private static final String SAVED_BUNDLE_TAG = "saved_bundle"; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (savedInstanceState != null) { savedState = savedInstanceState.getBundle(SAVED_BUNDLE_TAG); } } 

    Puisque destroyView n'est pas appelé dans la situation de rotation spéciale, nous pouvons être certains que si elle crée l'état, nous devrions l'utiliser.

     @Override public void onDestroyView() { super.onDestroyView(); savedState = saveState(); createdStateInDestroyView = true; myObject = null; } 

    Cette partie serait la même.

     private Bundle saveState() { Bundle state = new Bundle(); state.putSerializable(SAVED_BUNDLE_TAG, myObject); return state; } 

    Maintenant, voici la partie délicate. Dans ma méthode onActivityCreated, je crée une instanciation sur la variable "myObject", mais la rotation se produit sur Activity et onCreateView ne sont pas appelés. Par conséquent, myObject sera nul dans cette situation lorsque l'orientation tourne plus d'une fois. Je contourne ce problème en réutilisant le même paquet qui a été sauvegardé sur le bouton Créer en tant que forfait.

      @Override public void onSaveInstanceState(Bundle outState) { if (myObject == null) { outState.putBundle(SAVED_BUNDLE_TAG, savedState); } else { outState.putBundle(SAVED_BUNDLE_TAG, createdStateInDestroyView ? savedState : saveState()); } createdStateInDestroyView = false; super.onSaveInstanceState(outState); } 

    Maintenant, où que vous souhaitiez restaurer l'état, utilisez simplement le lot stocké sauvegardé

      @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { ... if(savedState != null) { myObject = (MyObject) savedState.getSerializable(SAVED_BUNDLE_TAG); } ... } 

    Grâce à DroidT , j'ai fait ceci:

    Je me rends compte que si le Fragment n'exécute pas surCreateView (), sa vue n'est pas instanciée. Donc, si le fragment sur la pile arrière n'a pas créé ses vues, je sauvegarde le dernier état enregistré, sinon je crée mon propre paquet avec les données que je souhaite enregistrer / restaurer.

    1) Étendre cette classe:

     import android.os.Bundle; import android.support.v4.app.Fragment; public abstract class StatefulFragment extends Fragment { private Bundle savedState; private boolean saved; private static final String _FRAGMENT_STATE = "FRAGMENT_STATE"; @Override public void onSaveInstanceState(Bundle state) { if (getView() == null) { state.putBundle(_FRAGMENT_STATE, savedState); } else { Bundle bundle = saved ? savedState : getStateToSave(); state.putBundle(_FRAGMENT_STATE, bundle); } saved = false; super.onSaveInstanceState(state); } @Override public void onCreate(Bundle state) { super.onCreate(state); if (state != null) { savedState = state.getBundle(_FRAGMENT_STATE); } } @Override public void onDestroyView() { savedState = getStateToSave(); saved = true; super.onDestroyView(); } protected Bundle getSavedState() { return savedState; } protected abstract boolean hasSavedState(); protected abstract Bundle getStateToSave(); } 

    2) Dans votre Fragment, vous devez avoir ceci:

     @Override protected boolean hasSavedState() { Bundle state = getSavedState(); if (state == null) { return false; } //restore your data here return true; } 

    3) Par exemple, vous pouvez appeler hasSavedState dans OnCityCreated:

     @Override public void onActivityCreated(Bundle state) { super.onActivityCreated(state); if (hasSavedState()) { return; } //your code here } 
     final FragmentTransaction ft = getFragmentManager().beginTransaction(); ft.hide(currentFragment); ft.add(R.id.content_frame, newFragment.newInstance(context), "Profile"); ft.addToBackStack(null); ft.commit(); 
    coAndroid est un fan Android de Google, tout sur les téléphones Android, Android Wear, Android Dev et Android Games Apps.