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. } }); }
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. } }); }
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. } }); } }