studio - java android fragment
Una vez por todas, ¿cómo guardar correctamente el estado de la instancia de los Fragmentos en la pila trasera? (6)
En la última biblioteca de soporte, ninguna de las soluciones aquí discutidas es necesaria. Puedes jugar con los fragmentos de tu Activity
tu gusto usando FragmentTransaction
. Solo asegúrese de que sus fragmentos puedan identificarse con un ID o etiqueta.
Los fragmentos se restaurarán automáticamente siempre que no intentes volver a onCreate()
en cada llamada a onCreate()
. En su lugar, debe verificar si savedInstanceState
no es nulo y encontrar las referencias antiguas a los fragmentos creados en este caso.
Aquí hay un ejemplo:
@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);
}
...
}
Sin embargo, tenga en cuenta que actualmente hay un bug al restaurar el estado oculto de un fragmento. Si está ocultando fragmentos en su actividad, deberá restaurar este estado manualmente en este caso.
He encontrado muchos ejemplos de una pregunta similar en SO, pero desafortunadamente ninguna respuesta cumple con mis requisitos.
Tengo diferentes diseños para retrato y paisaje y estoy usando la contraportada, lo que me impide usar setRetainState()
y los trucos que usan las rutinas de cambio de configuración.
Le muestro cierta información al usuario en TextViews, que no se guardan en el controlador predeterminado. Al escribir mi solicitud únicamente utilizando Actividades, lo siguiente funcionó bien:
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));
}
Con Fragment
s, esto funciona solo en situaciones muy específicas. Específicamente, lo que se rompe horriblemente es reemplazar un fragmento, colocarlo en la pila posterior y luego girar la pantalla mientras se muestra el nuevo fragmento. Por lo que entendí, el fragmento antiguo no recibe una llamada a onSaveInstanceState()
cuando se reemplaza, pero permanece vinculado de alguna manera a la Activity
y este método se llama más adelante cuando su View
ya no existe, por lo que busco cualquiera de los resultados de TextView
. en una NullPointerException
.
Además, encontré que mantener la referencia a mis TextViews
no es una buena idea con Fragment
s, incluso si estaba de acuerdo con la Activity
. En ese caso, onSaveInstanceState()
realmente guarda el estado pero el problema vuelve a aparecer si giro la pantalla dos veces cuando el fragmento está oculto, ya que no se llama a su onCreateView()
en la nueva instancia.
Pensé en guardar el estado en onDestroyView()
en algún elemento miembro de la clase Bundle
(en realidad es más datos, no solo un TextView
) y guardar eso en onSaveInstanceState()
pero hay otros inconvenientes. Principalmente, si el fragmento se muestra actualmente, el orden de las dos funciones se invierte, por lo que debo tener en cuenta dos situaciones diferentes. ¡Debe haber una solución más limpia y correcta!
Esta es la forma en que estoy usando en este momento ... es muy complicado, pero al menos maneja todas las situaciones posibles. En caso de que alguien esté interesado.
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());
}
/* (...) */
}
Alternativamente , siempre es posible mantener los datos mostrados en las View
pasivas en variables y usar las View
solo para mostrarlos, manteniendo las dos cosas sincronizadas. Aunque no considero la última parte muy limpia.
Gracias a DroidT , hice esto:
Me doy cuenta de que si el Fragmento no se ejecuta enCreateView (), su vista no está instanciada. Entonces, si el fragmento en la pila trasera no creó sus vistas, guardo el último estado almacenado, de lo contrario construyo mi propio paquete con los datos que quiero guardar / restaurar.
1) Extiende esta clase:
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) En tu Fragmento, debes tener esto:
@Override
protected boolean hasSavedState() {
Bundle state = getSavedState();
if (state == null) {
return false;
}
//restore your data here
return true;
}
3) Por ejemplo, puede llamar a hasSavedState en onActivityCreated:
@Override
public void onActivityCreated(Bundle state) {
super.onActivityCreated(state);
if (hasSavedState()) {
return;
}
//your code here
}
Para guardar correctamente el estado de la instancia de Fragment
debe hacer lo siguiente:
1. En el fragmento, guarde el estado de la instancia al anular onSaveInstanceState()
y restaurar en 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. Y un punto importante , en la actividad, debe guardar la instancia del fragmento en onSaveInstanceState()
y restaurar en 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);
}
Espero que esto ayude.
Solo quiero dar la solución que se me ocurrió para manejar todos los casos presentados en este post que obtuve de Vasek y devconsole. Esta solución también maneja el caso especial cuando el teléfono se gira más de una vez, mientras que los fragmentos no son visibles.
Aquí es donde almaceno el paquete para su uso posterior, ya que onCreate y onSaveInstanceState son las únicas llamadas que se realizan cuando el fragmento no es 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);
}
}
Dado que destroyView no se llama en la situación de rotación especial, podemos estar seguros de que si crea el estado deberíamos usarlo.
@Override
public void onDestroyView() {
super.onDestroyView();
savedState = saveState();
createdStateInDestroyView = true;
myObject = null;
}
Esta parte sería la misma.
private Bundle saveState() {
Bundle state = new Bundle();
state.putSerializable(SAVED_BUNDLE_TAG, myObject);
return state;
}
Ahora aquí está la parte difícil. En mi método onActivityCreated, he creado una instancia de la variable "myObject", pero la rotación ocurre en Activity y onCreateView no recibe llamadas. Por lo tanto, myObject será nulo en esta situación cuando la orientación rote más de una vez. Evito esto reutilizando el mismo paquete que se guardó en onCreate como el paquete saliente.
@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);
}
Ahora, donde quiera que desee restaurar el estado, use el paquete savedState
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
...
if(savedState != null) {
myObject = (MyObject) savedState.getSerializable(SAVED_BUNDLE_TAG);
}
...
}
final FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.hide(currentFragment);
ft.add(R.id.content_frame, newFragment.newInstance(context), "Profile");
ft.addToBackStack(null);
ft.commit();