التواصل مع الأجزاء

لإعادة استخدام الأجزاء، عليك إنشاؤها كمكونات مستقلة تمامًا تحدّد التصميم وسلوكها الخاصَّين. بمجرد تحديد هذه الأجزاء القابلة لإعادة الاستخدام، يمكنك ربطها بنشاط وربطها بمنطق التطبيق لإدراك واجهة المستخدم المركّبة الشاملة.

للتفاعل بشكل صحيح مع أحداث المستخدم ومشاركة معلومات الحالة، غالبًا ما تحتاج إلى وجود قنوات اتصال بين النشاط وأجزائه أو بين جزأين أو أكثر. لإبقاء الأجزاء مستقلّة، تجنَّب استخدام أجزاء تتواصل مباشرةً مع أجزاء أخرى أو مع نشاط المضيف.

توفّر مكتبة Fragment خيارَين للتواصل: واجهة برمجة تطبيقات ViewModel مشتركة وFragment Result API. يعتمد الخيار المقترَح على حالة الاستخدام. لمشاركة البيانات الدائمة مع واجهات برمجة التطبيقات المخصّصة، استخدِم السمة ViewModel. للحصول على نتيجة لمرة واحدة تضم بيانات يمكن وضعها في Bundle، استخدِم Fragment Result API.

تشرح الأقسام التالية طريقة استخدام ViewModel وFragment Result API للتواصل بين الأجزاء والأنشطة.

مشاركة البيانات باستخدام نموذج عرض

تكون 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 على مراحل نشاط 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 من Fragment، يتم تنفيذ كل FragmentManager 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.
        }
    });
}
يرسل الجزء ب البيانات إلى الجزء أ باستخدام FragmentManager
الشكل 1. يرسل الجزء "ب" البيانات إلى الجزء "أ" باستخدام FragmentManager.

في الجزء 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 لإرسال نتيجة
 إلى العنصر الرئيسي
الشكل 2 يمكن للجزء الفرعي استخدام 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);
    }
});

تلقّي نتائج في نشاط المضيف

لتلقّي نتيجة تجزئة في نشاط المضيف، اضبط أداة معالجة النتائج على مدير الأجزاء باستخدام 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.
            }
        });
    }
}