Fragmento de Android: cómo guardar estados de vistas en un fragmento cuando se empuja otro fragmento sobre él

137

En Android, un fragmento (por ejemplo FragA) se agrega a la pila y otro fragmento (por ejemplo FragB) llega a la parte superior. Ahora al devolver el golpe FragAllega a la cima y onCreateView()se llama. Ahora me encontraba FragAen un estado particular antes de que FragBme pusieran encima.

Mi pregunta es ¿cómo puedo restaurar FragAa su estado anterior? ¿Hay alguna manera de guardar el estado (como decir en un paquete) y, de ser así, ¿qué método debo anular?

pankajagarwal
fuente

Respuestas:

98

En el ejemplo de FragmentList de la guía de fragmentos puedes encontrar:

@Override
public void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    outState.putInt("curChoice", mCurCheckPosition);
}

Que puedes usar más tarde así:

@Override
public void onActivityCreated(Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    if (savedInstanceState != null) {
        // Restore last state for checked position.
        mCurCheckPosition = savedInstanceState.getInt("curChoice", 0);
    }
}

Soy un principiante en Fragments pero parece que es la solución de su problema;) OnActivityCreated se invoca después de que el fragmento vuelve de la pila posterior.

ania
fuente
18
No pude hacer que esto funcione guardadoInstanceState siempre fue nulo. Estoy agregando fragmentos a través del diseño xml. Tuve que cambiar la mCurCheckPosition a estática y luego funciona, pero se siente hacky.
scottyab
57
no llama a OnSaveInstanceState, ¿por qué lo haría? Entonces, este enfoque no funciona.
medianoche
10
¿Funcionará realmente este enfoque en caso de que queramos retener el estado del fragmento mientras regresamos de otro fragmento en la misma Actividad? onSaveInstanceState () se llama solo en los eventos Activity onPause / onStop. De acuerdo con la documentación: "También como una actividad, puede retener el estado de un fragmento usando un Paquete, en caso de que el proceso de la actividad se cancele y necesite restaurar el estado del fragmento cuando se recrea la actividad. Puede guardar el estado durante la devolución de llamada onSaveInstanceState () del fragmento y restaurarla durante onCreate (), onCreateView () o onActivityCreated () ".
Paramvir Singh
26
Para el registro, este enfoque es incorrecto y no debería estar cerca de los votos positivos que tiene. onSaveInstanceStatesolo se llama cuando su actividad correspondiente también se está cerrando.
Martin Konecny
16
onSaveInstanceState () se llama onle cuando se producen cambios de configuración y se destruye la actividad, esta respuesta es incorrecta
Tadas Valaitis
83

Los fragmentos onSaveInstanceState(Bundle outState)nunca se llamarán a menos que la actividad del fragmento lo llame a sí mismo y a los fragmentos adjuntos. Por lo tanto, este método no se llamará hasta que algo (generalmente rotación) fuerce la actividad SaveInstanceStatey la restaure más tarde. Pero si solo tiene una actividad y un gran conjunto de fragmentos dentro de ella (con un uso intensivo de replace) y la aplicación se ejecuta solo en una orientación, es onSaveInstanceState(Bundle outState)posible que no se llame durante mucho tiempo.

Sé tres posibles soluciones alternativas.

El primero:

use argumentos de fragmentos para contener datos importantes:

public class FragmentA extends Fragment {
    private static final String PERSISTENT_VARIABLE_BUNDLE_KEY = "persistentVariable";

    private EditText persistentVariableEdit;

    public FragmentA() {
        setArguments(new Bundle());
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_a, null);

        persistentVariableEdit = (EditText) view.findViewById(R.id.editText);

        TextView proofTextView = (TextView) view.findViewById(R.id.textView);

        Bundle mySavedInstanceState = getArguments();
        String persistentVariable = mySavedInstanceState.getString(PERSISTENT_VARIABLE_BUNDLE_KEY);

        proofTextView.setText(persistentVariable);


        view.findViewById(R.id.btnPushFragmentB).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                getFragmentManager()
                        .beginTransaction()
                        .replace(R.id.frameLayout, new FragmentB())
                        .addToBackStack(null)
                        .commit();
            }
        });

        return view;
    }

    @Override
    public void onPause() {
        super.onPause();
        String persistentVariable = persistentVariableEdit.getText().toString();

        getArguments().putString(PERSISTENT_VARIABLE_BUNDLE_KEY, persistentVariable);
    }
}

La segunda pero menos pedante forma: mantenga las variables en singletons

El tercero: no replace()fragmentes sino add()/ show()/ hide()ellos en su lugar.

Fyodor Volchyok
fuente
3
La mejor solución cuando Fragment.onSaveInstanceState()nunca fue llamado. Simplemente guarde sus propios datos en el argumento, incluidos los elementos en la vista de lista o solo sus ID (si tiene otro administrador de datos centralizado). No es necesario guardar la posición de la vista de lista, que se guardó y restauró automáticamente.
John Pang
Traté de usar su ejemplo en mi aplicación, pero esto: String persistentVariable = mySavedInstanceState.getString(PERSISTENT_VARIABLE_BUNDLE_KEY);siempre es así null. ¿Cuál es el problema?
fragon
Usar los fragmentos getArguments()es DEFINITIVAMENTE el camino a seguir, incluidos los fragmentos anidados en un ViewPager. Uso 1 actividad e intercambio muchos fragmentos de entrada / salida, y esto funciona perfectamente. Aquí hay una prueba simple para que usted examine cualquier solución propuesta: 1) vaya del Fragmento A al Fragmento B; 2) cambiar la orientación del dispositivo dos veces; 3) presione el botón Atrás en el dispositivo.
Andy H.
Intenté el primer enfoque pero no funcionó para mí. getArguments () siempre devuelve nulo. También tiene sentido porque el fragmento se reemplaza y en onCreate () establece un nuevo paquete para que se pierda el paquete anterior. ¿Qué me estoy perdiendo y si me equivoco?
Zvi
@Zvi, escribí este código hace 1,5 años y no recuerdo todos los detalles, pero como recuerdo, el reemplazo no recrea fragmentos, el fragmento se recrea solo si ha creado una nueva instancia a partir de su código. En este caso, obviamente, el constructor llamó y setArguments(new Bundle());sobrescribe el antiguo paquete. Así que asegúrese de crear un fragmento solo una vez, y luego use esta instancia en lugar de crear uno nuevo cada vez.
Fyodor Volchyok
20

Solo tenga en cuenta que si trabaja con Fragments usando ViewPager, es bastante fácil. Sólo tiene que llamar a este método: setOffscreenPageLimit().

Acordar a los documentos:

Establezca el número de páginas que se deben conservar a ambos lados de la página actual en la jerarquía de vistas en un estado inactivo. Las páginas más allá de este límite se recrearán desde el adaptador cuando sea necesario.

Problema similar aquí

víspera
fuente
55
Esto es diferente. setOffScreenPageLimit actúa como un caché (es decir, cuántas páginas debe manejar el ViewPager en un momento dado) pero no se usa para guardar el estado de un Fragmento.
Renaud Mathieu
En mi caso, funcionó con setOffscreenPageLimit (): aunque los fragmentos se destruyeron, el estado de la vista se guardó y se restauró.
Davincho
Gracias, también me ayudó.
Elijah
Años después y esto sigue siendo tan relevante. Aunque en realidad no responde la pregunta, resuelve un problema
Supreme Dolphin el
19

Simplemente infle su Vista por una vez.

Ejemplo sigue:

public class AFragment extends Fragment {

private View mRootView;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    if(mRootView==null){
        mRootView = inflater.inflate(R.id.fragment_a, container, false);
        //......
    }
    return mRootView;
}

}

Lym Zoy
fuente
también debe mantener los fragmentos existentes en una matriz o algo así
Amir
He oído que mantener una referencia al rootView de un fragmento es una mala práctica, ¿podría provocar fugas [cita requerida]?
giraffe.guru
1
@ giraffe.guru se Fragmentrefiere a que su vista raíz no hará eso. Si bien la referencia por parte de algunos elementos raíz del GC , como la propiedad estática global, la variable de subproceso no ui. Una Fragmentinstancia no es raíz de GC, por lo que se puede recolectar basura. También lo hará su vista raíz.
Lym Zoy
Tú mismo mi día.
Vijendra patidar
Dulce y sencillo.
Rupam Das
8

Trabajé con un problema muy similar a este. Como sabía que volvería con frecuencia a un fragmento anterior, verifiqué si el fragmento .isAdded()era verdadero, y si es así, en lugar de hacer un transaction.replace()solo hago un transaction.show(). Esto evita que el fragmento se vuelva a crear si ya está en la pila, sin necesidad de guardar el estado.

Fragment target = <my fragment>;
FragmentTransaction transaction = getFragmentManager().beginTransaction();
transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
if(target.isAdded()) {
    transaction.show(target);
} else {
    transaction.addToBackStack(button_id + "stack_item");
    transaction.replace(R.id.page_fragment, target);
}
transaction.commit();

Otra cosa a tener en cuenta es que, si bien esto preserva el orden natural de los fragmentos, es posible que aún necesite manejar la actividad en sí misma que se destruye y recrea en el cambio de orientación (configuración). Para solucionar esto en AndroidManifest.xml para su nodo:

android:configChanges="orientation|screenSize"

En Android 3.0 y superior, screenSizeaparentemente se requiere.

Buena suerte

rmirabelle
fuente
transaction.addToBackStack (button_id + "stack_item"); // ¿qué hace esta línea? ¿Qué es button_id aquí?
raghu_3
button_id es solo una variable inventada. El argumento de cadena pasado a addToBackStack es solo un nombre opcional para el estado de backstack: puede configurarlo como nulo si solo está administrando un backstack único.
rmirabelle
, estoy teniendo un problema similar a este con los fragmentos, ¿puedes investigarlo? stackoverflow.com/questions/22468977/…
raghu_3
1
Nunca agregue android:configChanges=everythinYouCanThinkOf|moreThingsYouFoundOnTheInternetsen su manifiesto. En cambio, aprenda cómo guardar y restaurar el estado, por ejemplo desde aquí: speakerdeck.com/cyrilmottier/…
Marcin Koziński
Y luego, una vez que haya aprendido cuán increíblemente difícil de manejar es el estado de ahorro y que la solución presentada resuelva el problema de manera más limpia en su situación particular, continúe y
úselo
5

La mejor solución que encontré está a continuación:

onSavedInstanceState (): siempre se llama dentro del fragmento cuando la actividad se va a cerrar (mover la actividad de una a otra o cambiar la configuración). Entonces, si estamos llamando a varios fragmentos en la misma actividad, entonces tenemos que usar el siguiente enfoque:

Use OnDestroyView () del fragmento y guarde todo el objeto dentro de ese método. Luego OnActivityCreated (): compruebe que si el objeto es nulo o no (porque este método llama cada vez). Ahora restaure el estado de un objeto aquí.

¡Funciona siempre!

Aman Goel
fuente
4

si está manejando los cambios de configuración en su actividad de fragmento especificada en el manifiesto de Android como este

<activity
    android:name=".courses.posts.EditPostActivity"
    android:configChanges="keyboardHidden|orientation"
    android:screenOrientation="unspecified" />

entonces onSaveInstanceStateno se invocará el fragmento y el savedInstanceStateobjeto siempre será nulo.

Brook Oldre
fuente
1

No creo que onSaveInstanceStatesea ​​una buena solución. solo se usa para actividades que habían sido destruidas.

Desde Android 3.0, Fragmen ha sido administrado por FragmentManager, la condición es: una actividad que mapea muchos fragmentos, cuando el fragmento se agrega (no reemplaza: se volverá a crear) en backStack, la vista se destruirá. cuando regrese al último, se mostrará como antes.

Así que creo que el fragmentManger y la transacción son lo suficientemente buenos como para manejarlo.

pequeño padre
fuente
0

Usé un enfoque híbrido para fragmentos que contienen una vista de lista. Parece ser rentable ya que no reemplazo el fragmento actual, sino que agrego el nuevo fragmento y oculto el actual. Tengo el siguiente método en la actividad que aloja mis fragmentos:

public void addFragment(Fragment currentFragment, Fragment targetFragment, String tag) {
    FragmentManager fragmentManager = getSupportFragmentManager();
    FragmentTransaction transaction = fragmentManager.beginTransaction();
    transaction.setCustomAnimations(0,0,0,0);
    transaction.hide(currentFragment);
    // use a fragment tag, so that later on we can find the currently displayed fragment
    transaction.add(R.id.frame_layout, targetFragment, tag)
            .addToBackStack(tag)
            .commit();
}

Utilizo este método en mi fragmento (que contiene la vista de lista) cada vez que se hace clic / se toca un elemento de la lista (y, por lo tanto, necesito iniciar / mostrar el fragmento de detalles):

FragmentManager fragmentManager = getActivity().getSupportFragmentManager();
SearchFragment currentFragment = (SearchFragment) fragmentManager.findFragmentByTag(getFragmentTags()[0]);
DetailsFragment detailsFragment = DetailsFragment.newInstance("some object containing some details");
((MainActivity) getActivity()).addFragment(currentFragment, detailsFragment, "Details");

getFragmentTags()devuelve una matriz de cadenas que uso como etiquetas para diferentes fragmentos cuando agrego un nuevo fragmento (vea el transaction.addmétodo en el addFragmentmétodo anterior).

En el fragmento que contiene la vista de lista, hago esto en su método onPause ():

@Override
public void onPause() {
    // keep the list view's state in memory ("save" it) 
    // before adding a new fragment or replacing current fragment with a new one
    ListView lv =  (ListView) getActivity().findViewById(R.id.listView);
    mListViewState = lv.onSaveInstanceState();
    super.onPause();
}

Luego, en onCreateView del fragmento (en realidad en un método que se invoca en onCreateView), restauro el estado:

// Restore previous state (including selected item index and scroll position)
if(mListViewState != null) {
    Log.d(TAG, "Restoring the listview's state.");
    lv.onRestoreInstanceState(mListViewState);
}
Javad Sadeqzadeh
fuente
0

Al final, después de probar muchas de estas soluciones complicadas, ya que solo necesitaba guardar / restaurar un solo valor en mi Fragmento (el contenido de un EditText), y aunque podría no ser la solución más elegante, crear una referencia compartida y almacenar mi estado a mi me funciono

VMMF
fuente
0

Una forma sencilla de mantener los valores de los campos en diferentes fragmentos de una actividad.

Cree las instancias de fragmentos y agregue en lugar de reemplazar y eliminar

    FragA  fa= new FragA();
    FragB  fb= new FragB();
    FragC  fc= new FragB();
    fragmentManager = getSupportFragmentManager();
    fragmentTransaction = fragmentManager.beginTransaction();
    fragmentTransaction.add(R.id.fragmnt_container, fa);
    fragmentTransaction.add(R.id.fragmnt_container, fb);
    fragmentTransaction.add(R.id.fragmnt_container, fc);
    fragmentTransaction.show(fa);
    fragmentTransaction.hide(fb);
    fragmentTransaction.hide(fc);
    fragmentTransaction.commit();

Luego solo muestra y oculta los fragmentos en lugar de agregarlos y eliminarlos nuevamente

    fragmentTransaction = fragmentManager.beginTransaction();
    fragmentTransaction.hide(fa);
    fragmentTransaction.show(fb);
    fragmentTransaction.hide(fc);
    fragmentTransaction.commit()

;

Sreejesh K Nair
fuente
-1
private ViewPager viewPager;
viewPager = (ViewPager) findViewById(R.id.pager);
mAdapter = new TabsPagerAdapter(getSupportFragmentManager());
viewPager.setAdapter(mAdapter);
viewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {

        @Override
        public void onPageSelected(int position) {
            // on changing the page
            // make respected tab selected
            actionBar.setSelectedNavigationItem(position);
        }

        @Override
        public void onPageScrolled(int arg0, float arg1, int arg2) {
        }

        @Override
        public void onPageScrollStateChanged(int arg0) {
        }
    });
}

@Override
public void onTabReselected(Tab tab, FragmentTransaction ft) {
}

@Override
public void onTabSelected(Tab tab, FragmentTransaction ft) {
    // on tab selected
    // show respected fragment view
    viewPager.setCurrentItem(tab.getPosition());
}

@Override
public void onTabUnselected(Tab tab, FragmentTransaction ft) {
}
Sathish Kumar
fuente
55
Considere incluir información sobre su respuesta, en lugar de simplemente publicar el código. Intentamos proporcionar no solo 'soluciones', sino ayudar a las personas a aprender. Debe explicar qué estuvo mal en el código original, qué hizo de manera diferente y por qué sus cambios funcionaron.
Andrew Barber