Fragmentos dentro de fragmentos

145

Gostaria de saber se isso é realmente um bug na API do Android:

Eu tenho uma configuração assim:

┌----┬---------┐
|    |         |
|  1 |    2    |
|    |┌-------┐|
|    ||       ||
|    ||   3   ||
└----┴┴-------┴┘
  1. É um menu que carrega o fragmento # 2 (uma tela de pesquisa) no painel direito.
  2. É uma tela de pesquisa que contém o fragmento 3, que é uma lista de resultados.
  3. A lista de resultados é usada em vários lugares (inclusive como um fragmento de alto nível em funcionamento).

Essa funcionalidade funciona perfeitamente bem em um telefone (onde 1 e 2 e 3 são ActivityFragment).

No entanto, quando eu usei 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();

Onde R.id.leftPanee R.id.rightPaneestão <fragment>s em um layout linear horizontal.

Entendo que o código acima remove o fragmento que é residente e o substitui por um novo fragmento. Brilhante ... Obviamente, não é isso que acontece, porque quando esse código é executado pela segunda vez, você obtém a seguinte exceção:

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

Isso é causado porque o contêiner de FragmentNumber3 foi duplicado e não possui mais um ID exclusivo. O fragmento inicial não foi destruído (?) Antes da adição do novo (na minha opinião, isso significa que não foi substituído ).

Alguém pode me dizer se isso é possível ( esta resposta sugere que não é) ou é um bug?

Graeme
fonte
1
possível duplicata de Fragmento Dentro Fragmento
rds
6
@ rds esta é uma pergunta antiga, pouco inútil para marcar como duplicada.
pietv8x

Respostas:

203

Fragmentos aninhados não são suportados no momento. Tentar colocar um fragmento na interface do usuário de outro fragmento resultará em comportamento indefinido e provavelmente interrompido.

Atualização : fragmentos aninhados são suportados no Android 4.2 (e Biblioteca de suporte do Android rev 11): http://developer.android.com/about/versions/android-4.2.html#NestedFragments

OBSERVAÇÃO (conforme este documento ): " Observação: você não pode aumentar um layout em um fragmento quando esse layout inclui a <fragment>. Fragmentos aninhados são suportados apenas quando adicionados dinamicamente a um fragmento. "

hackbod
fonte
14
Não é suportado porque não era uma meta de design para a implementação inicial. Ouvi muitas solicitações para o recurso, por isso provavelmente será feito em algum momento, mas, como de costume, há muitas outras coisas competindo com ele em prioridade.
hackbod
4
Eu gerenciei isso estendendo FragmentActivity, FragmentManager e FragmentTransaction. A premissa básica é estender o DeferringFragmentActivity em minhas atividades, fornecendo a mesma API para que nenhum outro código seja alterado. Quando chamo getFragmentManager, recebo uma instância que DeferringFragmentManager e, quando chamo beginTransaction, recebo DeferredTransaction. Essa transação armazena POJOs com o método e argumentos chamados. Quando commit é chamado, procuramos primeiro por DeferredTransactions pendentes. Depois que todas as transações foram confirmadas, iniciamos uma transação real e executamos todos os métodos armazenados com args.
Dskinner 31/05
11
Esse ponto é agora. FragmentAgora, os aninhados fazem parte da API do Android! developer.android.com/about/versions/… .
Alex Lockwood
9
Uau, que pesadelo: se você usa <fragment> em um fragmento, e esse fragmento usa fragmentos filhos, ele não falha com um erro claro ("não é possível adicionar fragmentos filhos a fragmentos de layout") - ele falha misteriosamente com exceções como "fragmento não criou uma visualização". Lá vai várias horas de tempo de depuração ...
Glenn Maynard
6
@ MartínMarconcini com certeza, mas isso não é de todo aparente com base na funcionalidade fornecida pela API. Se algo não for permitido, deve ser claramente documentado, não deixe o desenvolvedor puxar os cabelos, porque algo não está funcionando como você esperaria.
dcow
98

Fragmentos aninhados são suportados no Android 4.2 e posterior

A Biblioteca de suporte do Android também agora suporta fragmentos aninhados , para que você possa implementar designs de fragmentos aninhados no Android 1.6 e superior.

Para aninhar um fragmento, basta chamar getChildFragmentManager () no fragmento no qual você deseja adicionar um fragmento. Isso retorna um FragmentManager que você pode usar como normalmente faz na atividade de nível superior para criar transações de fragmento. Por exemplo, aqui está um código que adiciona um fragmento de dentro de uma classe Fragment existente:

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

Para ter uma idéia mais detalhada sobre fragmentos aninhados, consulte estes tutoriais
Parte 1
Parte 2
Parte 3

e aqui está uma postagem do SO que discute sobre as práticas recomendadas para fragmentos aninhados .

Raneez Ahmed
fonte
principal desvantagem do Nestedfragment é que não podemos chamar optionmenu de childfragment :( se estamos usando ABS
LOG_TAG
Você pode procurar na minha edição? É muito semelhante .. stackoverflow.com/questions/32240138/… . Para mim, o framnet criança não está sendo inflado a partir do código
Nicks
33

.. você pode limpar seu fragmento aninhado no destroyviewmétodo do fragmento pai :

@Override
    public void onDestroyView() {

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

        transaction.remove(nestedFragment);

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

        super.onDestroyView();
    }
furykid
fonte
4
Se você realizar alguns testes de ciclo de vida com SetAlwaysFinish ( bricolsoftconsulting.com/2011/12/23/… ), verá que esse código causa um erro quando outra atividade é iniciada com o término sempre ativado (IllegalStateException: Não é possível executar esta ação depois de onSaveInstanceState). Quebrar o código acima em try / catch não é a solução mais elegante, mas parece fazer tudo funcionar.
Theo
Isso quase funcionou. Mais tarde, recebi um Stackoverflow no desenho da interface do usuário. Definitivamente evitar fragmentos aninhados ...
neteinstein
14

Eu tenho um aplicativo que estou desenvolvendo que é semelhante a Tabs na barra de ação que lança fragmentos, alguns desses fragmentos possuem vários fragmentos incorporados.

Eu estava recebendo o mesmo erro quando tentei executar o aplicativo. Parece que se você instanciar os fragmentos no layout xml após a seleção de uma guia e depois a selecionar novamente, eu receberia o erro do inflador.

Resolvi isso substituindo todos os fragmentos em xml por Linearlayouts e, em seguida, usando uma transação de fragmento / gerenciador de fragmentos para instanciar os fragmentos, tudo parece funcionar corretamente pelo menos em um nível de teste no momento.

Espero que isso ajude você.

draksia
fonte
Alguém pode comentar sobre a eficácia dessa abordagem? Acho lamentável poder usar Fragmentos com apenas um nível de profundidade - é melhor não usá-los. Adicioná-los programaticamente em grupos de exibição de espaço reservado funcionará sem ressalvas?
Rafael Nobre
Ainda parece estar funcionando para mim, troco-os dentro e fora do espectador, sem problemas também. Uma ressalva: só estou fazendo isso no favo de mel, não compatível com o sanduíche de sorvete.
draksia
4

Eu enfrentei o mesmo problema, lutei alguns dias com ele e devo dizer que a maneira mais fácil de superar isso foi encontrar fragment.hide () / fragment.show () quando a guia é selecionada / desmarcada ()

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

Quando ocorre a rotação da tela, todos os fragmentos pai e filho são destruídos corretamente.

Essa abordagem também tem uma vantagem adicional - o uso de hide () / show () não faz com que as exibições de fragmentos percam seu estado; portanto, não há necessidade de restaurar a posição de rolagem anterior para ScrollViews, por exemplo.

O problema é que não sei se é correto não desanexar fragmentos quando eles não estão visíveis. Eu acho que o exemplo oficial do TabListener é projetado com um pensamento em mente de que os fragmentos são reutilizáveis ​​e você não deve poluir com a memória deles. será apropriado mantê-los ligados à atividade atual.

Eu gostaria de ouvir comentários de desenvolvedores mais experientes.

ievgen
fonte
0

Se você encontrar o seu fragmento aninhado não sendo removido ou duplicado (por exemplo, na reinicialização da atividade, na rotação da tela), tente alterar:

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

para

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

Se acima não ajudar, tente:

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

Aprendido aqui

Voy
fonte