Cómo obtener fragmentos existentes cuando se utiliza FragmentPagerAdapter

Tengo problemas para hacer mis fragmentos de comunicación entre sí a través de la Activity , que está utilizando el FragmentPagerAdapter , como una clase de ayuda que implementa la gestión de pestañas y todos los detalles de la conexión de un ViewPager con TabHost asociado. He implementado FragmentPagerAdapter tan igual como es proporcionado por el proyecto de ejemplo Android Support4Demos .

La pregunta principal es ¿cómo puedo obtener un fragmento particular de FragmentManager cuando no tengo Id o Tag? FragmentPagerAdapter está creando los fragmentos y generando automáticamente la identificación y las etiquetas.

12 Solutions collect form web for “Cómo obtener fragmentos existentes cuando se utiliza FragmentPagerAdapter”

Resumen del problema

Nota: En esta respuesta voy a referenciar FragmentPagerAdapter y su código fuente. Pero la solución general también debe aplicarse a FragmentStatePagerAdapter .

Si estás leyendo esto probablemente ya sabes que FragmentPagerAdapter / FragmentStatePagerAdapter está destinado a crear Fragments para tu ViewPager , pero después de Recreación de actividad (ya sea de una rotación de dispositivo o el sistema de matar tu aplicación para recuperar la memoria), estos Fragments no se crearán De nuevo, sino sus instancias recuperadas del FragmentManager . Ahora diga que su Activity necesita obtener una referencia a estos Fragments para trabajar en ellos. No tiene un id o tag para estos Fragments creados porque FragmentPagerAdapter establece internamente . Así que el problema es cómo obtener una referencia a ellos sin esa información …

Problema con las soluciones actuales: basándose en el código interno

Muchas de las soluciones que he visto en esta y otras preguntas similares se basan en obtener una referencia al Fragment existente llamando a FragmentManager.findFragmentByTag() y imitando la etiqueta creada internamente: "android:switcher:" + viewId + ":" + id El problema con esto es que usted está confiando en el código fuente interno, que como todos sabemos no está garantizado para permanecer igual para siempre. Los ingenieros de Android de Google podían decidir fácilmente cambiar la estructura de la tag que rompería tu código dejándote incapaz de encontrar una referencia a los Fragments existentes.

Solución alternativa sin depender de la tag interna

Este es un ejemplo sencillo de cómo obtener una referencia a los Fragments devueltos por FragmentPagerAdapter que no se basan en las tags internas establecidas en los Fragments . La clave es anular instantiateItem() y guardar las referencias allí en vez de en getItem() .

 public class SomeActivity extends Activity { private FragmentA m1stFragment; private FragmentB m2ndFragment; // other code in your Activity... private class CustomPagerAdapter extends FragmentPagerAdapter { // other code in your custom FragmentPagerAdapter... public CustomPagerAdapter(FragmentManager fm) { super(fm); } @Override public Fragment getItem(int position) { // Do NOT try to save references to the Fragments in getItem(), // because getItem() is not always called. If the Fragment // was already created then it will be retrieved from the FragmentManger // and not here (ie getItem() won't be called again). switch (position) { case 0: return new FragmentA(); case 1: return new FragmentB(); default: // This should never happen. Always account for each position above return null; } } // Here we can finally safely save a reference to the created // Fragment, no matter where it came from (either getItem() or // FragmentManger). Simply save the returned Fragment from // super.instantiateItem() into an appropriate reference depending // on the ViewPager position. @Override public Object instantiateItem(ViewGroup container, int position) { Fragment createdFragment = (Fragment) super.instantiateItem(container, position); // save the appropriate reference depending on position switch (position) { case 0: m1stFragment = (FragmentA) createdFragment; break; case 1: m2ndFragment = (FragmentB) createdFragment; break; } return createdFragment; } } public void someMethod() { // do work on the referenced Fragments, but first check if they // even exist yet, otherwise you'll get an NPE. if (m1stFragment != null) { // m1stFragment.doWork(); } if (m2ndFragment != null) { // m2ndFragment.doSomeWorkToo(); } } } 

O si prefiere trabajar con tags lugar de las variables de miembro de clase / referencias a los Fragments también puede tomar las tags establecidas por FragmentPagerAdapter de la misma manera: NOTA: esto no se aplica a FragmentStatePagerAdapter ya que no establece tags al crear Sus Fragments .

 @Override public Object instantiateItem(ViewGroup container, int position) { Fragment createdFragment = (Fragment) super.instantiateItem(container, position); // get the tags set by FragmentPagerAdapter switch (position) { case 0: String firstTag = createdFragment.getTag(); break; case 1: String secondTag = createdFragment.getTag(); break; } // ... save the tags somewhere so you can reference them later return createdFragment; } 

Tenga en cuenta que este método no se basan en imitar la tag interna establecida por FragmentPagerAdapter y en su lugar utiliza las API adecuadas para recuperarlas. De esta manera, incluso si la tag cambia en futuras versiones de SupportLibrary , seguirá siendo segura.


No olvides que dependiendo del diseño de tu Activity , los Fragments que estás intentando trabajar pueden o no existir aún, así que debes tener en cuenta esto haciendo chequeos null antes de usar tus referencias.

Además, si en vez de eso trabajas con FragmentStatePagerAdapter , entonces no querrás guardar referencias duras a tus Fragments ya que podrías tener muchas de ellas y las referencias duras las mantendrían innecesariamente en la memoria. En su lugar, guarde las referencias de Fragment en WeakReference variables WeakReference en lugar de las estándar. Me gusta esto:

 WeakReference<Fragment> m1stFragment = new WeakReference<Fragment>(createdFragment); // ...and access them like so Fragment firstFragment = m1stFragment.get(); if (firstFragment != null) { // reference hasn't been cleared yet; do work... } 

He encontrado la respuesta en mi pregunta basada en el poste siguiente: reutilizando fragmentos en un adaptador del fragmentpager

Pocas cosas que he aprendido:

  1. getItem(int position) en el FragmentPagerAdapter es un nombre bastante engañoso de lo que realmente hace este método. Crea nuevos fragmentos, no devuelve los existentes. En este sentido, el método debe cambiarse a algo como createItem(int position) en el SDK de Android. Así que este método no nos ayuda a conseguir fragmentos.
  2. Basado en la explicación en la referencia de soporte FragmentPagerAdapterholds de referencia a fragmentos antiguos debe dejar la creación de los fragmentos en el FragmentPagerAdapter y en tal sentido no tiene ninguna referencia a los Fragmentos o sus etiquetas. Sin embargo, si tiene una etiqueta de fragmento, puede recuperar fácilmente la referencia del FragmentManager llamando a findFragmentByTag() . Necesitamos una manera de encontrar la etiqueta de un fragmento en la posición de la página dada.

Solución

Agregue el siguiente método auxiliar en su clase para recuperar la etiqueta de fragmento y enviarlo al método findFragmentByTag() .

 private String getFragmentTag(int viewPagerId, int fragmentPosition) { return "android:switcher:" + viewPagerId + ":" + fragmentPosition; } 

¡NOTA! Este es el método idéntico que FragmentPagerAdapter utiliza al crear nuevos fragmentos. Consulta este enlace http://code.google.com/p/openintents/source/browse/trunk/compatibility/AndroidSupportV2/src/android/support/v2/app/FragmentPagerAdapter.java#104

He creado este método que está trabajando para mí para obtener una referencia al fragmento actual.

 public static Fragment getCurrentFragment(ViewPager pager, FragmentPagerAdapter adapter) { try { Method m = adapter.getClass().getSuperclass().getDeclaredMethod("makeFragmentName", int.class, long.class); Field f = adapter.getClass().getSuperclass().getDeclaredField("mFragmentManager"); f.setAccessible(true); FragmentManager fm = (FragmentManager) f.get(adapter); m.setAccessible(true); String tag = null; tag = (String) m.invoke(null, pager.getId(), (long) pager.getCurrentItem()); return fm.findFragmentByTag(tag); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } catch (NoSuchFieldException e) { e.printStackTrace(); } return null; } 

La forma en que lo hice es definir un Hashtable de WeakReferences de la siguiente manera:

 protected Hashtable<Integer, WeakReference<Fragment>> fragmentReferences; 

Luego escribí el método getItem () como este:

 @Override public Fragment getItem(int position) { Fragment fragment; switch(position) { case 0: fragment = new MyFirstFragmentClass(); break; default: fragment = new MyOtherFragmentClass(); break; } fragmentReferences.put(position, new WeakReference<Fragment>(fragment)); return fragment; } 

Entonces usted puede escribir un método:

 public Fragment getFragment(int fragmentId) { WeakReference<Fragment> ref = fragmentReferences.get(fragmentId); return ref == null ? null : ref.get(); } 

Esto parece funcionar bien y lo encuentro un poco menos hacky que el

 "android:switcher:" + viewId + ":" + position 

Truco, ya que no depende de cómo se implementa FragmentPagerAdapter. Por supuesto, si el fragmento ha sido liberado por el FragmentPagerAdapter o si aún no se ha creado, getFragment devolverá null.

Si alguien encuentra algo mal con este enfoque, los comentarios son más que bienvenidos.

No es necesario reemplazar instantiateItem ni confiar en la compatibilidad de crear etiquetas de fragmento con makeFragmentName interno. instantiateItem es un método público para que puedas (y de hecho deberías ) llamarlo al método onCreate de tu actividad para obtener (y almacenar en una onCreate local si es necesario) referencias a instancias de tus fragmentos. Sólo recuerde rodear un conjunto de instantiateItem llamadas con startUpdate y finishUpdate métodos como se describe en PagerAdapter javadoc:

Una llamada al método PagerAdapter startUpdate (ViewGroup) indica que el contenido del ViewPager está a punto de cambiar. Una o más llamadas a instantiateItem (ViewGroup, int) y / o destroyItem (ViewGroup, int, Object) seguirán y el final de una actualización será señalado por una llamada a finishUpdate (ViewGroup).

Así, por ejemplo, esta es la forma de almacenar referencias a los fragmentos de pestaña en el método onCreate :

 public class MyActivity extends AppCompatActivity { Fragment0 tab0; Fragment1 tab1; ViewPager viewPager; TabLayout tabLayout; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.myLayout); viewPager = (ViewPager) findViewById(R.id.myViewPager); MyPagerAdapter adapter = new MyPagerAdapter(getSupportFragmentManager()); viewPager.setAdapter(adapter); adapter.startUpdate(viewPager); tab0 = (Fragment0) adapter.instantiateItem(viewPager, 0); tab1 = (Fragment1) adapter.instantiateItem(viewPager, 1); adapter.finishUpdate(viewPager); tabLayout = (TabLayout) findViewById(R.id.tabs); tabLayout.setupWithViewPager(viewPager); } class MyPagerAdapter extends FragmentPagerAdapter { public MyPagerAdapter(FragmentManager manager) {super(manager);} @Override public int getCount() {return 2;} @Override public Fragment getItem(int position) { if (position == 0) return new Fragment0(); if (position == 1) return new Fragment1(); return null; // or throw some exception } @Override public CharSequence getPageTitle(int position) { if (position == 0) return getString(R.string.tab0); if (position == 1) return getString(R.string.tab1); return null; // or throw some exception } } } 

instantiateItem primero tratará de obtener referencias a las instancias de fragmento existentes de FragmentManager y sólo si todavía no existen, creará nuevas (y "almacenará" en el FragmentManager ) utilizando el método getItem de su adaptador.

Algunas informaciones adicionales:
Si no llama a instantiateItem seguido de finishUpdate (y precedido por startUpdate ) en su método onCreate , entonces se arriesga a que sus instancias de fragmento nunca se comprometan a FragmentManager : cuando su actividad se convierte en instantiateItem primer plano se llamará automáticamente para obtener sus fragmentos, Pero start / finishUpdate no puede (dependiendo de los detalles de la implementación) y lo que básicamente hacen es comenzar / comprometer un FragmentTransaction . Esto puede hacer que las referencias a las instancias de fragmentos creados se pierdan muy rápidamente (por ejemplo cuando gira la pantalla) y se recrean con mucha más frecuencia de lo necesario. Dependiendo de cómo "pesado" sus fragmentos son, puede tener consecuencias no despreciables del funcionamiento.

La solución sugerida por @personne3000 es agradable, pero tiene un problema: cuando la actividad va al fondo y se mata por el sistema (para obtener algo de memoria libre) y luego restaurado, el fragmentReferences estará vacío, porque getItem ' T ser llamado.

La clase a continuación maneja tal situación:

 public abstract class AbstractHolderFragmentPagerAdapter<F extends Fragment> extends FragmentPagerAdapter { public static final String FRAGMENT_SAVE_PREFIX = "holder"; private final FragmentManager fragmentManager; // we need to store fragment manager ourselves, because parent's field is private and has no getters. public AbstractHolderFragmentPagerAdapter(FragmentManager fm) { super(fm); fragmentManager = fm; } private SparseArray<WeakReference<F>> holder = new SparseArray<WeakReference<F>>(); protected void holdFragment(F fragment) { holdFragment(holder.size(), fragment); } protected void holdFragment(int position, F fragment) { if (fragment != null) holder.put(position, new WeakReference<F>(fragment)); } public F getHoldedItem(int position) { WeakReference<F> ref = holder.get(position); return ref == null ? null : ref.get(); } public int getHolderCount() { return holder.size(); } @Override public void restoreState(Parcelable state, ClassLoader loader) { // code inspired by Google's FragmentStatePagerAdapter implementation super.restoreState(state, loader); Bundle bundle = (Bundle) state; for (String key : bundle.keySet()) { if (key.startsWith(FRAGMENT_SAVE_PREFIX)) { int index = Integer.parseInt(key.substring(FRAGMENT_SAVE_PREFIX.length())); Fragment f = fragmentManager.getFragment(bundle, key); holdFragment(index, (F) f); } } } @Override public Parcelable saveState() { Bundle state = (Bundle) super.saveState(); if (state == null) state = new Bundle(); for (int i = 0; i < holder.size(); i++) { int id = holder.keyAt(i); final F f = getHoldedItem(i); String key = FRAGMENT_SAVE_PREFIX + id; fragmentManager.putFragment(state, key, f); } return state; } } 

El bloque principal de la carretera con conseguir una manija a los fragmentos es usted no puede confiar en getItem (). Después de un cambio de orientación, las referencias a los fragmentos serán nulas y getItem () no se volverá a llamar.

Este es un enfoque que no se basa en la implementación de FragmentPagerAdapter para obtener la etiqueta. Anula instantiateItem () que devolverá el fragmento creado desde getItem () o encontrado en el gestor de fragmentos.

 @Override public Object instantiateItem(ViewGroup container, int position) { Object value = super.instantiateItem(container, position); if (position == 0) { someFragment = (SomeFragment) value; } else if (position == 1) { anotherFragment = (AnotherFragment) value; } return value; } 

Siempre utilizo esta clase base, cuando necesito acceder a fragmentos secundarios o fragmentos primarios (actualmente visibles). No se basa en ningún detalle de implementación y se encarga de los cambios en el ciclo de vida, ya que los métodos sobrescritos se llaman en ambos casos, cuando se crea una nueva instancia de fragmento y cuando se recibe la instancia de FragmentManager.

 public abstract class FragmentPagerAdapterExt extends FragmentPagerAdapter { private final ArrayList<Fragment> mFragments; private Fragment mPrimaryFragment; public FragmentPagerAdapterExt(FragmentManager fm) { super(fm); mFragments = new ArrayList<>(getCount()); } @Override public Object instantiateItem(ViewGroup container, int position) { Object object = super.instantiateItem(container, position); mFragments.add((Fragment) object); return object; } @Override public void destroyItem(ViewGroup container, int position, Object object) { mFragments.remove(object); super.destroyItem(container, position, object); } @Override public void setPrimaryItem(ViewGroup container, int position, Object object) { super.setPrimaryItem(container, position, object); mPrimaryFragment = (Fragment) object; } /** Returns currently visible (primary) fragment */ public Fragment getPrimaryFragment() { return mPrimaryFragment; } /** Returned list can contain null-values for not created fragments */ public List<Fragment> getFragments() { return Collections.unmodifiableList(mFragments); } } 

Me las arreglé para solucionar este problema usando IDs en lugar de etiquetas. (Estoy usando FragmentStatePagerAdapter que utiliza mis Fragmentos personalizados en los que he anulado el método onAttach, donde guardas el ID en algún lugar:

 @Override public void onAttach(Context context){ super.onAttach(context); MainActivity.fragId = getId(); } 

Y entonces sólo tienes acceso al fragmento fácilmente dentro de la actividad:

 Fragment f = getSupportFragmentManager.findFragmentById(fragId); 

Vea este post sobre devolver fragmentos del FragmentPagerAdapter. ¿Se basa en saber el índice de su fragmento – pero esto se establecería en getItem () (en la instanciación sólo)

No sé si este es el mejor enfoque, pero nada más funcionó para mí. Todas las demás opciones, incluido getActiveFragment, devuelven un valor nulo o provocan que la aplicación se bloquee.

Me di cuenta de que en la rotación de la pantalla del fragmento se adjunta por lo que lo utiliza para enviar el fragmento de nuevo a la actividad.

En el fragmento:

 @Override public void onAttach(Activity activity) { super.onAttach(activity); try { mListener = (OnListInteractionListener) activity; mListener.setListFrag(this); } catch (ClassCastException e) { throw new ClassCastException(activity.toString() + " must implement OnFragmentInteractionListener"); } } 

Luego en la actividad:

 @Override public void setListFrag(MyListFragment lf) { if (mListFragment == null) { mListFragment = lf; } } 

Y finalmente en la actividad onCreate ():

 if (savedInstanceState != null) { if (mListFragment != null) mListFragment.setListItems(items); } 

Este enfoque asigna el fragmento visible real a la actividad sin crear una nueva.

Sólo tienes que probar este código,

 public class MYFragmentPAdp extends FragmentPagerAdapter { public MYFragmentPAdp(FragmentManager fm) { super(fm); } @Override public int getCount() { return 2; } @Override public Fragment getItem(int position) { if (position == 0) Fragment fragment = new Fragment1(); else (position == 1) Fragment fragment = new Fragment2(); return fragment; } } 
  • FragmentPagerAdapter con ViewPager y dos Fragmentos. Ir a la primera de la segunda y actualizar el primer texto
  • Las variables de miembro de fragmento que obtienen null cuando se accede en onPageSelected ()
  • FragmentPagerAdapter notifyDataSetChanged no funciona
  • FragmentPagerAdapter getItem posición incorrecta
  • FragmentPagerAdapter getItem no está siendo activado
  • Fragmentos de Viewpager y de Sherlock. ¿Cómo hacer somethng dentro de fragmento de FragmentActivity?
  • Fragmento en ViewPager devuelve objeto vacío onResume
  • Cómo actualizar dinámicamente el elemento TITLE del visor de vistas
  • Android: limitar los fragmentos de carga con una viewPager
  • Cómo cambiar fragmento de forma programática en FragmentPagerAdapter?
  • ¿Cómo cambiar el color de ActionBar al pasar entre fragmentos (diseño de material)?
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.