لإعادة استخدام الأجزاء، صممها كمكونات مستقلة تمامًا التي تحدد تخطيطها وسلوكها. بمجرد تحديد هذه أجزاء قابلة لإعادة الاستخدام، يمكنك ربطها بنشاط والتواصل بمنطق التطبيق لإدراك واجهة المستخدم المركبة بشكل عام.
وللتفاعل بشكل صحيح مع أحداث المستخدم ولمشاركة معلومات الحالة، غالبًا ما الحاجة إلى قنوات للتواصل بين النشاط الأجزاء أو بين جزأين أو أكثر. لإبقاء الأجزاء مستقلة بذاتها، لا تحتوي على أجزاء تتواصل مباشرةً مع الأجزاء الأخرى أو مع نشاط المضيف.
توفّر مكتبة "Fragment
" خيارَين للتواصل: نموذج مشترَك
ViewModel
والجزء
واجهة برمجة التطبيقات الخاصة بالنتائج ويعتمد الخيار المقترَح على حالة الاستخدام. للمشاركة
بيانات مستدامة مع واجهات برمجة تطبيقات مخصَّصة، استخدِم ViewModel
. بالنسبة
نتيجة لمرة واحدة مع البيانات التي يمكن وضعها في
Bundle
، استخدام الجزء
واجهة برمجة التطبيقات الخاصة بالنتائج
توضّح لك الأقسام التالية كيفية استخدام ViewModel
والجزء.
Result API للتواصل بين الأجزاء والأنشطة.
مشاركة البيانات باستخدام ViewModel
يُعد ViewModel
الخيار المثالي عندما
تحتاج إلى مشاركة البيانات بين
أجزاء متعددة أو بين الأجزاء ونشاط المضيف الخاص بها.
يتم تخزين عناصر من قِبل "ViewModel
"
وإدارة بيانات واجهة المستخدم. لمزيد من المعلومات عن ViewModel
، يُرجى الاطّلاع على
نظرة عامة على ViewModel.
مشاركة البيانات مع نشاط المضيف
في بعض الحالات، قد تحتاج إلى مشاركة البيانات بين الأجزاء نشاط المضيف الخاص بهم. على سبيل المثال، قد ترغب في تبديل واجهة مستخدم عامة يعتمد على تفاعل داخل جزء.
ننصحك باستخدام أنواع 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
أو استرداده إذا كان موجودًا بالفعل. يمكن لكلا المكونين ملاحظة
تعديل هذه البيانات.
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); }); } }
مشاركة البيانات بين الأجزاء
غالبًا ما يحتاج جزآن أو أكثر في نفس النشاط إلى التواصل مع بعضنا البعض. على سبيل المثال، تخيل أن جزءًا واحدًا يعرض قائمة أخرى تتيح للمستخدم تطبيق عوامل تصفية متنوعة على القائمة. يُذكر أن تنفيذ هذه الحالة ليس بالأمر الهين بدون أجزاء للتواصل بشكل مباشر، لكنها لم تعد بذاته. بالإضافة إلى ذلك، يجب أن يتعامل كلا الجزأين مع السيناريو حيث لا يتم إنشاء الجزء الآخر أو إظهاره حتى الآن.
يمكن أن تتشارك هذه الأجزاء ViewModel
باستخدام نطاق نشاطها.
للتعامل مع هذا الاتصال. ومن خلال مشاركة ViewModel
بهذه الطريقة،
الأجزاء لا تحتاج إلى معرفتها، والنشاط
ولا يحتاج إلى القيام بأي شيء لتسهيل الاتصال.
يوضح المثال التالي كيف يمكن لجزأين استخدام جزأين مشترك
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
، ما يمكّنها من التواصل
ذهابًا وإيابًا.
مشاركة البيانات بين جزء رئيسي وجزء فرعي
عند العمل على الأجزاء الثانوية، يبدأ الجزء الرئيسي والجزء الثانوي منه
قد تحتاج الأجزاء إلى مشاركة البيانات مع بعضها البعض. لمشاركة البيانات بين
في هذه الأجزاء، استخدِم الجزء الرئيسي على أنّه نطاق 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
في بعض الحالات، قد ترغب في تمرير قيمة لمرة واحدة بين جزأين أو بين جزء ونشاط المضيف الخاص به. على سبيل المثال، قد يكون لديك جزء يقرأ رموز الاستجابة السريعة، ويعيد البيانات إلى جزء سابق.
وفي الإصدار 1.3.0 والإصدارات الأحدث منه،
كل FragmentManager
implements
FragmentResultOwner
يعني ذلك أنّ واجهة FragmentManager
تعمل كمتجر مركزي للأجزاء.
نتائجك. يتيح هذا التغيير للمكونات التواصل مع بعضها البعض من خلال
وضع نتائج مجزّأة والاستماع إلى تلك النتائج، بدون الحاجة إلى
هذه العناصر إلى أن يكون لها إشارات مباشرة إلى بعضها البعض.
تمرير النتائج بين الأجزاء
لتمرير البيانات مرة أخرى إلى الجزء A من الجزء B، قم أولاً بتعيين أداة معالجة نتيجة
في الجزء A، الجزء الذي يتلقى النتيجة. اتصل
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، يحدد الجزء الذي ينتج النتيجة،
على FragmentManager
نفسها باستخدام نفس requestKey
. يمكنك إجراء ما يلي:
لذلك باستخدام
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); } });
تتلقى الجزء A بعد ذلك النتيجة وتنفذ استدعاء المستمع
بمجرد أن يكون الجزء
STARTED
لا يمكن أن يكون لديك سوى مستمع واحد ونتيجة لمفتاح معيّن. في حال الاتصال
setFragmentResult()
أكثر من مرة للمفتاح نفسه، وإذا كان المستمع
ليس STARTED
، يستبدل النظام أي نتائج معلقة ببياناتك المعدّلة
نتيجته.
إذا قمتَ بتعيين نتيجة بدون مستمع مناسب لتلقيها،
يتم تخزين النتيجة في FragmentManager
حتى تضبط أداة استماع
نفس المفتاح. وعندما يتلقّى المستمع نتيجة
onFragmentResult()
، سيتم محو النتيجة. هذا السلوك له
تأثيران رئيسيان:
- لا تتلقى الأجزاء في الحزمة الخلفية نتائج إلا بعد
فرقعة و
STARTED
. - إذا كان الجزء الذي يستمع إلى نتيجة هو
STARTED
عند ضبط النتيجة، يتم تنشيط معاودة اتصال المستمع ثم تنشيطها على الفور.
اختبار نتائج الأجزاء
استخدام
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
. أحد الوالدَين
ثم تتلقى النتيجة عندما يكون الجزء 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); } });
تلقّي النتائج في نشاط المضيف
لتلقي نتيجة مجزأة في نشاط المضيف، قم بتعيين أداة معالجة نتيجة
في مدير الأجزاء باستخدام
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. } }); } }