it-swarm.com.de

Wie kann man ein für alle Mal den Instanzstatus von Fragmenten im Backstack korrekt speichern?

Ich habe viele Fälle einer ähnlichen Frage zu SO gefunden, aber keine Antwort entspricht leider meinen Anforderungen.

Ich habe unterschiedliche Layouts für Hoch- und Querformat und verwende Backstack, wodurch ich sowohl die Verwendung von setRetainState() als auch Tricks mit Konfigurationsänderungsroutinen verhindere.

Ich zeige dem Benutzer in TextViews bestimmte Informationen, die nicht im Standardhandler gespeichert werden. Wenn ich meine Bewerbung ausschließlich mit Aktivitäten schreibe, hat Folgendes gut funktioniert:

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));
}

Mit Fragments funktioniert dies nur in ganz bestimmten Situationen. Insbesondere ist es fürchterlich, ein Fragment zu ersetzen, es in den hinteren Stapel zu legen und dann den Bildschirm zu drehen, während das neue Fragment angezeigt wird. Soweit ich weiß, wird das alte Fragment beim Ersetzen nicht mit onSaveInstanceState() aufgerufen, sondern bleibt irgendwie mit Activity verknüpft, und diese Methode wird später aufgerufen, wenn ihr View nicht mehr existiert Wenn Sie also nach einem meiner TextView -Ergebnisse suchen, wird NullPointerException.

Außerdem stellte ich fest, dass das Behalten des Verweises auf mein TextViews bei Fragments keine gute Idee ist, auch wenn es bei Activity in Ordnung war. In diesem Fall speichert onSaveInstanceState() den Status tatsächlich, aber das Problem tritt erneut auf, wenn ich den Bildschirm drehe zweimal, wenn das Fragment ausgeblendet ist, da dessen onCreateView() im neuen nicht aufgerufen wird Beispiel.

Ich dachte daran, den Status in onDestroyView() in ein Bundle- Klassenelement zu speichern (es sind eigentlich mehr Daten, nicht nur ein TextView) und das in zu speichern onSaveInstanceState() aber es gibt noch andere Nachteile. In erster Linie, wenn das Fragment ist angezeigt wird, ist die Reihenfolge des Aufrufs der beiden Funktionen umgekehrt, sodass zwei verschiedene Situationen berücksichtigt werden müssen. Es muss eine saubere und richtige Lösung geben!

448
The Vee

Gehen Sie folgendermaßen vor, um den Instanzstatus von Fragment korrekt zu speichern:

1. Speichern Sie im Fragment den Instanzstatus, indem Sie onSaveInstanceState() überschreiben und in onActivityCreated() wiederherstellen:

@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. Und wichtiger Punkt, in der Aktivität müssen Sie die Instanz des Fragments in onSaveInstanceState() speichern und in onCreate() wiederherstellen.

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);
}

Hoffe das hilft.

508
ThanhHH

Dies ist die Art, wie ich sie im Moment benutze ... es ist sehr kompliziert, aber es behandelt zumindest alle möglichen Situationen. Für den Fall, dass jemand interessiert ist.

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());
    }

    /* (...) */

}

Alternativ, es ist immer möglich, die Daten in passiven Views in Variablen anzuzeigen und die Views nur zum Anzeigen zu verwenden, um die beiden Dinge synchron zu halten. Ich halte den letzten Teil allerdings nicht für sehr sauber.

79
The Vee

In der neuesten Support-Bibliothek ist keine der hier diskutierten Lösungen mehr erforderlich. Sie können mit den Fragmenten Ihres Activity nach Belieben mit dem FragmentTransaction spielen. Stellen Sie einfach sicher, dass Ihre Fragmente entweder mit einer ID oder einem Tag identifiziert werden können.

Die Fragmente werden automatisch wiederhergestellt, solange Sie nicht versuchen, sie bei jedem Aufruf von onCreate() neu zu erstellen. Überprüfen Sie stattdessen, ob savedInstanceState nicht null ist, und suchen Sie in diesem Fall die alten Verweise auf die erstellten Fragmente.

Hier ist ein Beispiel:

@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);
    }
...
}

Beachten Sie jedoch, dass derzeit ein Fehler auftritt, wenn der verborgene Zustand eines Fragments wiederhergestellt wird. Wenn Sie Fragmente in Ihrer Aktivität verstecken, müssen Sie diesen Status in diesem Fall manuell wiederherstellen.

53
Ricardo

Ich möchte nur die Lösung nennen, die ich gefunden habe und die alle in diesem Beitrag vorgestellten Fälle behandelt, die ich von Vasek und devconsole abgeleitet habe. Diese Lösung behandelt auch den Sonderfall, wenn das Telefon mehr als einmal gedreht wird, während Fragmente nicht sichtbar sind.

Hier speichere ich das Bundle zur späteren Verwendung, da onCreate und onSaveInstanceState die einzigen Aufrufe sind, die ausgeführt werden, wenn das Fragment nicht sichtbar ist

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);
    }
}

Da destroyView in der speziellen Rotationssituation nicht aufgerufen wird, können wir sicher sein, dass wir es verwenden sollten, wenn es den Status erstellt.

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

Dieser Teil wäre der gleiche.

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

Nun hier ist der schwierige Teil. In meiner onActivityCreated-Methode instanziiere ich die "myObject" -Variable, aber die Rotation geschieht, wenn onActivity und onCreateView nicht aufgerufen werden. Aus diesem Grund ist myObject in dieser Situation null, wenn die Ausrichtung mehr als einmal gedreht wird. Um dies zu umgehen, verwende ich dasselbe Bundle, das in onCreate gespeichert wurde, erneut als ausgehendes Bundle.

    @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);
}

Verwenden Sie jetzt, wo immer Sie den Status wiederherstellen möchten, das Bundle savedState

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

Dank DroidT habe ich folgendes gemacht:

Mir ist klar, dass, wenn das Fragment onCreateView () nicht ausführt, seine Ansicht nicht instanziiert wird. Wenn das Fragment auf dem Backstack keine Ansichten erstellt hat, speichere ich den zuletzt gespeicherten Status. Andernfalls erstelle ich ein eigenes Bundle mit den Daten, die ich speichern/wiederherstellen möchte.

1) Erweitern Sie diese Klasse:

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) In deinem Fragment musst du folgendes haben:

@Override
protected boolean hasSavedState() {
    Bundle state = getSavedState();

    if (state == null) {
        return false;
    }

    //restore your data here

    return true;
}

3) Sie können beispielsweise hasSavedState in onActivityCreated aufrufen:

@Override
public void onActivityCreated(Bundle state) {
    super.onActivityCreated(state);

    if (hasSavedState()) {
        return;
    }

    //your code here
}
3
Noturno