Fragments dans les fragments

145

Je me demande s'il s'agit en fait d'un bogue dans l'API Android:

J'ai une configuration comme celle-ci:

┌----┬---------┐
|    |         |
|  1 |    2    |
|    |┌-------┐|
|    ||       ||
|    ||   3   ||
└----┴┴-------┴┘
  1. Est un menu qui charge le fragment # 2 (Un écran de recherche) dans le volet de droite.
  2. Est un écran de recherche qui contient le fragment # 3, qui est une liste de résultats.
  3. La liste de résultats est utilisée à plusieurs endroits (y compris en tant que fragment de haut niveau fonctionnel à part entière).

Cette fonctionnalité fonctionne parfaitement bien sur un téléphone (où 1 & 2 et 3 sont ActivityFragments).

Cependant, lorsque j'ai utilisé ce code:

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

R.id.leftPaneet R.id.rightPanesont <fragment>dans une disposition linéaire horizontale.

Je crois comprendre que le code ci-dessus supprime le fragment qui est résident, puis le remplace par un nouveau fragment. Brillant ... Évidemment, ce n'est pas ce qui se passe car lorsque ce code s'exécute la deuxième fois, vous obtenez l'exception suivante:

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

Cela est dû au fait que le conteneur de FragmentNumber3 a été dupliqué et qu'il n'a plus d'ID unique. Le fragment initial n'a pas été détruit (?) Avant l'ajout du nouveau (dans mon esprit, cela signifie qu'il n'a pas été remplacé ).

Quelqu'un peut-il me dire si c'est possible ( cette réponse suggère que ce n'est pas le cas) ou est-ce un bug?

Graeme
la source
1
duplicata possible de Fragment Inside Fragment
rds
6
@rds c'est une question ancienne, un peu inutile de marquer comme dupliquée.
pietv8x

Réponses:

203

Les fragments imbriqués ne sont actuellement pas pris en charge. Essayer de mettre un fragment dans l'interface utilisateur d'un autre fragment entraînera un comportement non défini et probablement rompu.

Mise à jour : les fragments imbriqués sont pris en charge à partir d'Android 4.2 (et de la bibliothèque de support Android rev 11): http://developer.android.com/about/versions/android-4.2.html#NestedFragments

REMARQUE (selon ce document ): " Remarque: vous ne pouvez pas gonfler une mise en page en un fragment lorsque cette mise en page comprend un <fragment>. Les fragments imbriqués ne sont pris en charge que lorsqu'ils sont ajoutés dynamiquement à un fragment. "

hackbod
la source
14
Non pris en charge car il ne s'agissait pas d'un objectif de conception pour la mise en œuvre initiale. J'ai entendu beaucoup de demandes pour la fonctionnalité, donc cela sera probablement fait à un moment donné, mais comme d'habitude, il y a beaucoup d'autres choses qui la concurrencent en priorité.
hackbod
4
J'ai géré cela en étendant FragmentActivity, FragmentManager et FragmentTransaction. La prémisse de base est d'étendre DeferringFragmentActivity dans mes activités, en fournissant la même API afin qu'aucun autre code ne change. Quand j'appelle getFragmentManager, j'obtiens une instance qui DeferringFragmentManager, et quand j'appelle beginTransaction, j'obtiens un DeferredTransaction. Cette transaction stocke les POJO avec la méthode et les arguments appelés. Lorsque la validation est appelée, nous recherchons d'abord toutes les DeferredTransactions en attente. Une fois que toutes les transactions ont été validées, nous commençons une transaction réelle et exécutons toutes les méthodes stockées avec args.
dskinner
11
Ce point est maintenant. Les imbriqués Fragmentfont désormais partie de l'API Android, oui! developer.android.com/about/versions/… .
Alex Lockwood
9
Wow, quel cauchemar: si vous utilisez <fragment> sur un fragment, et que ce fragment utilise des fragments enfants, cela n'échoue pas avec une erreur claire ("impossible d'ajouter des fragments enfants aux fragments de disposition") - il échoue mystérieusement avec des exceptions comme «le fragment n'a pas créé de vue». Il y a plusieurs heures de débogage ...
Glenn Maynard
6
@ MartínMarconcini bien sûr mais ce n'est pas du tout apparent basé sur la fonctionnalité fournie par l'API. Si quelque chose n'est pas autorisé, il doit être clairement documenté et ne pas laisser au développeur le soin de se tirer les cheveux parce que quelque chose ne fonctionne pas comme vous le souhaiteriez.
dcow
98

Les fragments imbriqués sont pris en charge dans Android 4.2 et versions ultérieures

La bibliothèque de support Android prend désormais également en charge les fragments imbriqués , vous pouvez donc implémenter des conceptions de fragments imbriqués sur Android 1.6 et versions ultérieures.

Pour imbriquer un fragment, appelez simplement getChildFragmentManager () sur le fragment dans lequel vous souhaitez ajouter un fragment. Cela renvoie un FragmentManager que vous pouvez utiliser comme vous le faites normalement à partir de l'activité de niveau supérieur pour créer des transactions de fragment. Par exemple, voici du code qui ajoute un fragment à partir d'une classe Fragment existante:

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

Pour avoir plus d'idées sur les fragments imbriqués, veuillez parcourir ces tutoriels
Partie 1
Partie 2
Partie 3

et voici un article SO qui traite des meilleures pratiques pour les fragments imbriqués .

Raneez Ahmed
la source
Le principal inconvénient de Nestedfragment est que nous ne pouvons pas appeler optionmenu depuis childfragment: (si nous utilisons ABS!
LOG_TAG
Pouvez-vous s'il vous plaît regarder dans mon numéro ?? C'est très similaire .. stackoverflow.com/questions/32240138/… . Pour moi, le framnet enfant ne se gonfle pas du code
Nicks
33

.. vous pouvez nettoyer votre fragment imbriqué dans la destroyviewméthode du fragment parent :

@Override
    public void onDestroyView() {

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

        transaction.remove(nestedFragment);

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

        super.onDestroyView();
    }
furykid
la source
4
Si vous effectuez des tests de cycle de vie avec SetAlwaysFinish ( bricolsoftconsulting.com/2011/12/23/… ), vous verrez que ce code provoque une erreur lorsqu'une autre activité se poursuit avec toujours la fin activée (IllegalStateException: impossible d'effectuer cette action après onSaveInstanceState). Emballer le code ci-dessus dans try / catch n'est pas la solution la plus élégante mais cela semble faire tout fonctionner.
Theo
Cela a presque fonctionné. Plus tard, j'ai eu un Stackoverflow sur l'interface utilisateur de dessin. Évitez
définitivement
14

J'ai une application que je développe qui est présentée de manière similaire avec des onglets dans la barre d'action qui lance des fragments, certains de ces fragments ont plusieurs fragments incorporés en eux.

J'obtenais la même erreur lorsque j'ai essayé d'exécuter l'application. Il semble que si vous instanciez les fragments dans la mise en page XML après qu'un onglet ait été désélectionné puis resélectionné, j'obtiendrais l'erreur inflator.

J'ai résolu ce problème en remplaçant tous les fragments en xml par des Linearlayouts, puis en utilisant un gestionnaire de fragments / une transaction de fragment pour instancier les fragments, tout semble fonctionner correctement au moins au niveau du test pour le moment.

J'espère que cela vous aidera.

Draksia
la source
Quelqu'un peut-il commenter l'efficacité de cette approche? Je trouve malheureux de pouvoir utiliser des fragments à un seul niveau de profondeur - autant ne pas les utiliser du tout alors. Les ajouter par programme à des groupes de vues d'espace réservé fonctionnera sans mise en garde?
Rafael Nobre
Cela semble toujours fonctionner pour moi, je les permute dans et hors de la visionneuse sans problème également. Une mise en garde que je ne fais que sur le nid d'abeille pas avec la compatibilité avec le sandwich à la crème glacée.
draksia
4

J'ai été confronté au même problème, j'ai eu du mal quelques jours avec cela et je dois dire que le moyen le plus simple de le surmonter est d'utiliser fragment.hide () / fragment.show () lorsque l'onglet est sélectionné / désélectionné ().

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

Lorsque la rotation de l'écran se produit, tous les fragments parents et enfants sont correctement détruits.

Cette approche présente également un avantage supplémentaire: l'utilisation de hide () / show () ne fait pas perdre aux vues fragmentées leur état, il n'est donc pas nécessaire de restaurer la position de défilement précédente pour ScrollViews par exemple.

Le problème est que je ne sais pas s'il est correct de ne pas détacher les fragments lorsqu'ils ne sont pas visibles. Je pense que l'exemple officiel de TabListener est conçu en pensant que les fragments sont réutilisables et que vous ne devriez pas polluer avec eux la mémoire, cependant, je pense que si vous n'avez que quelques onglets et que vous savez que les utilisateurs basculeront fréquemment entre eux, il sera approprié pour les garder attachés à l'activité en cours.

J'aimerais entendre les commentaires de développeurs plus expérimentés.

ievgen
la source
0

Si vous constatez que votre fragment imbriqué n'est pas supprimé ou dupliqué (par exemple, lors du redémarrage de l'activité, lors de la rotation de l'écran), essayez de changer:

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

à

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

Si ci-dessus ne vous aide pas, essayez:

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

Appris ici

Voy
la source