Comunicar com fragmentos

Para reutilizar fragmentos, crie-os como componentes totalmente independentes que definem o próprio layout e comportamento. Assim que você tiver definido esses fragmentos reutilizáveis, será possível os associar a uma atividade e conectar à lógica do aplicativo para abranger a interface composta.

Para reagir corretamente aos eventos do usuário e compartilhar informações de estado, geralmente é necessário ter canais de comunicação entre uma atividade e os fragmentos dela ou entre dois ou mais fragmentos. Para manter os fragmentos independentes, não faça com que eles se comuniquem diretamente com outros fragmentos ou com a atividade do host.

A biblioteca Fragment oferece duas opções de comunicação: um ViewModel compartilhado e a API Fragment Result. A opção recomendada depende do caso de uso. Para compartilhar dados persistentes com APIs personalizadas, use um ViewModel. Para conferir um resultado único com dados que podem ser colocados em um Bundle, use a API Fragment Result.

As seções abaixo mostram como usar o ViewModel e a API Fragment Result para a comunicação entre seus fragmentos e suas atividades.

Compartilhar dados usando um ViewModel

O ViewModel é a opção ideal quando você precisa compartilhar dados entre vários fragmentos ou entre fragmentos e a atividade do host. Objetos ViewModel armazenam e gerenciam dados da interface. Para mais informações sobre o ViewModel, consulte a Visão geral do ViewModel.

Compartilhar dados com a atividade do host

Em alguns casos, pode ser necessário compartilhar dados entre fragmentos e a atividade host. Por exemplo, talvez você queira ativar ou desativar um componente de interface global com base em uma interação dentro de um fragmento.

Considere o seguinte ItemViewModel:

Kotlin

class ItemViewModel : ViewModel() {
    private val mutableSelectedItem = MutableLiveData<Item>()
    val selectedItem: LiveData<Item> get() = mutableSelectedItem

    fun selectItem(item: Item) {
        mutableSelectedItem.value = item
    }
}

Java

public class ItemViewModel extends ViewModel {
    private final MutableLiveData<Item> selectedItem = new MutableLiveData<Item>();
    public void selectItem(Item item) {
        selectedItem.setValue(item);
    }
    public LiveData<Item> getSelectedItem() {
        return selectedItem;
    }
}

Neste exemplo, os dados armazenados são agrupados em uma classe MutableLiveData. LiveData é uma classe armazenadora de dados observáveis que reconhece o ciclo de vida. MutableLiveData permite que o valor seja alterado. Para saber mais sobre LiveData, consulte a Visão geral de LiveData.

Seu fragmento e a atividade host podem extrair uma instância compartilhada de um ViewModel com escopo de atividade transmitindo a atividade para o construtor ViewModelProvider. O ViewModelProvider processa a instanciação do ViewModel ou, se ele já existir, a extração dele. Os dois componentes podem observar e modificar esses dados.

Kotlin

class MainActivity : AppCompatActivity() {
    // Using the viewModels() Kotlin property delegate from the activity-ktx
    // artifact to retrieve the ViewModel in the activity scope.
    private val viewModel: ItemViewModel by viewModels()
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        viewModel.selectedItem.observe(this, Observer { item ->
            // Perform an action with the latest item data.
        })
    }
}

class ListFragment : Fragment() {
    // Using the activityViewModels() Kotlin property delegate from the
    // fragment-ktx artifact to retrieve the ViewModel in the activity scope.
    private val viewModel: ItemViewModel by activityViewModels()

    // Called when the item is clicked.
    fun onItemClicked(item: Item) {
        // Set a new item.
        viewModel.selectItem(item)
    }
}

Java

public class MainActivity extends AppCompatActivity {
    private ItemViewModel viewModel;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        viewModel = new ViewModelProvider(this).get(ItemViewModel.class);
        viewModel.getSelectedItem().observe(this, item -> {
            // Perform an action with the latest item data.
        });
    }
}

public class ListFragment extends Fragment {
    private ItemViewModel viewModel;

    @Override
    public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        viewModel = new ViewModelProvider(requireActivity()).get(ItemViewModel.class);
        ...
        items.setOnClickListener(item -> {
            // Set a new item.
            viewModel.select(item);
        });
    }
}

Compartilhar dados entre fragmentos

Com frequência, dois ou mais fragmentos na mesma atividade precisam se comunicar entre si. Por exemplo, imagine um fragmento que mostre uma lista e outro que permite ao usuário aplicar vários filtros à lista. A implementação desse caso não é trivial sem a comunicação direta dos fragmentos, mas, depois dela, eles não serão mais independentes. Além disso, ambos os fragmentos precisam processar o cenário em que o outro ainda não foi criado ou não está visível.

Esses fragmentos podem compartilhar um ViewModel usando o escopo de atividade para processar essa comunicação. Ao compartilhar o ViewModel dessa maneira, os fragmentos não precisam saber sobre a existência dos outros, e a atividade não precisa fazer nada para facilitar a comunicação.

O exemplo abaixo mostra como dois fragmentos podem usar um ViewModel compartilhado para se comunicar:

Kotlin

class ListViewModel : ViewModel() {
    val filters = MutableLiveData<Set<Filter>>()

    private val originalList: LiveData<List<Item>>() = ...
    val filteredList: LiveData<List<Item>> = ...

    fun addFilter(filter: Filter) { ... }

    fun removeFilter(filter: Filter) { ... }
}

class ListFragment : Fragment() {
    // Using the activityViewModels() Kotlin property delegate from the
    // fragment-ktx artifact to retrieve the ViewModel in the activity scope.
    private val viewModel: ListViewModel by activityViewModels()
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        viewModel.filteredList.observe(viewLifecycleOwner, Observer { list ->
            // Update the list UI.
        }
    }
}

class FilterFragment : Fragment() {
    private val viewModel: ListViewModel by activityViewModels()
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        viewModel.filters.observe(viewLifecycleOwner, Observer { set ->
            // Update the selected filters UI.
        }
    }

    fun onFilterSelected(filter: Filter) = viewModel.addFilter(filter)

    fun onFilterDeselected(filter: Filter) = viewModel.removeFilter(filter)
}

Java

public class ListViewModel extends ViewModel {
    private final MutableLiveData<Set<Filter>> filters = new MutableLiveData<>();

    private final LiveData<List<Item>> originalList = ...;
    private final LiveData<List<Item>> filteredList = ...;

    public LiveData<List<Item>> getFilteredList() {
        return filteredList;
    }

    public LiveData<Set<Filter>> getFilters() {
        return filters;
    }

    public void addFilter(Filter filter) { ... }

    public void removeFilter(Filter filter) { ... }
}

public class ListFragment extends Fragment {
    private ListViewModel viewModel;

    @Override
    public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        viewModel = new ViewModelProvider(requireActivity()).get(ListViewModel.class);
        viewModel.getFilteredList().observe(getViewLifecycleOwner(), list -> {
            // Update the list UI.
        });
    }
}

public class FilterFragment extends Fragment {
    private ListViewModel viewModel;

    @Override
    public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
        viewModel = new ViewModelProvider(requireActivity()).get(ListViewModel.class);
        viewModel.getFilters().observe(getViewLifecycleOwner(), set -> {
            // Update the selected filters UI.
        });
    }

    public void onFilterSelected(Filter filter) {
        viewModel.addFilter(filter);
    }

    public void onFilterDeselected(Filter filter) {
        viewModel.removeFilter(filter);
    }
}

Os dois fragmentos usam a atividade do host como o escopo para ViewModelProvider. Como os fragmentos usam o mesmo escopo, eles recebem a mesma instância do ViewModel, permitindo que se comuniquem entre si.

Compartilhar dados entre um fragmento pai e um filho

Ao trabalhar com fragmentos filhos, o fragmento pai e os filhos podem precisar compartilhar dados entre si. Para compartilhar dados entre esses fragmentos, use o fragmento pai como o escopo de ViewModel, conforme mostrado neste exemplo:

Kotlin

class ListFragment: Fragment() {
    // Using the viewModels() Kotlin property delegate from the fragment-ktx
    // artifact to retrieve the ViewModel.
    private val viewModel: ListViewModel by viewModels()
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        viewModel.filteredList.observe(viewLifecycleOwner, Observer { list ->
            // Update the list UI.
        }
    }
}

class ChildFragment: Fragment() {
    // Using the viewModels() Kotlin property delegate from the fragment-ktx
    // artifact to retrieve the ViewModel using the parent fragment's scope
    private val viewModel: ListViewModel by viewModels({requireParentFragment()})
    ...
}

Java

public class ListFragment extends Fragment {
    private ListViewModel viewModel;

    @Override
    public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
        viewModel = new ViewModelProvider(this).get(ListViewModel.class);
        viewModel.getFilteredList().observe(getViewLifecycleOwner(), list -> {
            // Update the list UI.
        }
    }
}

public class ChildFragment extends Fragment {
    private ListViewModel viewModel;
    @Override
    public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
        viewModel = new ViewModelProvider(requireParentFragment()).get(ListViewModel.class);
        ...
    }
}

ViewModel como escopo para o gráfico de navegação

Se você estiver usando a biblioteca de Navegação, também poderá definir um ViewModel como escopo para o ciclo de vida da NavBackStackEntry de um destino. Por exemplo, o ViewModel pode ter o escopo definido como NavBackStackEntry para ListFragment:

Kotlin

class ListFragment: Fragment() {
    // Using the navGraphViewModels() Kotlin property delegate from the fragment-ktx
    // artifact to retrieve the ViewModel using the NavBackStackEntry scope.
    // R.id.list_fragment == the destination id of the ListFragment destination
    private val viewModel: ListViewModel by navGraphViewModels(R.id.list_fragment)

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        viewModel.filteredList.observe(viewLifecycleOwner, Observer { item ->
            // Update the list UI.
        }
    }
}

Java

public class ListFragment extends Fragment {
    private ListViewModel viewModel;

    @Override
    public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
    NavController navController = NavHostFragment.findNavController(this);
        NavBackStackEntry backStackEntry = navController.getBackStackEntry(R.id.list_fragment)

        viewModel = new ViewModelProvider(backStackEntry).get(ListViewModel.class);
        viewModel.getFilteredList().observe(getViewLifecycleOwner(), list -> {
            // Update the list UI.
        }
    }
}

Para saber mais sobre como definir um ViewModel como escopo para uma NavBackStackEntry, consulte Interagir programaticamente com o componente de navegação.

Receber resultados usando a API Fragment Result

Em alguns casos, pode ser necessário transmitir um valor único entre dois fragmentos ou entre um fragmento e a atividade host. Por exemplo, você pode ter um fragmento que lê códigos QR, transmitindo os dados de volta para um fragmento anterior.

Na versão 1.3.0 e versões mais recentes da biblioteca Fragment, cada FragmentManager implementa FragmentResultOwner. Isso significa que um FragmentManager pode atuar como armazenamento central para resultados de fragmento. Essa mudança permite que componentes se comuniquem entre si, definindo resultados de fragmento e detectando esses resultados sem exigir que os componentes tenham referências diretas uns aos outros.

Transmitir resultados entre fragmentos

Para transmitir dados de volta ao fragmento A do fragmento B, defina um listener de resultado no fragmento A, que recebe o resultado. Chame setFragmentResultListener() no FragmentManager do fragmento A, conforme mostrado no exemplo a seguir:

Kotlin

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    // Use the Kotlin extension in the fragment-ktx artifact.
    setFragmentResultListener("requestKey") { requestKey, bundle ->
        // We use a String here, but any type that can be put in a Bundle is supported.
        val result = bundle.getString("bundleKey")
        // Do something with the result.
    }
}

Java

@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    getParentFragmentManager().setFragmentResultListener("requestKey", this, new FragmentResultListener() {
        @Override
        public void onFragmentResult(@NonNull String requestKey, @NonNull Bundle bundle) {
            // We use a String here, but any type that can be put in a Bundle is supported.
            String result = bundle.getString("bundleKey");
            // Do something with the result.
        }
    });
}
O fragmento B envia dados para o A usando um FragmentManager
Figura 1. O fragmento B envia dados para o A usando um FragmentManager.

No fragmento B, que produz o resultado, defina o resultado no mesmo FragmentManager usando a mesma requestKey. Para isso, use a API setFragmentResult():

Kotlin

button.setOnClickListener {
    val result = "result"
    // Use the Kotlin extension in the fragment-ktx artifact.
    setFragmentResult("requestKey", bundleOf("bundleKey" to result))
}

Java

button.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        Bundle result = new Bundle();
        result.putString("bundleKey", "result");
        getParentFragmentManager().setFragmentResult("requestKey", result);
    }
});

Em seguida, o fragmento A recebe o resultado e executa o callback do listener quando o fragmento é STARTED.

Você pode ter apenas um listener e resultado para uma determinada chave. Se você chamar setFragmentResult() mais de uma vez para a mesma chave e se o listener não for STARTED, o sistema substituirá todos os resultados pendentes pelo resultado atualizado.

Se você definir um resultado sem um listener correspondente para recebê-lo, ele será armazenado no FragmentManager até que você defina um listener com a mesma chave. Quando um listener recebe um resultado e dispara o callback onFragmentResult(), o resultado é apagado. Esse comportamento tem duas grandes implicações:

  • Fragmentos na backstack não recebem resultados até que tenham sido retirados e STARTED (iniciados).
  • Se um fragmento que detecta um resultado for STARTED no momento de definição do resultado, o callback do listener será disparado imediatamente.

Testar resultados de fragmentos

Use o FragmentScenario para testar chamadas para setFragmentResult() e setFragmentResultListener(). Crie um cenário para o fragmento em teste usando launchFragmentInContainer ou launchFragment e chame manualmente o método que não está sendo testado.

Para testar setFragmentResultListener(), crie um cenário com o fragmento que faz a chamada de setFragmentResultListener(). Em seguida, chame setFragmentResult() diretamente e verifique o resultado:

@Test
fun testFragmentResultListener() {
    val scenario = launchFragmentInContainer<ResultListenerFragment>()
    scenario.onFragment { fragment ->
        val expectedResult = "result"
        fragment.parentFragmentManager.setFragmentResult("requestKey", bundleOf("bundleKey" to expectedResult))
        assertThat(fragment.result).isEqualTo(expectedResult)
    }
}

class ResultListenerFragment : Fragment() {
    var result : String? = null
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // Use the Kotlin extension in the fragment-ktx artifact.
        setFragmentResultListener("requestKey") { requestKey, bundle ->
            result = bundle.getString("bundleKey")
        }
    }
}

Para testar setFragmentResult(), crie um cenário com o fragmento que faz a chamada de setFragmentResult(). Em seguida, chame setFragmentResultListener() diretamente e verifique o resultado:

@Test
fun testFragmentResult() {
    val scenario = launchFragmentInContainer<ResultFragment>()
    lateinit var actualResult: String?
    scenario.onFragment { fragment ->
        fragment.parentFragmentManager
                .setFragmentResultListener("requestKey") { requestKey, bundle ->
            actualResult = bundle.getString("bundleKey")
        }
    }
    onView(withId(R.id.result_button)).perform(click())
    assertThat(actualResult).isEqualTo("result")
}

class ResultFragment : Fragment(R.layout.fragment_result) {
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        view.findViewById(R.id.result_button).setOnClickListener {
            val result = "result"
            // Use the Kotlin extension in the fragment-ktx artifact.
            setFragmentResult("requestKey", bundleOf("bundleKey" to result))
        }
    }
}

Transmitir resultados entre fragmentos pai e filho

Para transmitir um resultado de um fragmento filho para um pai, use getChildFragmentManager() no fragmento pai em vez de getParentFragmentManager() ao chamar setFragmentResultListener().

Kotlin

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    // Set the listener on the child fragmentManager.
    childFragmentManager.setFragmentResultListener("requestKey") { key, bundle ->
        val result = bundle.getString("bundleKey")
        // Do something with the result.
    }
}

Java

@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // Set the listener on the child fragmentManager.
    getChildFragmentManager()
        .setFragmentResultListener("requestKey", this, new FragmentResultListener() {
            @Override
            public void onFragmentResult(@NonNull String requestKey, @NonNull Bundle bundle) {
                String result = bundle.getString("bundleKey");
                // Do something with the result.
            }
        });
}
Um fragmento filho pode usar FragmentManager para enviar um resultado
            ao pai
Figura 2 Um fragmento filho pode usar FragmentManager para enviar um resultado ao pai.

O fragmento filho define o resultado no FragmentManager dele. Em seguida, o pai recebe o resultado assim que o fragmento é STARTED:

Kotlin

button.setOnClickListener {
    val result = "result"
    // Use the Kotlin extension in the fragment-ktx artifact.
    setFragmentResult("requestKey", bundleOf("bundleKey" to result))
}

Java

button.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        Bundle result = new Bundle();
        result.putString("bundleKey", "result");
        // The child fragment needs to still set the result on its parent fragment manager.
        getParentFragmentManager().setFragmentResult("requestKey", result);
    }
});

Receber resultados na atividade do host

Para receber um resultado de fragmento na atividade do host, defina um listener de resultado no gerenciador de fragmentos usando getSupportFragmentManager().

Kotlin

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        supportFragmentManager
                .setFragmentResultListener("requestKey", this) { requestKey, bundle ->
            // We use a String here, but any type that can be put in a Bundle is supported.
            val result = bundle.getString("bundleKey")
            // Do something with the result.
        }
    }
}

Java

class MainActivity extends AppCompatActivity {
    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        getSupportFragmentManager().setFragmentResultListener("requestKey", this, new FragmentResultListener() {
            @Override
            public void onFragmentResult(@NonNull String requestKey, @NonNull Bundle bundle) {
                // We use a String here, but any type that can be put in a Bundle is supported.
                String result = bundle.getString("bundleKey");
                // Do something with the result.
            }
        });
    }
}