Fragmentos dentro de Fragmentos

145

Me pregunto si esto es realmente un error en la API de Android:

Tengo una configuración así:

┌----┬---------┐
|    |         |
|  1 |    2    |
|    |┌-------┐|
|    ||       ||
|    ||   3   ||
└----┴┴-------┴┘
  1. Es un menú que carga el fragmento n. ° 2 (Una pantalla de búsqueda) en el panel derecho.
  2. Es una pantalla de búsqueda que contiene el fragmento n. ° 3, que es una lista de resultados.
  3. La lista de resultados se usa en varios lugares (incluso como un fragmento funcional de alto nivel por derecho propio).

Esta funcionalidad funciona perfectamente en un teléfono (donde 1 y 2 y 3 son ActivityFragments).

Sin embargo, cuando usé este código:

    FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();       
    Fragment frag = new FragmentNumber2();
    if(toLoad != null) frag.setArguments(toLoad);
    transaction.replace(R.id.rightPane, frag);      
    transaction.commit();

Donde R.id.leftPaney R.id.rightPaneestán <fragment>s en un diseño lineal horizontal.

Tengo entendido que el código anterior elimina el fragmento que es residente y luego lo reemplaza con un nuevo fragmento. Brillante ... Obviamente, eso no es lo que sucede porque cuando este código se ejecuta la segunda vez, obtienes la siguiente excepción:

07-27 15:22:55.940: ERROR/AndroidRuntime(8105): Caused by: java.lang.IllegalArgumentException: Binary XML file line #57: Duplicate id 0x7f080024, tag null, or parent id 0x0 with another fragment for FragmentNumber3

Esto se debe a que el contenedor para FragmentNumber3 se ha duplicado y ya no tiene una ID única. El Fragmento inicial no ha sido destruido (?) Antes de que se agregue el nuevo (en mi opinión, eso significa que no ha sido reemplazado ).

¿Alguien puede decirme si esto es posible ( esta respuesta sugiere que no lo es) o es un error?

Graeme
fuente
1
posible duplicado de Fragment Inside Fragment
rds
66
@rds esta es una pregunta antigua, poco útil para marcar como duplicado.
pietv8x

Respuestas:

203

Los fragmentos anidados no son compatibles actualmente. Intentar colocar un fragmento dentro de la interfaz de usuario de otro fragmento dará como resultado un comportamiento indefinido y probablemente roto.

Actualización : los fragmentos anidados son compatibles con Android 4.2 (y Android Support Library rev 11): http://developer.android.com/about/versions/android-4.2.html#NestedFragments

NOTA (según esta documentación ): " Nota: No puede inflar un diseño en un fragmento cuando ese diseño incluye un <fragment>. Los fragmentos anidados solo son compatibles cuando se agregan dinámicamente a un fragmento " .

hackbod
fuente
14
No se admite porque no era un objetivo de diseño para la implementación inicial. He escuchado muchas solicitudes para la función, por lo que probablemente se hará en algún momento, pero como de costumbre, hay muchas otras cosas que compiten con ella de manera prioritaria.
Hackbod
44
Logré esto extendiendo FragmentActivity, FragmentManager y FragmentTransaction. La premisa básica es extender DeferringFragmentActivity en mis actividades, proporcionando la misma API para que no haya otros cambios en el código. Cuando llamo a getFragmentManager, obtengo una instancia que DeferringFragmentManager, y cuando llamo a beginTransaction, obtengo una DeferredTransaction. Esta transacción almacena POJO con el método y los argumentos llamados. Cuando commit es call, buscamos primero cualquier DeferredTransactions pendiente. Una vez que se han confirmado todas las transacciones, comenzamos una transacción real y ejecutamos todos los métodos almacenados con argumentos.
dskinner
11
Ese punto es ahora. Los anidados Fragmentahora son parte de la API de Android, ¡sí! developer.android.com/about/versions/… .
Alex Lockwood el
9
Wow, qué pesadilla: si usa <fragment> en un Fragment, y ese Fragment utiliza fragmentos secundarios, no falla con un error claro ("no se pueden agregar fragmentos secundarios a los fragmentos de diseño") - falla misteriosamente con excepciones como "el fragmento no creó una vista". Hay varias horas de depuración ...
Glenn Maynard
66
@ MartínMarconcini seguro, pero eso no es del todo aparente en función de la funcionalidad proporcionada por la API. Si algo no está permitido, debe estar claramente documentado, no dejar que el desarrollador se quite el pelo porque algo no está funcionando de la manera esperada.
dcow
98

Los fragmentos anidados son compatibles con Android 4.2 y versiones posteriores

La Biblioteca de soporte de Android ahora también admite fragmentos anidados , por lo que puede implementar diseños de fragmentos anidados en Android 1.6 y superior.

Para anidar un fragmento, simplemente llame a getChildFragmentManager () en el Fragmento en el que desea agregar un fragmento. Esto devuelve un FragmentManager que puede usar como lo hace normalmente desde la actividad de nivel superior para crear transacciones fragmentarias. Por ejemplo, aquí hay un código que agrega un fragmento dentro de una clase Fragment existente:

Fragment videoFragment = new VideoPlayerFragment();
FragmentTransaction transaction = getChildFragmentManager().beginTransaction();
transaction.add(R.id.video_fragment, videoFragment).commit();

Para tener más idea sobre los fragmentos anidados, consulte estos tutoriales
Parte 1
Parte 2
Parte 3

y aquí hay una publicación SO que discute sobre las mejores prácticas para fragmentos anidados .

Raneez Ahmed
fuente
La desventaja principal de Nestedfragment es que no podemos llamar a optionmenu desde childfragment :( si estamos usando ABS!
LOG_TAG
¿Puedes mirar mi problema? Es muy similar ... stackoverflow.com/questions/32240138/… . Para mí, el framnet infantil no se está inflando del código
Nicks
33

.. puede limpiar su fragmento anidado en el destroyviewmétodo del fragmento principal :

@Override
    public void onDestroyView() {

      try{
        FragmentTransaction transaction = getSupportFragmentManager()
                .beginTransaction();

        transaction.remove(nestedFragment);

        transaction.commit();
      }catch(Exception e){
      }

        super.onDestroyView();
    }
furia
fuente
44
Si realiza algunas pruebas de ciclo de vida con SetAlwaysFinish ( bricolsoftconsulting.com/2011/12/23/… ), verá que este código causa un error cuando otra actividad se pone en la parte superior con siempre finalizado habilitado (IllegalStateException: no se puede realizar esta acción después de onSaveInstanceState). Ajustar el código anterior en try / catch no es la solución más elegante, pero parece hacer que todo funcione.
Theo
Esto casi funcionó. Más tarde obtuve un Stackoverflow en el dibujo de la interfaz de usuario. Definitivamente evite fragmentos anidados ...
neteinstein
14

Tengo una aplicación que estoy desarrollando que se presenta de forma similar a las pestañas en la barra de acción que lanza fragmentos, algunos de estos fragmentos tienen múltiples fragmentos incrustados dentro de ellos.

Recibía el mismo error cuando intenté ejecutar la aplicación. Parece que si crea una instancia de los Fragmentos dentro del diseño xml después de que una pestaña no se haya seleccionado y luego se vuelva a seleccionar, obtendría el error del inflador.

Resolví esto reemplazando todos los fragmentos en xml con Linearlayouts y luego usando un administrador de fragmentos / transacción de fragmentos para instanciar los fragmentos, todo parece funcionar correctamente al menos en un nivel de prueba en este momento.

Espero que esto te ayude.

draksia
fuente
¿Alguien puede comentar sobre la efectividad de este enfoque? Me parece desafortunado poder usar Fragmentos de solo un nivel de profundidad; es mejor que no los use en ese momento. ¿Agregarlos mediante programación en los grupos de vista de marcador de posición funcionará sin advertencias?
Rafael Nobre
Todavía parece estar funcionando para mí, los cambio dentro y fuera del visor sin ningún problema también. Una advertencia: solo estoy haciendo esto en nido de abeja, no con compatibilidad con Ice cream sandwich.
draksia 01 de
4

Me he enfrentado con el mismo problema, he luchado un par de días con él y debería decir que la forma más fácil de superar que encontré esto es usar fragment.hide () / fragment.show () cuando la pestaña está seleccionada / no seleccionada ()

public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction ft)
{
    if (mFragment != null)
        ft.hide(mFragment);
}

Cuando se produce la rotación de la pantalla, todos los fragmentos primarios y secundarios se destruyen correctamente.

Este enfoque también tiene una ventaja adicional: el uso de hide () / show () no hace que las vistas de fragmentos pierdan su estado, por lo que no es necesario restaurar la posición de desplazamiento anterior para ScrollViews, por ejemplo.

El problema es que no sé si es correcto no separar fragmentos cuando no son visibles. Creo que el ejemplo oficial de TabListener está diseñado pensando que los fragmentos son reutilizables y que no debes contaminarlos con la memoria, sin embargo, creo que si solo tienes unas pocas pestañas y sabes que los usuarios cambiarán entre ellas con frecuencia. será apropiado mantenerlos unidos a la actividad actual.

Me gustaría escuchar comentarios de desarrolladores más experimentados.

ievgen
fuente
0

Si encuentra que su fragmento anidado no se elimina o duplica (por ejemplo, en el reinicio de la actividad, en la rotación de la pantalla) intente cambiar:

transaction.add(R.id.placeholder, newFragment);

a

transaction.replace(R.id.placeholder, newFragment);

Si lo anterior no ayuda, intente:

Fragment f = getChildFragmentManager().findFragmentById(R.id.placeholder);

FragmentTransaction transaction = getChildFragmentManager().beginTransaction();

if (f == null) {
    Log.d(TAG, "onCreateView: fragment doesn't exist");
    newFragment= new MyFragmentType();
    transaction.add(R.id.placeholder, newFragment);
} else {
    Log.d(TAG, "onCreateView: fragment already exists");
    transaction.replace(R.id.placeholder, f);
}
transaction.commit();

Aprendí aquí

Voy
fuente