FragmentManager
é
a classe responsável por executar ações nos fragmentos do app, por exemplo,
adicionar, remover ou substituir e adicioná-los à backstack.
Talvez você nunca interaja com o FragmentManager
de forma direta, se estiver usando
a biblioteca Jetpack Navigation, já que ela trabalha com
FragmentManager
em seu nome. No entanto, qualquer app que use fragmentos está
usando o FragmentManager
em algum nível. Então é importante entender o que
ele é e como funciona.
Esta página abrange os seguintes tópicos:
- Como acessar o
FragmentManager
. - O papel do
FragmentManager
em relação a suas atividades e seus fragmentos. - Como gerenciar a backstack com o
FragmentManager
. - Como fornecer dados e dependências aos fragmentos.
Acessar o FragmentManager
Você pode acessar o FragmentManager
usando uma atividade ou um fragmento.
FragmentActivity
e as subclasses dela, por exemplo,
AppCompatActivity
,
têm acesso ao FragmentManager
pelo
método
getSupportFragmentManager()
.
Os fragmentos podem hospedar um ou mais fragmentos filhos. Dentro de
um fragmento, é possível conseguir uma referência ao FragmentManager
que gerencia
os filhos do fragmento pelo
getChildFragmentManager()
.
Se você precisar acessar o host FragmentManager
, use
getParentFragmentManager()
.
Confira alguns exemplos das relações entre
fragmentos, os hosts deles e as instâncias do FragmentManager
associadas a
cada um.
A Figura 1 mostra dois exemplos, cada um deles com um único host de atividade. A
atividade do host em ambos os exemplos mostra uma navegação de nível superior para
o usuário como uma
BottomNavigationView
responsável por trocar o fragmento de host com diferentes telas
no app. Cada tela é implementada como um fragmento separado.
O fragmento de host no Exemplo 1 hospeda dois fragmentos filhos que compõem uma tela de visualização dividida. O fragmento de host no Exemplo 2 hospeda um único fragmento filho que compõe o fragmento de exibição de uma visualização deslizável.
Considerando essa configuração, é possível pensar em cada host como tendo um FragmentManager
associado a ele gerenciando os fragmentos filhos. Isso é ilustrado na
Figura 2, bem como os mapeamentos de propriedades entre supportFragmentManager
,
parentFragmentManager
e childFragmentManager
.
A propriedade FragmentManager
adequada para referência depende de onde está
o local de chamadas na hierarquia de fragmentos e de qual gerenciador de fragmentos
você está tentando usar para o acesso.
Quando você tiver uma referência ao FragmentManager
, poderá usá-la para
manipular os fragmentos exibidos ao usuário.
Fragmentos filhos
De modo geral, seu app consiste em um número pequeno ou único
de atividades no projeto do aplicativo, em que cada uma representa
um grupo de telas relacionadas. A atividade pode fornecer um ponto para posicionar
a navegação de nível superior e um local para o escopo de objetos ViewModel
e outro estado de visualização
entre fragmentos. Um fragmento representa um destino individual no
app.
Para mostrar vários fragmentos de uma só vez, por exemplo, em uma visualização dividida ou um painel, use fragmentos filhos gerenciados pelo fragmento de destino e pelo gerenciador de fragmentos filhos.
Outros casos de uso para fragmentos filhos são os seguintes:
- Deslizes de tela,
usando um
ViewPager2
em um fragmento pai para gerenciar uma série de visualizações do fragmento filho. - Subnavegação em um conjunto de telas relacionadas.
- O Jetpack Navigation usa fragmentos filhos como destinos individuais. Uma
atividade hospeda um único
NavHostFragment
pai e preenche o espaço com diferentes fragmentos de destino filhos à medida que os usuários navegam pelo app.
Usar o FragmentManager
O FragmentManager
gerencia a backstack de fragmentos. No ambiente de execução, o
FragmentManager
pode executar operações de backstack, como a adição ou remoção de
fragmentos em resposta a interações do usuário. Cada conjunto de mudanças é
combinado como uma única unidade, chamada
FragmentTransaction
.
Confira uma discussão mais aprofundada sobre transações de fragmentos no
guia de transações de fragmentos.
Quando o usuário toca no botão "Voltar" no dispositivo ou quando você chama
FragmentManager.popBackStack()
,
a transação de fragmento na camada superior é removida da backstack. Se não há mais transações de
fragmentos na backstack e se você não está usando fragmentos filhos, o evento de
retorno surge na atividade. Se você está usando fragmentos filhos, consulte as
considerações especiais para fragmentos filhos e irmãos.
Quando você chama
addToBackStack()
em uma transação, ela pode incluir qualquer número de
operações, por exemplo, a adição de vários fragmentos ou a substituição de fragmentos em vários
contêineres.
Quando a backstack é encerrada, todas essas
operações são revertidas a uma única ação atômica. No entanto, se você tiver confirmado
outras transações antes da chamada de popBackStack()
e se
não tiver usado addToBackStack()
na transação, essas operações
não serão revertidas. Portanto, em uma única FragmentTransaction
, evite
intercalar transações que afetam a backstack com transações que não fazem isso.
Realizar uma transação
Para exibir um fragmento em um contêiner de layout, use o FragmentManager
para criar uma FragmentTransaction
. Dentro da transação, é possível
realizar uma operação
add()
ou replace()
no contêiner.
Por exemplo, uma FragmentTransaction
simples pode ter esta aparência:
Kotlin
supportFragmentManager.commit { replace<ExampleFragment>(R.id.fragment_container) setReorderingAllowed(true) addToBackStack("name") // Name can be null }
Java
FragmentManager fragmentManager = getSupportFragmentManager(); fragmentManager.beginTransaction() .replace(R.id.fragment_container, ExampleFragment.class, null) .setReorderingAllowed(true) .addToBackStack("name") // Name can be null .commit();
Nesse exemplo, ExampleFragment
substitui o fragmento, se houver, que está
no contêiner de layout identificado pelo
ID R.id.fragment_container
. Fornecer a classe do fragmento ao
método replace()
permite que o FragmentManager
processe a instanciação usando
FragmentFactory
.
Para saber mais, consulte a seção Fornecer dependências para seus
fragmentos.
setReorderingAllowed(true)
otimiza as mudanças de estado dos fragmentos envolvidos na transação
para que as animações e transições funcionem corretamente. Para ver mais informações sobre
como navegar com animações e transições, consulte
Transações de fragmentos e
Navegar entre fragmentos usando animações.
Chamar
addToBackStack()
confirma a transação na backstack. Posteriormente, o usuário poderá reverter a
transação e recuperar o fragmento anterior pressionando o botão
"Voltar". Se você adicionou ou removeu vários fragmentos em uma única
transação, todas essas operações serão desfeitas quando a backstack
for retirada. O nome opcional fornecido na chamada addToBackStack()
permite
retornar para uma transação específica usando
popBackStack()
.
Se você não chamar addToBackStack()
ao realizar uma transação que
remove um fragmento, ele será destruído quando a
transação for confirmada, e o usuário não poderá navegar de volta a ele. Se você
chamar addToBackStack()
ao remover um fragmento, ele será
apenas STOPPED
e, mais tarde, será RESUMED
quando o usuário navegar de volta. Nesse caso,
a visualização será destruída. Para mais informações, consulte
Ciclo de vida do fragmento.
Encontrar um fragmento já existente
Você pode conseguir uma referência ao fragmento atual em um contêiner de layout
usando
findFragmentById()
.
Use findFragmentById()
para procurar um fragmento pelo ID fornecido quando
inflado a partir do XML ou pelo ID do contêiner quando adicionado em uma
FragmentTransaction
. Veja um exemplo:
Kotlin
supportFragmentManager.commit { replace<ExampleFragment>(R.id.fragment_container) setReorderingAllowed(true) addToBackStack(null) } ... val fragment: ExampleFragment = supportFragmentManager.findFragmentById(R.id.fragment_container) as ExampleFragment
Java
FragmentManager fragmentManager = getSupportFragmentManager(); fragmentManager.beginTransaction() .replace(R.id.fragment_container, ExampleFragment.class, null) .setReorderingAllowed(true) .addToBackStack(null) .commit(); ... ExampleFragment fragment = (ExampleFragment) fragmentManager.findFragmentById(R.id.fragment_container);
Como alternativa, você pode atribuir uma tag exclusiva a um fragmento e conseguir uma
referência usando
findFragmentByTag()
.
É possível transferir uma tag usando o atributo XML android:tag
em fragmentos que
são definidos no seu layout ou durante uma operação de add()
ou replace()
em uma FragmentTransaction
.
Kotlin
supportFragmentManager.commit { replace<ExampleFragment>(R.id.fragment_container, "tag") setReorderingAllowed(true) addToBackStack(null) } ... val fragment: ExampleFragment = supportFragmentManager.findFragmentByTag("tag") as ExampleFragment
Java
FragmentManager fragmentManager = getSupportFragmentManager(); fragmentManager.beginTransaction() .replace(R.id.fragment_container, ExampleFragment.class, null, "tag") .setReorderingAllowed(true) .addToBackStack(null) .commit(); ... ExampleFragment fragment = (ExampleFragment) fragmentManager.findFragmentByTag("tag");
Considerações especiais para fragmentos filhos e irmãos
Apenas um FragmentManager
pode controlar a backstack do fragmento
a qualquer momento. Se o app mostrar vários fragmentos irmãos na
tela ao mesmo tempo ou se usar fragmentos filhos, um
FragmentManager
será designado para processar a navegação principal do app.
Para definir o fragmento de navegação principal em uma transação de fragmento,
chame o
método setPrimaryNavigationFragment()
na transação, transmitindo-o na instância do fragmento em que
childFragmentManager
tem o controle principal.
Pense na estrutura de navegação como uma série de camadas, em que a atividade é a camada mais externa, envolvendo cada camada de fragmentos filhos dentro dela. Cada camada tem um único fragmento de navegação principal.
Quando ocorre o evento de retorno, a camada mais interna controla o comportamento de navegação. Quando a camada mais interna não tem mais transações de fragmentos de onde retornar, o controle retorna à próxima camada externa, e esse processo é repetido até você chegar à atividade.
Quando dois ou mais fragmentos são mostrados ao mesmo tempo, apenas um deles é considerado o fragmento de navegação principal. A definição de um fragmento como o de navegação principal remove a designação do fragmento anterior. Usando o exemplo anterior, se você definir o fragmento de detalhe como o de navegação principal, a designação do fragmento principal será removida.
Suporte a várias backstacks
Em alguns casos, seu app pode precisar ser compatível com várias backstacks. Um exemplo
comum é quando o app usa uma barra de navegação inferior. FragmentManager
permite
o suporte a várias backstacks com os métodos saveBackStack()
e
restoreBackStack()
. Esses métodos permitem alternar entre as backstacks,
salvando uma backstack e restaurando outra diferente.
saveBackStack()
funciona de maneira semelhante a chamar popBackStack()
com o parâmetro opcional
name
: a transação especificada e todas as transações depois dela na
pilha são exibidas. A diferença é que saveBackStack()
salva o estado de todos os fragmentos nas transações
exibidas.
Por exemplo, vamos supor que você já tenha adicionado um fragmento à backstack
confirmando um FragmentTransaction
usando addToBackStack()
, conforme mostrado no
exemplo a seguir.
Kotlin
supportFragmentManager.commit { replace<ExampleFragment>(R.id.fragment_container) setReorderingAllowed(true) addToBackStack("replacement") }
Java
supportFragmentManager.beginTransaction() .replace(R.id.fragment_container, ExampleFragment.class, null) // setReorderingAllowed(true) and the optional string argument for // addToBackStack() are both required if you want to use saveBackStack() .setReorderingAllowed(true) .addToBackStack("replacement") .commit();
Nesse caso, é possível salvar essa transação de fragmento e o estado de
ExampleFragment
chamando saveBackStack()
:
Kotlin
supportFragmentManager.saveBackStack("replacement")
Java
supportFragmentManager.saveBackStack("replacement");
É possível chamar restoreBackStack()
com o mesmo parâmetro de nome para restaurar todas
as transações exibidas e todos os estados do fragmento salvos:
Kotlin
supportFragmentManager.restoreBackStack("replacement")
Java
supportFragmentManager.restoreBackStack("replacement");
Fornecer dependências para seus fragmentos
Ao adicionar um fragmento, é possível instanciá-lo manualmente e
adicioná-lo à FragmentTransaction
.
Kotlin
fragmentManager.commit { // Instantiate a new instance before adding val myFragment = ExampleFragment() add(R.id.fragment_view_container, myFragment) setReorderingAllowed(true) }
Java
// Instantiate a new instance before adding ExampleFragment myFragment = new ExampleFragment(); fragmentManager.beginTransaction() .add(R.id.fragment_view_container, myFragment) .setReorderingAllowed(true) .commit();
Quando você confirma a transação do fragmento, a instância do fragmento
criado é a usada. No entanto, durante uma
mudança de configuração, sua
atividade e todos os fragmentos dela são destruídos e, em seguida, recriados com
os recursos Android
mais aplicáveis.
O FragmentManager
processa tudo isso para você: ele recria instâncias
dos seus fragmentos, anexa essas instâncias ao host e recria o estado da
backstack.
Por padrão, o FragmentManager
usa uma
FragmentFactory
fornecida pelo
framework para criar uma nova instância do seu fragmento. Essa
fábrica padrão usa reflexão para localizar e invocar um construtor sem argumento
para seu fragmento. Isso significa que não é possível usar essa fábrica padrão para
fornecer dependências para seu fragmento. Além disso, por padrão, qualquer construtor
personalizado usado para criar o fragmento na primeira vez não é usado
durante a recriação.
Para fornecer dependências ao seu fragmento ou usar qualquer
construtor personalizado, crie uma subclasse FragmentFactory
personalizada
e substitua
FragmentFactory.instantiate
.
Depois disso, você pode substituir a fábrica padrão do FragmentManager
pela
fábrica personalizada, que será usada para instanciar seus fragmentos.
Vamos supor que você tenha um DessertsFragment
responsável por mostrar
sobremesas conhecidas na sua cidade natal e que DessertsFragment
tenha uma dependência em uma classe DessertsRepository
que fornece as
informações necessárias para exibir
a interface correta ao usuário.
Você pode definir seu DessertsFragment
para exigir uma instância
de DessertsRepository
no construtor.
Kotlin
class DessertsFragment(val dessertsRepository: DessertsRepository) : Fragment() { ... }
Java
public class DessertsFragment extends Fragment { private DessertsRepository dessertsRepository; public DessertsFragment(DessertsRepository dessertsRepository) { super(); this.dessertsRepository = dessertsRepository; } // Getter omitted. ... }
Uma implementação simples do seu FragmentFactory
pode ficar semelhante
a esta:
Kotlin
class MyFragmentFactory(val repository: DessertsRepository) : FragmentFactory() { override fun instantiate(classLoader: ClassLoader, className: String): Fragment = when (loadFragmentClass(classLoader, className)) { DessertsFragment::class.java -> DessertsFragment(repository) else -> super.instantiate(classLoader, className) } }
Java
public class MyFragmentFactory extends FragmentFactory { private DessertsRepository repository; public MyFragmentFactory(DessertsRepository repository) { super(); this.repository = repository; } @NonNull @Override public Fragment instantiate(@NonNull ClassLoader classLoader, @NonNull String className) { Class<? extends Fragment> fragmentClass = loadFragmentClass(classLoader, className); if (fragmentClass == DessertsFragment.class) { return new DessertsFragment(repository); } else { return super.instantiate(classLoader, className); } } }
Esse exemplo de subclasses FragmentFactory
substitui o método
instantiate()
para fornecer uma lógica personalizada de criação de fragmentos para um DessertsFragment
.
Outras classes de fragmentos são processadas pelo comportamento padrão de
FragmentFactory
a super.instantiate()
.
Você pode designar MyFragmentFactory
como a fábrica a ser usada ao
construir os fragmentos do app definindo uma propriedade no
FragmentManager
. Defina essa propriedade antes do super.onCreate()
da atividade
para garantir que MyFragmentFactory
seja usado ao
recriar seus fragmentos.
Kotlin
class MealActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { supportFragmentManager.fragmentFactory = MyFragmentFactory(DessertsRepository.getInstance()) super.onCreate(savedInstanceState) } }
Java
public class MealActivity extends AppCompatActivity { @Override protected void onCreate(@Nullable Bundle savedInstanceState) { DessertsRepository repository = DessertsRepository.getInstance(); getSupportFragmentManager().setFragmentFactory(new MyFragmentFactory(repository)); super.onCreate(savedInstanceState); } }
A definição de FragmentFactory
na atividade substitui a criação de
fragmentos em toda a hierarquia de fragmentos da atividade. Em outras palavras,
o childFragmentManager
de todos os fragmentos filhos que você adiciona usa o conjunto de fábrica de fragmentos
personalizado mostrado aqui, a menos que seja substituído em um nível inferior.
Testar com FragmentFactory
Em uma única arquitetura de atividade, teste os fragmentos em
isolamento usando a
classe
FragmentScenario
. Como não é possível confiar na lógica onCreate
personalizada da sua
atividade, você pode transmitir a FragmentFactory
como um argumento
para o teste de fragmentos, conforme mostrado no exemplo a seguir.
// Inside your test val dessertRepository = mock(DessertsRepository::class.java) launchFragment<DessertsFragment>(factory = MyFragmentFactory(dessertRepository)).onFragment { // Test Fragment logic }
Para saber mais sobre esse processo de testagem e conferir exemplos completos, consulte Testar seus fragmentos.