Raz na zawsze, jak poprawnie zapisać stan instancji fragmentów w tylnym stosie?
Znalazłem wiele przykładów podobnych pytań na SO, ale żadna odpowiedź niestety nie spełnia moich wymagań.
Mam różne układy dla pionowego i poziomego i używam tylnego stosu, co uniemożliwia mi używanie setRetainState()
i sztuczek przy użyciu procedur zmiany konfiguracji.
Pokazuję użytkownikowi pewne informacje w TextViews, które nie są zapisywane w domyślnym programie obsługi. Podczas pisania mojej aplikacji wyłącznie za pomocą czynności, działały następujące cóż:
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));
}
Z Fragment
s, działa to tylko w bardzo specyficznych sytuacjach. W szczególności, to, co straszliwie się psuje, to wymiana fragmentu, umieszczenie go w tylnym stosie, a następnie obrócenie ekranu podczas pokazywania nowego fragmentu. Z tego, co zrozumiałem, Stary fragment nie odbiera połączenia do onSaveInstanceState()
podczas zastępowania, ale pozostaje w jakiś sposób połączony z Activity
i ta metoda jest wywoływana później, gdy jej View
już nie istnieje, więc szukanie któregokolwiek z moich TextView
skutkuje NullPointerException
.
Odkryłem również, że utrzymywanie odniesienia do mojego TextViews
nie jest dobrym pomysłem przy Fragment
s, nawet jeśli było to w porządku z Activity
s. W takim przypadku onSaveInstanceState()
faktycznie zapisuje stan, ale problem pojawia się ponownie, jeśli obrócę ekran dwa razy, gdy fragment jest ukryty, ponieważ jego onCreateView()
nie zostanie wywołany w nowej instancji.
Myślałem o zapisaniu stanu w onDestroyView()
do jakiegoś elementu klasy Bundle
-type (w rzeczywistości jest to więcej danych, a nie tylko jeden TextView
) i zapisaniu to W onSaveInstanceState()
ale są inne wady. Przede wszystkim, jeśli fragment jest obecnie pokazany, kolejność wywołania obu funkcji jest odwrócona, więc musiałbym uwzględnić dwie różne sytuacje. Musi być czystsze i prawidłowe rozwiązanie!
6 answers
Aby poprawnie zapisać stan instancji Fragment
należy wykonać następujące czynności:
1. w fragmencie Zapisz stan instancji przez nadpisanie onSaveInstanceState()
i przywróć w 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. i ważny punkt , w aktywności należy zapisać instancję fragmentu w onSaveInstanceState()
i przywrócić w 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);
}
Mam nadzieję, że to pomoże.Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-02-16 12:43:38
To jest sposób, w jaki używam w tej chwili... to bardzo skomplikowane, ale przynajmniej radzi sobie ze wszystkimi możliwymi sytuacjami. Gdyby ktoś był zainteresowany.
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());
}
/* (...) */
}
Alternatywnie , Zawsze istnieje możliwość przechowywania danych wyświetlanych w pasywnych View
s W zmiennych i używania View
s Tylko do ich wyświetlania, zachowując synchronizację obu rzeczy. Nie uważam jednak ostatniej części za czystą.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-06-25 15:04:22
W najnowszej bibliotece wsparcia żadne z omawianych rozwiązań nie jest już konieczne. Możesz grać swoimi fragmentami Activity
, Jak chcesz, używając FragmentTransaction
. Po prostu upewnij się, że fragmenty można zidentyfikować za pomocą identyfikatora lub tagu.
Fragmenty zostaną przywrócone automatycznie, o ile nie spróbujesz ich odtworzyć przy każdym wywołaniu onCreate()
. Zamiast tego powinieneś sprawdzić, czy savedInstanceState
nie jest null i znaleźć stare odniesienia do utworzonych fragmentów w tym przypadku.
Tutaj jest przykładem:
@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);
}
...
}
Zauważ jednak, że obecnie występuje błąd podczas przywracania ukrytego stanu fragmentu. Jeśli ukrywasz fragmenty w swojej aktywności, w tym przypadku musisz ręcznie przywrócić ten stan.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-10-06 21:32:15
Chcę tylko podać rozwiązanie, które wymyśliłem, które obsługuje wszystkie przypadki przedstawione w tym poście, które zaczerpnąłem z Vasek i devconsole. To rozwiązanie obsługuje również specjalny przypadek, gdy telefon jest obracany więcej niż jeden raz, gdy fragmenty nie są widoczne.
Oto, gdzie przechowuję pakiet do późniejszego wykorzystania, ponieważ onCreate i onSaveInstanceState są jedynymi wywołaniami, które są wykonywane, gdy fragment nie jest widoczny
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);
}
}
Ponieważ destroyView nie jest wywoływany w specjalnym sytuacja rotacyjna możemy być pewni, że jeśli tworzy stan, powinniśmy go użyć.
@Override
public void onDestroyView() {
super.onDestroyView();
savedState = saveState();
createdStateInDestroyView = true;
myObject = null;
}
Ta część byłaby taka sama.
private Bundle saveState() {
Bundle state = new Bundle();
state.putSerializable(SAVED_BUNDLE_TAG, myObject);
return state;
}
Teraztutaj jest trudna część. W mojej metodzie onActivityCreated inicjuję zmienną "myObject", ale rotacja dzieje się onActivity i onCreateView nie jest wywoływana. Dlatego myObject będzie null w tej sytuacji, gdy orientacja obraca się więcej niż jeden raz. Obejść to poprzez ponowne użycie tego samego pakietu, który został zapisany w onCreate jako out going 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);
}
Teraz, gdziekolwiek chcesz przywrócić stan, użyj pakietu savedState
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
...
if(savedState != null) {
myObject = (MyObject) savedState.getSerializable(SAVED_BUNDLE_TAG);
}
...
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-11-02 17:04:07
Dzięki DroidT , zrobiłem to:
Zdaję sobie sprawę, że jeśli Fragment nie wykona oncreateview (), jego widok nie zostanie utworzony. Tak więc, jeśli fragment na tylnym stosie nie stworzył swoich widoków, zapisuję ostatni zapisany stan, w przeciwnym razie buduję własny pakiet z danymi, które chcę zapisać/przywrócić.
1) Rozszerz tę klasę:
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) w Twoim fragmencie musisz mieć to:
@Override
protected boolean hasSavedState() {
Bundle state = getSavedState();
if (state == null) {
return false;
}
//restore your data here
return true;
}
3) na przykład, można wywołać hasSavedState w onActivityCreated:
@Override
public void onActivityCreated(Bundle state) {
super.onActivityCreated(state);
if (hasSavedState()) {
return;
}
//your code here
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-08-24 18:58:59
final FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.hide(currentFragment);
ft.add(R.id.content_frame, newFragment.newInstance(context), "Profile");
ft.addToBackStack(null);
ft.commit();
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-12-21 06:45:31