หากต้องการใช้ Fragment ซ้ำ ให้สร้างเป็นคอมโพเนนต์ที่สมบูรณ์ในตัว ที่กำหนดเลย์เอาต์และลักษณะการทำงานของตัวเอง เมื่อคุณให้คำจำกัดความ Fragment ที่นำกลับมาใช้ใหม่ได้ คุณสามารถเชื่อมโยง Fragment กับกิจกรรมและเชื่อมต่อ ด้วยตรรกะแอปพลิเคชันเพื่อให้รู้จัก UI แบบผสมโดยรวม
เพื่อตอบสนองต่อเหตุการณ์ของผู้ใช้อย่างเหมาะสมและการแชร์ข้อมูลสถานะ คุณมักจะ จึงจำเป็นต้องมีช่องทางการสื่อสารระหว่างกิจกรรมกับกิจกรรม หรือระหว่างส่วนย่อย 2 ส่วนขึ้นไป หากต้องการให้ส่วนย่อยอยู่ในตัวเอง ไม่มีส่วนย่อยที่สื่อสารกับส่วนย่อยโดยตรง หรือ กับกิจกรรมผู้จัด
ไลบรารี Fragment
มีตัวเลือกการสื่อสาร 2 แบบ ได้แก่
ViewModel
และส่วนย่อย
API ของผลลัพธ์ ตัวเลือกที่แนะนำจะขึ้นอยู่กับกรณีการใช้งาน เพื่อแชร์
ข้อมูลถาวรที่มี API ที่กำหนดเอง ให้ใช้ ViewModel
สำหรับ
ผลลัพธ์แบบครั้งเดียวที่มีข้อมูลซึ่งสามารถวางไว้ใน
Bundle
ให้ใช้ Fragment
API ของผลลัพธ์
ส่วนต่อไปนี้จะแสดงวิธีใช้ ViewModel
และ Fragment
ผลลัพธ์ API เพื่อสื่อสารระหว่างส่วนย่อยและกิจกรรม
แชร์ข้อมูลโดยใช้ ViewModel
ViewModel
คือตัวเลือกที่เหมาะสมในกรณีต่อไปนี้
คุณต้องแชร์ข้อมูลระหว่าง
ส่วนย่อยหลายรายการหรือระหว่างส่วนย่อยกับกิจกรรมโฮสต์
ViewModel
ออบเจ็กต์จัดเก็บและ
จัดการข้อมูล UI ดูข้อมูลเพิ่มเติมเกี่ยวกับ ViewModel
ได้ที่
ดูภาพรวมของโมเดล
แชร์ข้อมูลกับกิจกรรมของโฮสต์
ในบางกรณี คุณอาจจำเป็นต้องแชร์ข้อมูลระหว่าง Fragment กับ กิจกรรมผู้จัด เช่น คุณอาจต้องการสลับ UI ส่วนกลาง คอมโพเนนต์ตามการโต้ตอบภายในส่วนย่อย
ลองพิจารณา 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; } }
ในตัวอย่างนี้ ข้อมูลที่จัดเก็บไว้จะรวมไว้ใน
MutableLiveData
LiveData
เป็นแอปที่คำนึงถึงวงจร
คลาสผู้ถือข้อมูลที่ได้รับอนุญาตให้สังเกตพฤติกรรมผู้ใช้ได้ MutableLiveData
ให้ค่าของ
มีการเปลี่ยนแปลง ดูข้อมูลเพิ่มเติมเกี่ยวกับ LiveData
ได้ที่
ภาพรวม LiveData
ทั้งส่วนย่อยและกิจกรรมของโฮสต์สามารถเรียกข้อมูลอินสแตนซ์ที่แชร์ได้
ของ ViewModel
ที่มีขอบเขตกิจกรรมโดยส่งกิจกรรมไปยัง
ViewModelProvider
เครื่องมือสร้างขึ้นมา ViewModelProvider
จัดการการเริ่มต้น ViewModel
หรือเรียกข้อมูล หากมีอยู่แล้ว ทั้ง 2 คอมโพเนนต์สามารถสังเกตและ
แก้ไขข้อมูลนี้
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); }); } }
แชร์ข้อมูลระหว่างส่วนย่อย
ส่วนย่อยอย่างน้อย 2 ส่วนในกิจกรรมเดียวกันมักจะต้องสื่อสารกับ ระหว่างกัน ตัวอย่างเช่น สมมติว่ามีส่วนย่อยหนึ่งที่แสดงลิสต์รายการและ อีกแบบที่ช่วยให้ผู้ใช้ใช้ตัวกรองต่างๆ กับรายการได้ การนำกรณีนี้ไปใช้ก็ไม่ง่ายหากไม่มีส่วนใดๆ ติดต่อสื่อสารได้โดยตรง แต่หลังจากนั้นก็ไม่มี จบในตัว นอกจากนี้ Fragment ทั้ง 2 แบบต้องรองรับสถานการณ์ ที่ยังไม่มีการสร้างหรือมองเห็นได้อีกส่วนหนึ่ง
ส่วนย่อยเหล่านี้สามารถแชร์ ViewModel
โดยใช้ขอบเขตกิจกรรม
เพื่อจัดการการสื่อสารนี้ การแชร์ ViewModel
ในลักษณะนี้
ส่วนย่อยไม่จำเป็นต้องรู้เกี่ยวกับกันและกัน และกิจกรรมนั้น
คุณไม่จำเป็นต้องดำเนินการใดๆ เพื่ออำนวยความสะดวกในการสื่อสาร
ตัวอย่างต่อไปนี้แสดงการใช้ Fragment แบบ 2 ส่วนที่แชร์
ViewModel
สำหรับการสื่อสาร:
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); } }
ส่วนย่อยทั้งสองใช้กิจกรรมโฮสต์เป็นขอบเขตสำหรับ
ViewModelProvider
เนื่องจากส่วนย่อยใช้ขอบเขตเดียวกัน จึงจะได้รับ
อินสแตนซ์เดียวกันของ ViewModel
ซึ่งช่วยให้สื่อสาร
กลับไปกลับมา
แชร์ข้อมูลระหว่างส่วนย่อยระดับบนสุดและย่อย
เมื่อทำงานกับ Fragment ย่อย Fragment ระดับบนสุดและส่วนย่อย
Fragment อาจต้องแชร์ข้อมูลให้แก่กัน เพื่อแชร์ข้อมูลระหว่าง
ส่วนย่อยเหล่านี้ ให้ใช้ส่วนย่อยหลักเป็นขอบเขต 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); ... } }
กำหนดขอบเขต ViewModel ไปยังกราฟการนำทาง
หากคุณกำลังใช้ไลบรารีการนำทาง คุณยังสามารถ
กำหนดขอบเขต ViewModel
ตามวงจรชีวิตของปลายทาง
NavBackStackEntry
สำหรับ
เช่น ViewModel
สามารถกำหนดขอบเขตเป็น NavBackStackEntry
สำหรับ 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. } } }
สำหรับข้อมูลเพิ่มเติมเกี่ยวกับการกำหนดขอบเขต ViewModel
เป็น NavBackStackEntry
โปรดดู
โต้ตอบกับคอมโพเนนต์การนำทางแบบเป็นโปรแกรม
รับผลลัพธ์โดยใช้ Fragment Result API
ในบางกรณี คุณอาจต้องการส่งค่าแบบครั้งเดียวระหว่าง 2 ส่วน หรือระหว่างส่วนย่อยกับกิจกรรมโฮสต์ ตัวอย่างเช่น คุณอาจมี ส่วนย่อยที่อ่านคิวอาร์โค้ดและส่งข้อมูลกลับไปยังส่วนย่อยก่อนหน้า
ใน Fragment เวอร์ชัน 1.3.0 ขึ้นไป
แต่ละรายการ FragmentManager
implements
FragmentResultOwner
ซึ่งหมายความว่า FragmentManager
สามารถทำหน้าที่เป็นร้านค้ากลางสำหรับส่วนย่อย
ผลลัพธ์ การเปลี่ยนแปลงนี้ช่วยให้คอมโพเนนต์สื่อสารถึงกันได้โดย
ตั้งค่าผลลัพธ์ส่วนย่อยแล้วรอฟังผลลัพธ์เหล่านั้น โดยไม่จำเป็นต้อง
องค์ประกอบเหล่านั้นให้อ้างอิงกันและกันได้โดยตรง
ส่งผลลัพธ์ระหว่างส่วนย่อย
หากต้องการส่งข้อมูลกลับไปยังส่วนย่อย A จากส่วนย่อย B ให้ตั้งค่า Listener ผลลัพธ์ก่อน
บน Fragment A คือ Fragment ที่รับผลลัพธ์ โทร
setFragmentResultListener()
ใน FragmentManager
ของส่วนย่อย A ดังที่แสดงในตัวอย่างต่อไปนี้
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. } }); }
![ส่วนย่อย b ส่งข้อมูลเพื่อแบ่งส่วน a โดยใช้ FragmentManager](https://developer.android.com/static/images/guide/fragments/fragment-a-to-b.png?authuser=2&hl=th)
FragmentManager
ในส่วนย่อย B ส่วนที่สร้างผลลัพธ์ ให้กำหนดผลลัพธ์
ใน FragmentManager
เดียวกันโดยใช้ requestKey
เดียวกัน คุณทำได้
ดังนั้นด้วยการใช้
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); } });
Fragment A จะได้รับผลลัพธ์และเรียกใช้ Callback ของผู้ฟัง
เมื่อส่วนย่อย
STARTED
คุณมี Listener เพียงรายการเดียวและผลลัพธ์สำหรับคีย์หนึ่งๆ ได้ หากคุณโทรหา
setFragmentResult()
มากกว่า 1 ครั้งสำหรับคีย์เดียวกัน และหาก Listener
ไม่ใช่ STARTED
ระบบจะแทนที่ผลลัพธ์ที่รอดำเนินการด้วยผลลัพธ์ที่อัปเดต
ผลลัพธ์
หากคุณตั้งค่าผลลัพธ์โดยไม่มี Listener ที่เกี่ยวข้องให้รับผลลัพธ์นั้น
ระบบจะจัดเก็บผลลัพธ์ไว้ใน FragmentManager
จนกว่าคุณจะตั้งค่า Listener ด้วย
คีย์เดียวกัน เมื่อผู้ฟังได้รับผลลัพธ์และเริ่มการทำงานของ
onFragmentResult()
Callback ระบบล้างผลลัพธ์แล้ว ลักษณะการทำงานนี้มี
ผลกระทบหลัก 2 ประการ ได้แก่
- ส่วนย่อยในกองหลังจะไม่ได้รับผลลัพธ์จนกว่าจะ
โดนระเบิดและเป็น
STARTED
- หากส่วนย่อยที่รอฟังผลลัพธ์คือ
STARTED
เมื่อตั้งค่าผลลัพธ์แล้ว Callback ของผู้ฟังจะเริ่มทำงานทันที
ผลการทดสอบส่วนย่อย
ใช้
FragmentScenario
เพื่อทดสอบการโทรไปยัง setFragmentResult()
และ setFragmentResultListener()
สร้างสถานการณ์สำหรับส่วนย่อยภายใต้การทดสอบโดยใช้
launchFragmentInContainer
หรือ
launchFragment
,
จากนั้นจึงเรียกใช้เมธอดที่ยังไม่ได้ทดสอบด้วยตนเอง
หากต้องการทดสอบ setFragmentResultListener()
ให้สร้างสถานการณ์ที่มี
ส่วนย่อยที่เรียกไปยัง setFragmentResultListener()
ถัดไป
โทรหา setFragmentResult()
โดยตรงและยืนยันผลลัพธ์:
@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")
}
}
}
หากต้องการทดสอบ setFragmentResult()
ให้สร้างสถานการณ์ที่มีส่วนย่อยที่ทำให้
โทรหา setFragmentResult()
ถัดไป โทรหา setFragmentResultListener()
โดยตรง และตรวจสอบผลลัพธ์ ดังนี้
@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))
}
}
}
ส่งผลลัพธ์ระหว่างส่วนหลักและส่วนย่อย
หากต้องการส่งผลลัพธ์จากส่วนย่อยไปยังระดับบนสุด
ใช้ getChildFragmentManager()
จากส่วนย่อยระดับบนแทน
getParentFragmentManager()
เมื่อโทรหา 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. } }); }
![ส่วนย่อยย่อยสามารถใช้ FragmentManager เพื่อส่งผลลัพธ์
ให้ผู้ปกครอง](https://developer.android.com/static/images/guide/fragments/pass-parent-child.png?authuser=2&hl=th)
FragmentManager
เพื่อส่งผลลัพธ์ไปยังระดับบนสุดส่วนย่อยย่อยจะกำหนดผลลัพธ์ใน FragmentManager
ผู้ปกครอง
จากนั้นจะได้รับผลลัพธ์เมื่อส่วนย่อยเป็น 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); } });
รับผลลัพธ์ในกิจกรรมของผู้จัด
หากต้องการรับผลลัพธ์ส่วนย่อยในกิจกรรมโฮสต์ ให้ตั้งค่า Listener ผลลัพธ์
บนตัวจัดการส่วนย่อยโดยใช้
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. } }); } }