Gerenciador de fragmentos

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.

Dois exemplos de layout da interface que mostram as relações entre
            fragmentos e as atividades de host deles.
Figura 1. Dois exemplos de layout da IU que mostram as relações entre fragmentos e as atividades de host deles.

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.

Cada host tem o próprio FragmentManager associado
            que gerencia os fragmentos filhos
Figura 2. Cada host tem o próprio FragmentManager associado que gerencia os fragmentos filhos.

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.