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

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

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

توفّر مكتبة "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.
        }
    });
}
يرسل الجزء &quot;ب&quot; البيانات إلى الجزء &quot;أ&quot; باستخدام &quot;مدير FragmentManager&quot;
الشكل 1. يرسل الجزء B البيانات إلى الجزء A باستخدام 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.
            }
        });
    }
}