tutorial studio navegar fragments example entre ejemplo activity android android-fragments

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