¿Cómo puedo mantener el estado del fragmento cuando se agrega a la stack posterior?

He escrito una actividad ficticia que alterna entre dos fragmentos. Cuando pasas de FragmentA a FragmentB, FragmentA se agrega a la stack posterior. Sin embargo, cuando regreso a FragmentA (presionando hacia atrás), se crea un FragmentA totalmente nuevo y se pierde el estado en que se encontraba. Tengo la sensación de que estoy buscando lo mismo que esta pregunta, pero he incluido un ejemplo de código completo para ayudar a eliminar el problema:

public class FooActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); final FragmentTransaction transaction = getFragmentManager().beginTransaction(); transaction.replace(android.R.id.content, new FragmentA()); transaction.commit(); } public void nextFragment() { final FragmentTransaction transaction = getFragmentManager().beginTransaction(); transaction.replace(android.R.id.content, new FragmentB()); transaction.addToBackStack(null); transaction.commit(); } public static class FragmentA extends Fragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { final View main = inflater.inflate(R.layout.main, container, false); main.findViewById(R.id.next_fragment_button).setOnClickListener(new View.OnClickListener() { public void onClick(View v) { ((FooActivity) getActivity()).nextFragment(); } }); return main; } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); // Save some state! } } public static class FragmentB extends Fragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { return inflater.inflate(R.layout.b, container, false); } } } 

Con algunos mensajes de registro agregados:

 07-05 14:28:59.722 D/OMG ( 1260): FooActivity.onCreate 07-05 14:28:59.742 D/OMG ( 1260): FragmentA.onCreateView 07-05 14:28:59.742 D/OMG ( 1260): FooActivity.onResume  07-05 14:29:12.842 D/OMG ( 1260): FooActivity.nextFragment 07-05 14:29:12.852 D/OMG ( 1260): FragmentB.onCreateView  07-05 14:29:16.792 D/OMG ( 1260): FragmentA.onCreateView 

Nunca llama a FragmentA.onSaveInstanceState y crea un nuevo FragmentA cuando respondes. Sin embargo, si estoy en FragmentA y locking la pantalla, se llama a FragmentA.onSaveInstanceState. Tan extraño … ¿me equivoco al esperar que un fragmento agregado a la stack de respaldo no necesite recreación? Esto es lo que dicen los documentos :

Mientras que, si llama a addToBackStack () al eliminar un fragmento, entonces el fragmento se detiene y se reanudará si el usuario navega hacia atrás.

Si regresa a un fragmento de la stack posterior, no vuelve a crear el fragmento pero vuelve a utilizar la misma instancia y comienza con onCreateView() en el ciclo de vida del fragmento, consulte Fragmento del ciclo de vida .

Por lo tanto, si desea almacenar el estado, debe usar variables de instancia y no confiar en onSaveInstanceState() .

Comparando con el UINavigationController de Apple y UIViewController , Google no funciona bien en la architecture de software de Android. Y el documento de Android sobre Fragment no ayuda mucho.

Cuando ingresa FragmentB desde FragmentA, la instancia FragmentA existente no se destruye. Cuando presiona Atrás en FragmentB y regresa a FragmentA, no creamos una nueva instancia de FragmentA. Se onCreateView() la instancia de FragmentA existente onCreateView() .

La clave es que no deberíamos inflar de nuevo la vista en onCreateView() FragmentA, porque estamos usando la instancia existente de FragmentA. Necesitamos guardar y reutilizar el rootView.

El siguiente código funciona bien. No solo mantiene el estado del fragmento, sino que también reduce la carga de la RAM y la CPU (porque solo inflemos el diseño si es necesario). No puedo creer que el código de muestra y el documento de Google nunca lo mencionen, pero siempre inflan el diseño .

Versión 1 (No use la versión 1. Use la versión 2)

 public class FragmentA extends Fragment { View _rootView; public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { if (_rootView == null) { // Inflate the layout for this fragment _rootView = inflater.inflate(R.layout.fragment_a, container, false); // Find and setup subviews _listView = (ListView)_rootView.findViewById(R.id.listView); ... } else { // Do not inflate the layout again. // The returned View of onCreateView will be added into the fragment. // However it is not allowed to be added twice even if the parent is same. // So we must remove _rootView from the existing parent view group // (it will be added back). ((ViewGroup)_rootView.getParent()).removeView(_rootView); } return _rootView; } } 

—— Actualización el 3 de mayo de 2005: ——-

Como mencionan los comentarios, a veces _rootView.getParent() es nulo en onCreateView , lo que provoca el locking. La versión 2 elimina _rootView en onDestroyView (), como se sugirió dell116. Probado en Android 4.0.3, 4.4.4, 5.1.0.

Versión 2

 public class FragmentA extends Fragment { View _rootView; public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { if (_rootView == null) { // Inflate the layout for this fragment _rootView = inflater.inflate(R.layout.fragment_a, container, false); // Find and setup subviews _listView = (ListView)_rootView.findViewById(R.id.listView); ... } else { // Do not inflate the layout again. // The returned View of onCreateView will be added into the fragment. // However it is not allowed to be added twice even if the parent is same. // So we must remove _rootView from the existing parent view group // in onDestroyView() (it will be added back). } return _rootView; } @Override public void onDestroyView() { if (_rootView.getParent() != null) { ((ViewGroup)_rootView.getParent()).removeView(_rootView); } super.onDestroyView(); } } 

¡¡¡ADVERTENCIA!!!

Este es un HACK! Aunque lo estoy usando en mi aplicación, debe probar y leer los comentarios con cuidado.

Supongo que hay una forma alternativa de lograr lo que estás buscando. No digo que sea una solución completa, pero cumplió el propósito en mi caso.

Lo que hice fue reemplazar el fragmento en el fragmento de objective que acababa de agregar. Entonces, básicamente, usarás el método add() lugar de replace() .

¿Qué más hice? Escondo mi fragmento actual y también lo agrego al backstack.

Por lo tanto, superpone un nuevo fragmento sobre el fragmento actual sin destruir su vista (compruebe que su método onDestroyView() no se está llamando. Además, agregarlo a backstate me da la ventaja de reanudar el fragmento.

Aquí está el código:

 Fragment fragment=new DestinationFragment(); FragmentManager fragmentManager = getFragmentManager(); android.app.FragmentTransaction ft=fragmentManager.beginTransaction(); ft.add(R.id.content_frame, fragment); ft.hide(SourceFragment.this); ft.addToBackStack(SourceFragment.class.getName()); ft.commit(); 

El sistema AFAIK solo llama onCreateView() si la vista se destruye o no se crea. Pero aquí hemos guardado la vista al no eliminarla de la memoria. Por lo tanto, no creará una nueva vista.

Y cuando regrese de Destination Fragment, aparecerá el último FragmentTransaction eliminará el fragmento superior que hará que la vista superior (SourceFragment) aparezca sobre la pantalla.

COMENTARIO: Como he dicho, no es una solución completa, ya que no elimina la vista del fragmento Fuente y, por lo tanto, ocupa más memoria de la habitual. Pero aún sirve al propósito. También estamos utilizando un mecanismo totalmente diferente para ocultar la vista en lugar de reemplazar lo que no es tradicional

Por lo tanto, no se trata de cómo se mantiene el estado, sino de cómo se mantiene la vista.

Encontré este problema en un Fragmento que contiene un mapa, que tiene demasiados detalles de configuración para guardar / recargar. Mi solución fue básicamente mantener este Fragmento activo todo el tiempo (similar a lo que @kaushal mencionó).

Digamos que tiene el Fragmento A actual y quiere mostrar el Fragmento B. Resumiendo las consecuencias:

  • replace () – elimina el Fragmento A y sustitúyelo por el Fragmento B. El Fragmento A se volverá a crear una vez que se vuelva a colocar en el frente.
  • add () – (crear y) agregar un Fragmento B y superponer el Fragmento A, que aún está activo en el fondo
  • remove () – se puede usar para eliminar el Fragmento B y volver a A. El Fragmento B se volverá a crear cuando se llame más adelante.

Por lo tanto, si desea mantener ambos fragmentos “guardados”, simplemente alternarlos usando hide () / show ().

Pros : método fácil y simple para mantener múltiples Fragmentos corriendo
Contras : usa mucha más memoria para mantener todos funcionando. Puede tener problemas, por ejemplo, mostrar muchos mapas de bits de gran tamaño

onSaveInstanceState() solo se onSaveInstanceState() si hay un cambio de configuración.

Desde el cambio de un fragmento a otro no hay cambio de configuración, por lo que no hay ninguna llamada a onSaveInstanceState() allí. ¿Qué estado no está siendo guardado? ¿Puedes especificar?

Si ingresas texto en EditText, se guardará automáticamente. Cualquier elemento de UI sin ID es el elemento cuyo estado de vista no se guardará.

Aquí, ya que onSaveInstanceState en fragmento no llama cuando agrega fragmentos en backstack. El ciclo de vida del fragmento en backstack cuando se restaura comienza en onCreateView y termina en onDestroyView mientras que onSaveInstanceState se llama entre onDestroyView y onDestroy . Mi solución es crear variable de instancia e init en onCreate . Código de muestra:

 private boolean isDataLoading = true; private ArrayList listData; public void onCreate(Bundle savedInstanceState){ super.onCreate(savedInstanceState); isDataLoading = false; // init list at once when create fragment listData = new ArrayList(); } 

Y compruébalo onActivityCreated :

 public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); if(isDataLoading){ fetchData(); }else{ //get saved instance variable listData() } } private void fetchData(){ // do fetch data into listData } 
 getSupportFragmentManager().addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() { @Override public void onBackStackChanged() { if (getSupportFragmentManager().getBackStackEntryCount() == 0) { //setToolbarTitle("Main Activity"); } else { Log.e("fragment_replace11111", "replace"); } } }); YourActivity.java @Override public void onBackPressed() { Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.Fragment_content); if (fragment instanceof YourFragmentName) { fragmentReplace(new HomeFragment(),"Home Fragment"); txt_toolbar_title.setText("Your Fragment"); } else{ super.onBackPressed(); } } public void fragmentReplace(Fragment fragment, String fragment_name) { try { fragmentTransaction = fragmentManager.beginTransaction(); fragmentTransaction.replace(R.id.Fragment_content, fragment, fragment_name); fragmentTransaction.setCustomAnimations(R.anim.enter_from_right, R.anim.exit_to_left, R.anim.enter_from_left, R.anim.exit_to_right); fragmentTransaction.addToBackStack(fragment_name); fragmentTransaction.commitAllowingStateLoss(); } catch (Exception e) { e.printStackTrace(); } } 

Mi problema fue similar, pero me superé sin mantener el fragmento vivo. Supongamos que tiene una actividad que tiene 2 fragmentos: F1 y F2. F1 se inicia inicialmente y digamos que contiene cierta información de usuario y luego, con alguna condición, F2 aparece al pedirle al usuario que complete un atributo adicional : su número de teléfono. A continuación, quiere que ese número de teléfono vuelva a F1 y complete el registro, pero se da cuenta de que se perdió toda la información del usuario anterior y no tiene sus datos anteriores. El fragmento se recrea desde cero e incluso si guardó esta información en onSaveInstanceState el paquete vuelve a cero en onActivityCreated .

Solución: guarde la información requerida como una variable de instancia en la actividad de llamadas. Luego pasa esa variable de instancia a tu fragmento.

 @Override public void onActivityCreated(@Nullable Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); Bundle args = getArguments(); // this will be null the first time F1 is created. // it will be populated once you replace fragment and provide bundle data if (args != null) { if (args.get("your_info") != null) { // do what you want with restred information } } } 

Entonces, siguiendo con mi ejemplo: antes de mostrar F2, guardo datos de usuario en la variable de instancia usando una callback. Luego, inicio F2, el usuario completa el número de teléfono y presiona guardar. Utilizo otra callback en actividad, recopilo esta información y reemplazo mi fragmento F1, esta vez tiene datos agrupados que puedo usar.

 @Override public void onPhoneAdded(String phone) { //replace fragment F1 f1 = new F1 (); Bundle args = new Bundle(); yourInfo.setPhone(phone); args.putSerializable("you_info", yourInfo); f1.setArguments(args); getFragmentManager().beginTransaction() .replace(R.id.fragmentContainer, f1).addToBackStack(null).commit(); } } 

Puede encontrar más información sobre las devoluciones de llamada aquí: https://developer.android.com/training/basics/fragments/communicating.html

primero : simplemente use el método add en lugar de reemplazar el método de la clase FragmentTransaction, luego debe agregar secondFragment para astackr mediante el método addToBackStack

segundo : al hacer clic atrás tienes que llamar a popBackStackImmediate ()

 Fragment sourceFragment = new SourceFragment (); final Fragment secondFragment = new SecondFragment(); final FragmentTransaction ft = getChildFragmentManager().beginTransaction(); ft.add(R.id.child_fragment_container, secondFragment ); ft.hide(sourceFragment ); ft.addToBackStack(NewsShow.class.getName()); ft.commit(); ((SecondFragment)secondFragment).backFragmentInstanceClick = new SecondFragment.backFragmentNewsResult() { @Override public void backFragmentNewsResult() { getChildFragmentManager().popBackStackImmediate(); } }; 

Reemplace un Fragmento usando el siguiente código:

 Fragment fragment = new AddPaymentFragment(); getSupportFragmentManager().beginTransaction().replace(R.id.frame, fragment, "Tag_AddPayment") .addToBackStack("Tag_AddPayment") .commit(); 

La actividad onBackPressed () es:

  @Override public void onBackPressed() { android.support.v4.app.FragmentManager fm = getSupportFragmentManager(); if (fm.getBackStackEntryCount() > 1) { fm.popBackStack(); } else { finish(); } Log.e("popping BACKSTRACK===> ",""+fm.getBackStackEntryCount()); } 
 Public void replaceFragment(Fragment mFragment, int id, String tag, boolean addToStack) { FragmentTransaction mTransaction = getSupportFragmentManager().beginTransaction(); mTransaction.replace(id, mFragment); hideKeyboard(); if (addToStack) { mTransaction.addToBackStack(tag); } mTransaction.commitAllowingStateLoss(); } replaceFragment(new Splash_Fragment(), R.id.container, null, false); 

Solución perfecta que encuentra un fragmento antiguo en la stack y lo carga si existe en la stack.

 /** * replace or add fragment to the container * * @param fragment pass android.support.v4.app.Fragment * @param bundle pass your extra bundle if any * @param popBackStack if true it will clear back stack * @param findInStack if true it will load old fragment if found */ public void replaceFragment(Fragment fragment, @Nullable Bundle bundle, boolean popBackStack, boolean findInStack) { FragmentManager fm = getSupportFragmentManager(); FragmentTransaction ft = fm.beginTransaction(); String tag = fragment.getClass().getName(); Fragment parentFragment; if (findInStack && fm.findFragmentByTag(tag) != null) { parentFragment = fm.findFragmentByTag(tag); } else { parentFragment = fragment; } // if user passes the @bundle in not null, then can be added to the fragment if (bundle != null) parentFragment.setArguments(bundle); else parentFragment.setArguments(null); // this is for the very first fragment not to be added into the back stack. if (popBackStack) { fm.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE); } else { ft.addToBackStack(parentFragment.getClass().getName() + ""); } ft.replace(R.id.contenedor_principal, parentFragment, tag); ft.commit(); fm.executePendingTransactions(); } 

usarlo como

 Fragment f = new YourFragment(); replaceFragment(f, null, boolean true, true);