preface

A few days ago, Google released the Fragment Result API and the Activity Results API, which are used to replace the shortcomings of the previous method of communication between activities and fragments. Medium.com/androiddeve…

Pass data between fragments using the Fragment Result API:

To send data

@Override
@NonNull
public final @Override void setFragmentResult(@NonNull String requestKey, @NonNull Bundle result)
Copy the code

If FragmentB sends data to FragmentA, register a listener in FragmentA and send the data through the parent FragmentManager

Data reception:

@Override
@NonNull
public final @Override void setFragmentResultListener(
    @NonNull String requestKey,
    @NonNull LifecycleOwner lifecycleOwner,
    @NonNull FragmentResultListener listener
)
Copy the code

If you want to receive data in your Fragment, you can register a FragmentResultListener in the FragmentManager. RequestKey filters data sent by the FragmentManager

Set up a ResultListener setFragmentResultListener for a given requestKey. Once the given LifecycleOwner is at least STARTED, any result of setFragmentResult using the same requestKey setting will be passed to the callback. Callback will remain active until LifecycleOwner achieve DESTROYED status, or use the same call clearFragmentResultListener requestKey.

Sequence diagram analysis:

Lifecycle state and fragment order can be analyzed using a simplified sequence diagram:

  • If you listen for the Fragment life cycle, you can safely update the UI when new data is received, because the view is created (the onViewCreated() method is called before onStart()).

  • Before LifecycleOwner STARTED, only the latest value will be received if more than one data is passed:

  • When life cycle in LifecycleOwner DESTROYED, it will automatically remove the listener, if you want to manually remove the listener, you need to call FragmentManager. SetFragmentResultListener () method, Pass an empty FragmentResultListener

It’s clear from the sequence diagram that the overall flow can be seen, basically registering a listener in the FragmentManager, depending on the data the Fragment sends back.

Fragment data transfer at different levels

General fragment data transfer involves transfer between different levels, which can be divided into the following two types:

Two Fragment data passes at parent-child level

If you accept data sent by FragmentB in FragmentA, the FragmentA is the parent container of FragmentB, and they communicate through the Child FragmentManager

childFragmentManager.setFragmentResultListener(...)
Copy the code

Note that the Fragment that must be set for the listener uses the same FragmentManager.

Two Fragment data passes at the same level

If you accept data from a FragmentB in a FragmentA, and the FragmentA and FragmentB are at the same level, communicating via the parent FragmentManager, FragmentA must register the listener with parent FragmentManager

parentFragmentManager.setFragmentResultListener(...)
Copy the code

The source code parsing

Unlike the old Target Fragment Api, you can see that the listener is bound to the Fragment’s lifecycle, which brings the following benefits:

  • Passing data between fragments does not hold references to each other
  • Start processing data when the life cycle is ON_START to avoid unknown problems that can occur when the Fragment is in an unpredictable state
  • When the life cycle is ON_DESTROY, remove the listener

Let’s take a closer look at how Fragment and its lifecycle will bind and unbind data listening:

@Override public final void setFragmentResultListener(@NonNull final String requestKey, @NonNull final LifecycleOwner lifecycleOwner, @NonNull final FragmentResultListener listener) { final Lifecycle lifecycle = lifecycleOwner.getLifecycle(); / / destroyed directly return the if (lifecycle. GetCurrentState () = = lifecycle. State. The destroyed) {return; } LifecycleEventObserver observer = new LifecycleEventObserver() { @Override public void onStateChanged(@NonNull LifecycleOwner source, If (Event == Lifecycle.Event.ON_START) {// If (Event == Lifecycle. Bundle storedResult = mResults.get(requestKey); if (storedResult ! = null) {/ / if the results of a query is not null, trigger the callback listener. OnFragmentResult (requestKey storedResult); ClearFragmentResult (requestKey); If (event == life.event.on_destroy) {Lifecycle. RemoveObserver (this); mResultListeners.remove(requestKey); }}};Copy the code

You can see the above code does:

  • Lifecycle listens for changes in the Fragment’s Lifecycle
  • Start processing data when the life cycle is ON_START to avoid unknown problems that can occur when the Fragment is in an unpredictable state
  • When the life cycle is ON_DESTROY, remove the listener
  • A DESTROYED lifecycle is returned without processing

See how to receive data, let’s see how to send data:

@Override public final void setFragmentResult(@NonNull String requestKey, @ NonNull Bundle result) {/ / check if there is a listener to listen requestkey results FragmentManager. LifecycleAwareResultListener resultListener = mResultListeners.get(requestKey); // If the lifecycle starts, the callback if (resultListener! = null && resultListener.isAtLeast(Lifecycle.State.STARTED)) { resultListener.onFragmentResult(requestKey, result); } else {result mResults. Put (requestKey, result); }}Copy the code
  • Gets the listener registered with requestKey
  • When the life cycle is STARTED, data is sent
  • Otherwise, the data currently transferred is saved

What other methods are there for fragment communication?

What other communication methods are in fragments

Get an instance of the Fragment by using findFragmentById or associated Activity, and then call the Fragment’s public method:

  • The first step is to register the public method in the called MainFragment
Public void setData(List<String> dataList) {adapter.set(dataList); }Copy the code
  • The second step is to associate the activity with the actively called Fragment and call the public method after obtaining the MainFragment
/ / MenuFragment. Java file lv. SetOnItemClickListener (new AdapterView. OnItemClickListener () {@ Override public void onItemClick(AdapterView<? > parent, View view, int position, long id) { MainFragment mainFragment = (MainFragment) getActivity() .getSupportFragmentManager() .findFragmentByTag("mainFragment"); mainFragment.setData(mDataList.get(position)); }});Copy the code
  • Disadvantages: Fragments should not communicate directly with each other

Developer.android.com/topic/libra…

Interface callback to pass data between fragments:

  • Step1: create an interface and its corresponding set method in Menuragment:
Public interface OnDataTransferListener {public void dataTransfer(List<String> dataList); // Mainfragment. Java file public interface OnDataTransferListener {public void dataTransfer(List<String> dataList); } public void setOnDataTransferListener(OnDataTransferListener mListener) { this.mListener = mListener; }Copy the code
  • Step2: perform interface callback in the ListView item click event in MenuFragment
/ / MenuFragment. Java file lv. SetOnItemClickListener (new AdapterView. OnItemClickListener () {@ Override public void onItemClick(AdapterView<? > parent, View view, int position, long id) { if (mListener ! = null) { mListener.dataTransfer(mDataList.get(position)); }}});Copy the code
  • Step3: obtain the set method of the interface according to menuFragment in MainActivity, and transfer data in this method, as follows:
/ / in the MainActivity menuFragment. SetOnDataTransferListener (new menuFragment. OnDataTransferListener () {@ Override public void dataTransfer(List<String> data) { mainFragment.setData(data); }});Copy the code
  • Disadvantages: The Result API is more complex, please refer to the official documentation for details

Developer.android.com/training/ba…

Through Target fragments APIs (fragments. SetTargetFragment () & fragments. GetTargetFragment () method for the data transmission between fragments:

To quote Google’s official note:

Fragment.setTargetFragment()

Use case = 2 fragments hosted by the same activity.

Where startActivityForResult() establishes a relationship between 2 activities, setTargetFragment() defines the caller/called relationship between 2 fragments.

setTargetFragment(target) lets the “called” fragment know where to send the result. onActivityResult() is called manually in this case.

The call relationship can be represented with the following pseudocode:

public class Caller extends Fragment

     Fragment called = Called.newInstance()

     called.setTargetFragment(this)
Copy the code
public class Called extends DialogFragment

   intent = amazingData

   getTargetFragment().onActivityResult(  getTargetRequestCode(),,intent  )
Copy the code

In short, suppose A Fragment does something in B and wants to pass it back to A Fragment that has methods startActivityForResult() and onActivityResult (), but no setResult (). Is used to set the returned intent, so that we need by calling getActivity (). The setResult (ListTitleFragment REQUEST_DETAIL, intent);

(A,int t) (B,int t) (B,int t) (B,int t) (B,int t) (B,int t) (B,int t) Then call getTargetFragment().onActivityResult() in B’s code to pass the data back to A

  • Disadvantages: This method has been officially deprecated by Google. Target Fragment requires direct access to another fragment instance, which is very dangerous because you don’t know what state the Target fragment is in.

Pass Fragment data through the ViewModel container:

It is common for two or more fragments in an Activity to need to communicate with each other. Imagine a common case of a list-detail Fragment. Suppose you have a Fragment in which the user selects an item from a list and another Fragment that displays the contents of the selected item. This situation is not easy to handle because both fragments need to define some kind of interface description, and the owner Activity must bind the two together. In addition, both fragments must deal with situations where the other Fragment has not yet been created or is not visible.

You can use ViewModel objects to solve this common difficulty. The two fragments can handle such communication using their activity scope shared ViewModel, as shown in the following sample code:

public class SharedViewModel extends ViewModel { private final MutableLiveData<Item> selected = new MutableLiveData<Item>(); public void select(Item item) { selected.setValue(item); } public LiveData<Item> getSelected() { return selected; } } public class ListFragment extends Fragment { private SharedViewModel model; public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); model = new ViewModelProvider(requireActivity()).get(SharedViewModel.class); itemSelector.setOnClickListener(item -> { model.select(item); }); } } public class DetailFragment extends Fragment { public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); SharedViewModel model = new ViewModelProvider(requireActivity()).get(SharedViewModel.class); model.getSelected().observe(getViewLifecycleOwner(), item -> { // Update the UI. }); }}Copy the code

Notice that both fragments retrieve the activities that contain them. This way, when both fragments get their own ViewModelProvider, they receive the same SharedViewModel instance (whose scope is limited to that Activity).

This approach has the following advantages: The Activity does not need to perform any action, nor does it need to know anything about the communication. Fragments do not need to know each other except for the SharedViewModel convention. If one Fragment disappears, the other Fragment continues to work as usual. Each Fragment has its own life cycle and is not affected by the life cycle of the other Fragment. If one Fragment replaces another, the interface will continue to work without any problems.

conclusion

While using the Fragment Result APIs to replace the outdated Fragment Target APIs, the new APIs have some limitations when it comes to delivering bundles as data. Only simple data types, Serializable, and Parcelable data can be passed. The Fragment Result APIs allow applications to recover data from crashes without holding references to the other party, avoiding unknown problems when the Fragment is in an unpredictable state.

Considering the above communication methods, what do you think is the best way to communicate between fragments?

❤ ️ /Thank you for support /

That is all the content of this sharing. I hope it will help you

Don’t forget to share, like and bookmark your favorite things

Welcome to the public number programmer bus, from byte, shrimp, zhaoyin three brothers, share programming experience, technical dry goods and career planning, help you to avoid detours into the factory.