با قطعات ارتباط برقرار کنید

برای استفاده مجدد از قطعات، آنها را به عنوان اجزای کاملا مستقل بسازید که چیدمان و رفتار آنها را مشخص می کند. هنگامی که این قطعات قابل استفاده مجدد را تعریف کردید، می توانید آنها را با یک فعالیت مرتبط کنید و آنها را با منطق برنامه مرتبط کنید تا رابط کاربری ترکیبی کلی را درک کنید.

برای واکنش مناسب به رویدادهای کاربر و به اشتراک گذاری اطلاعات وضعیت، اغلب باید کانال های ارتباطی بین یک فعالیت و قطعات آن یا بین دو یا چند قطعه داشته باشید. برای اینکه قطعات مستقل باقی بمانند، قطعات به طور مستقیم با قطعات دیگر یا با فعالیت میزبان خود ارتباط برقرار نکنید .

کتابخانه Fragment دو گزینه برای ارتباط فراهم می کند: یک ViewModel مشترک و یک Fragment Result API. گزینه پیشنهادی بستگی به مورد استفاده دارد. برای اشتراک‌گذاری داده‌های پایدار با APIهای سفارشی، از ViewModel استفاده کنید. برای یک نتیجه یک بار مصرف با داده‌هایی که می‌توانند در یک Bundle قرار گیرند، از Fragment Result API استفاده کنید.

بخش های زیر به شما نشان می دهد که چگونه از ViewModel و Fragment Result API برای برقراری ارتباط بین قطعات و فعالیت های خود استفاده کنید.

داده ها را با استفاده از ViewModel به اشتراک بگذارید

ViewModel یک انتخاب ایده آل برای زمانی است که شما نیاز به اشتراک گذاری داده ها بین چند قطعه یا بین قطعات و فعالیت میزبان آنها دارید. اشیاء ViewModel داده های UI را ذخیره و مدیریت می کنند. برای اطلاعات بیشتر در مورد ViewModel ، به نمای کلی ViewModel مراجعه کنید.

داده ها را با فعالیت میزبان به اشتراک بگذارید

در برخی موارد، ممکن است لازم باشد داده ها را بین قطعات و فعالیت میزبان آنها به اشتراک بگذارید. برای مثال، ممکن است بخواهید یک مؤلفه UI سراسری را بر اساس تعامل درون یک قطعه تغییر دهید.

ItemViewModel زیر را در نظر بگیرید:

کاتلین

class ItemViewModel : ViewModel() {
    private val mutableSelectedItem = MutableLiveData<Item>()
    val selectedItem: LiveData<Item> get() = mutableSelectedItem

    fun selectItem(item: Item) {
        mutableSelectedItem.value = item
    }
}

جاوا

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 یا بازیابی آن در صورت وجود آن را انجام می دهد. هر دو مؤلفه می توانند این داده ها را مشاهده و اصلاح کنند.

کاتلین

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)
    }
}

جاوا

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 مشترک برای ارتباط استفاده کنند:

کاتلین

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)
}

جاوا

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 استفاده کنید:

کاتلین

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()})
    ...
}

جاوا

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 قرار داد:

کاتلین

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.
        }
    }
}

جاوا

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 نتیجه بگیرید

در برخی موارد، ممکن است بخواهید یک مقدار یکبار مصرف را بین دو قطعه یا بین یک قطعه و فعالیت میزبان آن ارسال کنید. برای مثال، ممکن است قطعه‌ای داشته باشید که کدهای QR را می‌خواند و داده‌ها را به قطعه قبلی ارسال می‌کند.

در Fragment نسخه 1.3.0 و بالاتر، هر FragmentManager FragmentResultOwner را پیاده سازی می کند. این بدان معنی است که یک FragmentManager می تواند به عنوان یک ذخیره مرکزی برای نتایج قطعه عمل کند. این تغییر به مؤلفه‌ها اجازه می‌دهد با تنظیم نتایج قطعه و گوش دادن به آن نتایج، بدون نیاز به ارجاع مستقیم آن مؤلفه‌ها به یکدیگر، با یکدیگر ارتباط برقرار کنند.

نتایج را بین قطعات ارسال کنید

برای بازگرداندن داده ها به قطعه A از قطعه B، ابتدا شنونده نتیجه را روی قطعه A، قطعه ای که نتیجه را دریافت می کند، تنظیم کنید. همانطور که در مثال زیر نشان داده شده است setFragmentResultListener() را در قسمت A's FragmentManager فراخوانی کنید:

کاتلین

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.
    }
}

جاوا

@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 داده ها را به قطعه a ارسال می کند
شکل 1. قطعه B داده ها را با استفاده از یک FragmentManager به قطعه A ارسال می کند.

در قطعه B، قطعه ای که نتیجه را تولید می کند، با استفاده از همان requestKey ، نتیجه را روی همان FragmentManager تنظیم کنید. می توانید این کار را با استفاده از API setFragmentResult() انجام دهید:

کاتلین

button.setOnClickListener {
    val result = "result"
    // Use the Kotlin extension in the fragment-ktx artifact.
    setFragmentResult("requestKey", bundleOf("bundleKey" to result))
}

جاوا

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))
        }
    }
}

نتایج را بین قطعات والد و فرزند ارسال کنید

برای ارسال نتیجه از قطعه فرزند به والدین، هنگام فراخوانی setFragmentResultListener() از getChildFragmentManager() از قطعه والد به جای getParentFragmentManager() استفاده کنید.

کاتلین

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.
    }
}

جاوا

@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 قطعه، والد نتیجه را دریافت می کند:

کاتلین

button.setOnClickListener {
    val result = "result"
    // Use the Kotlin extension in the fragment-ktx artifact.
    setFragmentResult("requestKey", bundleOf("bundleKey" to result))
}

جاوا

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() یک شنونده نتیجه را در مدیر قطعه تنظیم کنید.

کاتلین

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.
        }
    }
}

جاوا

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.
            }
        });
    }
}