Untuk dapat menggunakan kembali fragmen, build fragmen 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 dan untuk berbagi informasi status, sering kali Anda harus memiliki saluran komunikasi antara aktivitas dan fragmennya, atau antara dua fragmen atau lebih. Agar fragmen tetap mandiri, jangan membuat fragmen berkomunikasi langsung dengan fragmen lain 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 persisten dengan API kustom, gunakan ViewModel
. Untuk
hasil sekali pakai dengan data yang dapat ditempatkan di
Bundle
, gunakan 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 mengetahui informasi selengkapnya tentang ViewModel
, lihat
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 digabungkan dalam
class MutableLiveData
.
LiveData
adalah class penyimpanan data
observable berbasis siklus proses. MutableLiveData
memungkinkan nilainya
diubah. Untuk mengetahui informasi selengkapnya tentang LiveData
, lihat
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); }); } }
Berbagi data antarfragmen
Dua fragmen atau lebih dalam aktivitas yang sama sering kali perlu saling berkomunikasi. Misalnya, bayangkan satu fragmen yang menampilkan daftar dan fragmen lainnya yang memungkinkan pengguna menerapkan berbagai filter ke daftar. Menerapkan kasus ini tidak akan mudah tanpa fragmen berkomunikasi langsung, tetapi fragmen tersebut tidak akan lagi bersifat 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 berbagi ViewModel
seperti ini,
fragmen tidak perlu saling mengetahui, dan aktivitas
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); } }
Kedua fragmen menggunakan aktivitas host 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 berbagi data
antarfragmen ini, gunakan fragmen induk sebagai cakupan ViewModel
, seperti yang ditunjukkan
dalam contoh berikut:
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); ... } }
Menentukan 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 antarfragmen
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. } }); }
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 secara manual metode yang tidak diuji.
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,
gunakan getChildFragmentManager()
dari fragmen induk, bukan
getParentFragmentManager()
, saat memanggil 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. } }); }
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. } }); } }