Como se comunicar com fragmentos

Para reutilizar os fragmentos, você precisa criá-los como componentes completamente autossuficientes que definam o próprio layout e comportamento. Assim que você tiver definido esses fragmentos reutilizáveis, será possível associá-los a uma atividade e conectá-los à lógica do aplicativo para abranger a IU composta em sua totalidade.

Para reagir corretamente aos eventos do usuário ou 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 autossuficientes, não deixe 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 permanentes com qualquer API personalizada, use um ViewModel. Para ver um resultado único com dados que podem ser colocados em um Bundle, use a API Fragment Result.

As seções a seguir 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 IU. Para ver mais informações sobre 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 do host. Por exemplo, talvez você queira ativar ou desativar um componente de IU 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;
    }
}

Nesse 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 mudado. Para ver mais informações sobre LiveData, consulte a Visão geral do LiveData.

Seu fragmento e a atividade do host podem recuperar 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 recuperaçã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 exibe uma lista e outro que permite ao usuário aplicar vários filtros à lista. Esse caso pode não ser trivial para implementar sem que os fragmentos se comuniquem diretamente entre si, o que significaria que eles não são mais autossuficientes. 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 conhecer uns aos outros, e a atividade não precisa fazer nada para facilitar a comunicação.

O exemplo a seguir 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);
    }
}

Observe que os dois fragmentos usam a atividade do host como escopo para o 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 do ViewModel.

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

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

Se você estiver usando a biblioteca Navigation, 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 do host. Por exemplo, você pode ter um fragmento que lê códigos QR, transmitindo os dados de volta para um fragmento anterior. A partir do Fragment 1.3.0-alpha04, 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, o que produz o resultado, você precisa definir 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 pilha de retorno não recebem resultados até que tenham sido retirados e STARTED.
  • 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. Em seguida, 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, o fragmento pai precisa usar getChildFragmentManager() em vez de getParentFragmentManager() ao chamar setFragmentResultListener().

Kotlin

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    // We 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);
    // We 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
            }
        });
    }
}