Untuk menggunakan ulang fragmen, build setiap komponen sebagai komponen mandiri yang menentukan tata letak dan perilakunya sendiri. Setelah menentukan fragmen yang dapat digunakan kembali ini, Anda dapat mengaitkannya dengan aktivitas dan menghubungkannya dengan logika aplikasi untuk mewujudkan keseluruhan UI gabungan.
Untuk bereaksi secara tepat terhadap peristiwa pengguna, atau berbagi informasi status, Anda sering kali perlu memiliki saluran komunikasi antara aktivitas dan fragmennya, atau antara dua fragmen atau lebih. Agar fragmen tetap mandiri, Anda tidak boleh membiarkan fragmen berkomunikasi langsung dengan fragmen lainnya atau dengan aktivitas host-nya.
Library Fragment
menyediakan dua opsi komunikasi:
ViewModel
bersama dan Fragment
Result API. Opsi yang direkomendasikan bergantung pada kasus penggunaan. Untuk membagikan
data tetap dengan API kustom apa pun, Anda harus menggunakan ViewModel
. Untuk
hasil sekali pakai dengan data yang dapat ditempatkan di
Bundle
, Anda harus menggunakan Fragment
Result API.
Bagian berikut menunjukkan cara menggunakan ViewModel
dan Fragment
Result API untuk berkomunikasi antara fragmen dan aktivitas Anda.
Membagikan data menggunakan ViewModel
ViewModel
adalah pilihan ideal saat Anda perlu berbagi data antara beberapa fragmen atau antara fragmen dan aktivitas host-nya.
Objek ViewModel
menyimpan dan
mengelola data UI. Untuk informasi selengkapnya tentang ViewModel
, baca
Ringkasan ViewModel.
Membagikan data dengan aktivitas host
Pada kasus tertentu, Anda mungkin perlu membagikan data antara fragmen dan aktivitas host-nya. Misalnya, Anda mungkin ingin mengalihkan komponen UI global berdasarkan interaksi dalam fragmen.
Pertimbangkan ItemViewModel
berikut:
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; } }
Dalam contoh ini, data yang disimpan akan digabungkan ke dalam class MutableLiveData
.
LiveData
adalah class penyimpanan data
observable berbasis siklus proses. MutableLiveData
memungkinkan nilainya
diubah. Untuk informasi selengkapnya tentang LiveData
, baca
Ringkasan LiveData.
Fragmen dan aktivitas host-nya dapat mengambil instance bersama
ViewModel
dari cakupan aktivitas dengan meneruskan aktivitas ke
konstruktor
ViewModelProvider
. ViewModelProvider
menangani pembuatan instance ViewModel
atau mengambilnya jika sudah ada. Kedua komponen dapat mengamati dan
memodifikasi data ini:
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); }); } }
Membagikan data antar-fragmen
Dua fragmen atau lebih dalam aktivitas yang sama sering kali perlu saling berkomunikasi. Misalnya, bayangkan satu fragmen yang menampilkan daftar dan lainnya yang memungkinkan pengguna menerapkan berbagai filter ke daftar. Kasus ini mungkin tidak mudah diterapkan tanpa kedua fragmen berkomunikasi secara langsung, yang berarti fragmen tersebut tidak lagi berfungsi mandiri. Selain itu, kedua fragmen harus menangani skenario saat fragmen lain belum dibuat atau terlihat.
Fragmen ini dapat membagikan ViewModel
menggunakan cakupan aktivitasnya
untuk menangani komunikasi ini. Dengan membagikan ViewModel
menggunakan cara ini,
fragmen tidak perlu saling mengetahui, dan aktivitasnya
tidak perlu melakukan apa pun untuk memfasilitasi komunikasi.
Contoh berikut menunjukkan cara dua fragmen dapat menggunakan
ViewModel
bersama untuk berkomunikasi:
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); } }
Perhatikan bahwa kedua fragmen menggunakan aktivitas host-nya sebagai cakupan untuk
ViewModelProvider
. Karena fragmen menggunakan cakupan yang sama, fragmen tersebut
menerima instance ViewModel
yang sama, yang memungkinkannya berkomunikasi
dua arah.
Membagikan data antara fragmen induk dan turunan
Saat menangani fragmen turunan, fragmen induk dan fragmen
turunannya mungkin perlu membagikan data kepada satu sama lain. Untuk membagikan data antara fragmen ini, gunakan fragmen induk sebagai cakupan 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); ... } }
Memberi cakupan ViewModel
ke Grafik Navigasi
Jika menggunakan library Navigasi, Anda juga dapat
memberi cakupan ViewModel
ke siklus proses
NavBackStackEntry
tujuan. Misalnya,
ViewModel
dapat dicakup ke NavBackStackEntry
untuk 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 } } }
Untuk informasi selengkapnya tentang pencakupan ViewModel
ke NavBackStackEntry
, baca
Berinteraksi secara terprogram dengan komponen Navigasi.
Mendapatkan hasil menggunakan Fragment Result API
Pada kasus tertentu, sebaiknya Anda meneruskan nilai sekali pakai antara dua fragmen atau antara sebuah fragmen dan aktivitas host-nya. Misalnya, Anda mungkin memiliki
fragmen yang membaca kode QR, sehingga meneruskan data kembali ke fragmen sebelumnya.
Pada Fragment versi 1.3.0 dan yang lebih baru,
setiap FragmentManager
mengimplementasikan
FragmentResultOwner
.
Ini berarti bahwa FragmentManager
dapat bertindak sebagai penyimpanan pusat hasil
fragmen. Perubahan ini memungkinkan komponen untuk berkomunikasi satu sama lain dengan menetapkan hasil fragmen dan memproses hasil tersebut tanpa mengharuskan komponen tersebut mereferensikan satu sama lain secara langsung.
Meneruskan hasil antar-fragmen
Untuk meneruskan data kembali dari fragmen B ke fragmen A, tetapkan pemroses hasil
pada fragmen A, yakni fragmen yang menerima hasil. Panggil
setFragmentResultListener()
di FragmentManager
fragmen A, seperti yang ditunjukkan dalam contoh berikut:
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 } }); }

FragmentManager
.Dalam fragmen B, fragmen yang memberikan hasil, Anda harus menetapkan hasilnya
pada FragmentManager
yang sama menggunakan requestKey
yang sama. Anda dapat melakukannya menggunakan setFragmentResult()
API:
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); } });
Fragmen A kemudian menerima hasilnya dan mengeksekusi callback pemroses
setelah fragmen
STARTED
.
Anda hanya dapat memiliki satu pemroses dan satu hasil untuk setiap kunci. Jika Anda memanggil
setFragmentResult()
lebih dari sekali untuk kunci yang sama, dan jika pemroses
bukan STARTED
, sistem akan mengganti hasil yang tertunda dengan hasil
yang diperbarui. Jika Anda menetapkan hasil tanpa pemroses yang sesuai yang akan menerimanya,
hasilnya akan disimpan dalam FragmentManager
hingga pemroses dengan
kunci yang sama ditetapkan. Setelah pemroses menerima hasil dan mengaktifkan
callback onFragmentResult()
, hasilnya akan dihapus. Perilaku ini memiliki
dua implikasi utama:
- Fragmen pada data sebelumnya tidak menerima hasil hingga fragmen tersebut
muncul dan
STARTED
. - Jika fragmen yang memproses hasil adalah
STARTED
saat hasilnya ditetapkan, callback pemroses akan langsung diaktifkan.
Menguji hasil fragmen
Gunakan
FragmentScenario
untuk menguji panggilan ke setFragmentResult()
dan setFragmentResultListener()
.
Buat skenario untuk fragmen yang sedang diuji menggunakan
launchFragmentInContainer
atau
launchFragment
,
lalu panggil metode yang tidak sedang diuji secara manual.
Untuk menguji setFragmentResultListener()
, buat skenario dengan
fragmen yang membuat panggilan ke setFragmentResultListener()
. Selanjutnya,
panggil setFragmentResult()
secara langsung, dan verifikasi hasilnya:
@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")
}
}
}
Untuk menguji setFragmentResult()
, buat skenario dengan fragmen yang melakukan
panggilan ke setFragmentResult()
. Selanjutnya, panggil setFragmentResultListener()
secara langsung, dan verifikasi hasilnya:
@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))
}
}
}
Meneruskan hasil antara fragmen induk dan turunan
Untuk meneruskan hasil dari fragmen turunan ke induk, fragmen induk
harus menggunakan getChildFragmentManager()
, dan bukan
getParentFragmentManager()
saat memanggil 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 } }); }

FragmentManager
untuk mengirimkan hasil ke induknya.Fragmen turunan menetapkan hasil pada FragmentManager
-nya. Fragmen induk
kemudian menerima hasilnya setelah fragmen 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); } });
Menerima hasil dalam aktivitas host
Untuk menerima hasil fragmen dalam aktivitas host, tetapkan pemroses hasil
pada pengelola fragmen menggunakan
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 } }); } }