برقراری ارتباط با قطعه‌ها

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

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

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

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

Share data using a ViewModel

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

هم‌رسانی داده‌ها با فعالیت میزبان

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

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 را ببینید.

Both your fragment and its host activity can retrieve a shared instance of a ViewModel with activity scope by passing the activity into the ViewModelProvider constructor. 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 را دریافت می‌کنند که به آن‌ها امکان می‌دهد با یکدیگر ارتباط برقرار کنند.

هم‌رسانی داده‌ها بین قطعه والد و فرزند

When working with child fragments, your parent fragment and its child fragments might need to share data with each other. برای هم‌رسانی داده‌ها بین این قطعه‌ها، از قطعه والد به‌عنوان دامنه 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

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

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

انتقال نتایج بین قطعه‌ها

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

کاتلین

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.
        }
    });
}
fragment b sends data to fragment a using a FragmentManager
شکل ۱. تکه B بااستفاده از FragmentManager داده‌ها را به تکه A ارسال می‌کند.

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

کاتلین

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

سپس «قطعه الف» نتیجه را دریافت می‌کند و وقتی قطعه 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 برای ارسال نتیجه
            به والد خود استفاده کند
شکل ۲ قطعه فرزند می‌تواند از 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.
            }
        });
    }
}