Public number: Byte array hope to help you 🤣🤣

Fragment is a component with a long history in Android. Launched in Android 3.0 (API level 11), Fragment has become one of the most commonly used components in Android development today

Originally, fragments were introduced to allow for more dynamic and flexible interface design on large screens (such as tablets), and were designed as a lightweight Activity. Fragment can be used to divide the entire interface into multiple scattered area blocks, and the same Fragment can be applied to multiple activities, so as to realize the modularization of the interface and improve the reusability. With the gradual upgrade of Android system, the system has more and more functions, so it is natural to add a lot of fragments and activities exactly the same API. For example, if you want to jump to an Activity and get the return value, both the Activity and Fragment add the startActivityForResult method. With runtime permissions in 6.0, the requestPermissions method is added; At the time of 8.0 a picture in picture mode, and then combined with the onPictureInPictureModeChanged method

As the system changes, the Fragment gradually becomes less and less lightweight, and the complex functions make it more and more complex, which also leads to numerous dark holes in the previous version. Framework layer in the android. App. Fragments and support package of the android support. The v4. App. The fragments were now abandoned no longer maintenance, also left many unresolved bug, So fragments have not been a favorite component for developers for a long time

Now, in the era of AndroidX and Jetpack, Google is finally rethinking the position of the Fragment and doing a lot of refactoring. To quote the official line: We want the Fragment to be a true core component that has predictable, reasonable behavior, doesn’t have random errors, and doesn’t break existing functionality. We hope to release version 2.0 of Fragment at some point in time, which will only include the new, usable apis. But until the time is right, we will gradually add new ones to existing fragments and discard old apis, and provide better alternatives to old functionality. Migrating to Fragment 2.0 is easy when no one is using deprecated apis anymore

This article introduces the aspects of AndroidX Fragment in the new era. It has written more than 10,000 words, including basic knowledge and new knowledge. Maybe it contains some knowledge points that you have not known before. Hope to help you 🤣🤣

All of the sample code in this article uses the current, most recent version of the following dependency libraries

dependencies {
    implementation "Androidx. Appcompat: appcompat: 1.3.1." "
    implementation "Androidx. Fragments, fragments: 1.3.6."
    implementation "Androidx. Fragments: fragments - KTX: 1.3.6." "
}
Copy the code

1, how to use

How to add a Fragment to an Activity

1. Declare Fragment

The Fragment now supports passing layoutId directly into the constructor and automatically completing the View inflate operation in the onCreateView method, so subclasses don’t have to override the onCreateView method

public class Fragment implements ComponentCallbacks.View.OnCreateContextMenuListener.LifecycleOwner.ViewModelStoreOwner.HasDefaultViewModelProviderFactory.SavedStateRegistryOwner.ActivityResultCaller {

    @LayoutRes
    private int mContentLayoutId;

    @ContentView
    public Fragment(@LayoutRes int contentLayoutId) {
        this(a); mContentLayoutId = contentLayoutId; }@MainThread
    @Nullable
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
                             @Nullable Bundle savedInstanceState) {
        if(mContentLayoutId ! =0) {
            return inflater.inflate(mContentLayoutId, container, false);
        }
        return null; }}Copy the code

Therefore, in the simplest case we only need one line of code to declare a Fragment subclass

class PlaceholderFragment : Fragment(R.layout.fragment_placeholder)
Copy the code

2. Add Fragment

Fragments generally need to be combined with FragmentActivity, and the AppCompatActivity we use everyday already inherits from FragmentActivity directly. In addition, while a Fragment can choose any ViewGroup as its container, the FragmentContainerView subclass FrameLayout is officially highly recommended because it fixes some of the problems with the Fragment performing transitions

You can declare the FragmentContainerView name attribute to specify the Fragment’s full name path, so that the Activity automatically instantiates and adds the Fragment when it loads the layout file


      
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/fragmentContainerView"
        android:name="github.leavesc.fragment.PlaceholderFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</LinearLayout>
Copy the code

If you want to use your code to actively inject fragments at the appropriate time, you can also use supportFragmentManager instead of declaring the Name attribute to actively add. In addition, when the Configuration Change occurs, the system will automatically restore and rebuild each Activity and Fragment, so we need to actively judge whether the current Activity is normally started. SavedInstanceState == NULL, then actively add the Fragment. Otherwise, the two fragments overlap

/ * * *@Author: leavesC
 * @Date: 2021/9/12 "*@Desc:
 * @Github: https://github.com/leavesC * /
class MyFragmentActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_my_fragment)
        if (savedInstanceState == null) {
            supportFragmentManager.commit {
                add(R.id.fragmentContainerView, PlaceholderFragment())
                setReorderingAllowed(true)
                addToBackStack(null)}}}}Copy the code

SupportFragmentManager.com MIT method is an extension of the fragments – KTX libraries provide method, in fact also just do to FragmentManager and FragmentTransaction layer encapsulation

public inline fun FragmentManager.commit(
    allowStateLoss: Boolean = false,
    body: FragmentTransaction. () - >Unit
) {
    val transaction = beginTransaction()
    transaction.body()
    if (allowStateLoss) {
        transaction.commitAllowingStateLoss()
    } else {
        transaction.commit()
    }
}
Copy the code

Most of the sample code in this article does not take into account Activity destruction and rebuilding, but you will need to consider it in real development

2. Life cycle

1, the initial

Whatever the direct carrier of a Fragment is, it must ultimately be hosted in an Activity. A Fragment contains many life-cycle callback methods that are directly influenced by the life cycle of the host Activity, but also come from other sources. For example, the direct carrier of a Fragment can be an Activity or a Fragment at a higher level. The Fragment will automatically change as the carrier’s life cycle changes. If the direct carrier is ViewPager2, the Fragment will change separately when you switch ViewPager2

There is a classic picture about the Fragment life cycle, which directly shows the mapping between activities and fragments in various life cycle states

Most of the Fragment lifecycle methods map to activities, but both have a clear sequence of lifecycle methods. Taking a Fragment added to an Activity via FragmentContainerView as an example, the lifecycle changes from the time the Activity is started to the time you press the back button to exit the page:

  • The onCreate method of the Activity calls the Fragment’s onAttach(Context), onAttach(Activity), and onCreate
  • The onStart method of the Activity calls the Fragment onCreateView, onViewCreated, onActivityCreated, onViewStateRestored, and onStart
  • The onResume method of the Activity is followed by the onResume method of the Fragment
  • The onPause method of the Activity calls the onPause of the Fragment
  • The onStop method of the Activity calls the Fragment onStop
  • The onDestroy method of the Activity calls the Fragment’s onDestroyView, onDestroy, and onDetach methods

As you can see, the entire life cycle is divided by the onResume method, which is called back to indicate that the view is active in the foreground, the Activity is the Fragment carrier, The Fragment’s onResume method can be called only after its onResume method has been called back. Therefore, there is no nested call relationship between the two methods. For other methods, a callback means that the Activity is inactive or about to be destroyed, and you need to call back to complete the Fragment method before ending it, hence the nested call relationship


In addition, if the Activity starts another Activity, its onSaveInstanceState method is called, which also nested calls to the Fragment method. But the sequence of this method and the onStop method varies from system to system version. In all versions prior to API 28, onSaveInstanceState() is called before onStop(); API 28+ is called the other way around

When an Activity is returned from the background to the foreground, the Fragment will go through the lifecycle method again if the Activity is destroyed and rebuilt. The onCreate, onCreateView, onActivityCreated, onViewStateRestored method Bundle values are not null. The values we inserted in the onSaveInstanceState method are preserved and we can rely on this Bundle to help rebuild the page


In addition, the onAttach(Activity) and onActivityCreated(Bundle) methods, originally designed to connect the Fragment logic to the carrier Activity, have been marked as deprecated. As a result, it is possible for developers to write logic strongly associated with the Activity. This coupling is now officially discouraged. The View logic should be handled in the onViewCreated method, and other initialization code should be handled in the onCreate method. The Fragment initialization logic should not rely on these two deprecated methods, and the Fragment should be more independent

If you really need to be notified of the Activity’s onCreate event, you can do this by adding a LifecycleObserver to the onAttach(Context) method

    override fun onAttach(context: Context) {
        super.onAttach(context)
        requireActivity().lifecycle.addObserver(object : DefaultLifecycleObserver {
            override fun onCreate(owner: LifecycleOwner) {
                owner.lifecycle.removeObserver(this)
                //TODO            }})}Copy the code

2. Rollback stack & transactions

The life cycle of a Fragment is more than just a linear process, or a Fragment wouldn’t always be ridiculed for being too complex and changeable. The Fragment life cycle starts with the onAttach method and ends with the onDetach method, which can be executed N times between onCreateView and onDestroyView, as shown in the following figure

The reason why N times between onCreateView and onDestroyView is possible is that the FragmentTransaction operation can remove or replace N times in succession. That is, load different fragments successively. The newly loaded Fragment will take the place of the old Fragment and move to the foreground, and the old Fragment’s View will be destroyed. If the loading of a new Fragment is added to the rollback stack, the old Fragment will be brought back to the foreground when the user hits the return key, and the onCreateView to onDestroyView method will be revisited

Here comes the concept of Fragment rollback. Suppose FragmentA is replaced for FragmentB with the following code, then when the user presses the return key, the FragmentB is destroyed, FragmentA will re-execute all methods between onCreateView and onDestroyView, returning to the foreground

supportFragmentManager.commit {
    setReorderingAllowed(true)
    addToBackStack(null)
    replace(
        R.id.fragmentContainerView, FragmentB()
    )
}
Copy the code

We can exit FragmentB by hitting the back key, so it intuitively looks like FragmentB is being added to the rollback stack, but instead of fragments being added, those containing fragments areaddToBackStack(String)Method for an entire transaction

This sentence seems abstract, but let me give you an example to help you understand

Add a Fragment 1 and replace it with a Fragment 2. The replace transaction calls addToBackStack(NULL) and the Fragment 2 will be destroyed when the return key is clicked. Fragment 1 reloads into the foreground page. General code and operation effect:

supportFragmentManager.commit {
    setReorderingAllowed(true)
    addToBackStack(null)
    add(R.id.fragmentContainerView, Fragment1())
}

supportFragmentManager.commit {
    setReorderingAllowed(true)
    addToBackStack(null)
    replace(R.id.fragmentContainerView, Fragment2())
}
Copy the code

The order in which the Fragment lifecycle methods are called from the add operation to the return key is roughly as follows, leaving out some of the methods

// Perform the add operation first
Fragment-1: onStart-start
Fragment-1: onResume-start

// Execute replace
Fragment-1: onPause-start
Fragment-1: onStop-start

Fragment-2: onAttach-context-start
Fragment-2: onCreate-start
Fragment-2: onResume-start

Fragment-1: onDestroyView-start

// Click the return key
Fragment-2: onPause-start
Fragment-2: onStop-start

Fragment-1: onCreateView-start
Fragment-1: onActivityCreated-start
Fragment-1: onViewStateRestored-start
Fragment-1: onStart-start
Fragment-1: onResume-start

Fragment-2: onDestroyView-start
Fragment-2: onDestroy-start
Fragment-2: onDetach-start
Copy the code

The calling relationship of the whole process can be summarized as follows:

  • When the replace operation is performed. Fragment 1 will be executed firstonStopFragment 1 has been switched to the background. The Fragment 2 lifecycle is then executed untilonResumeFragment 2 has been switched to the foreground. Fragment 1 will then be executedonDestroyViewFragment 1 is not only switched to the background, its View is also destroyed
  • When you hit the back key. Fragment 2 executes firstonStopFragment 2 has been switched to the background. Fragment 1 will execute the Fragment againonCreateView 到 onResumeBetween all means back in the foreground. The Fragment 2 lifecycle is then executed untilonDetachFragment 2 is completely destroyed and cannot be returned to the instance page

When the return key is hit, replace calls the addToBackStack(NULL) method, meaning that the transaction is added to the fallback stack. Therefore, it is the transaction that responds to the return key event, so replace is cancelled. The FragmentManager is responsible for restoring the view to its previous state, so the entire Fragment 2 instance is completely destroyed and Fragment 1 is brought back to the fore

Therefore, the FragmentTransaction rollback stack keeps the transaction, not the Fragment instance, and the transaction we committed to it responds to the return event. The response is to undo the transaction and restore it to its previous state


If the above example doesn’t seem clear enough, here’s another example

Add a Fragment 1 and replace it with a Fragment 2. The replace transaction does not call addToBackStack(NULL), so you need to click the return key twice to exit the Fragment 2. And the Activity exits with it. General code and operation effect:

supportFragmentManager.commit {
    setReorderingAllowed(true)
    addToBackStack(null)
    add(R.id.fragmentContainerView, Fragment1())
}

supportFragmentManager.commit {
    setReorderingAllowed(true)
    //addToBackStack(null)
    replace(R.id.fragmentContainerView, Fragment2())
}
Copy the code

From the add operation to hitting the return key twice, the order in which the two Fragments’ life-cycle methods are called is roughly as follows, leaving out some methods

// Perform the add operation first
Fragment-1: onStart-start
Fragment-1: onResume-start

// Execute replace
Fragment-1: onPause-start
Fragment-1: onStop-start

Fragment-2: onAttach-context-start
Fragment-2: onCreate-start
Fragment-2: onResume-start

Fragment-1: onDestroyView-start

// Click the return key
Fragment-1: onDestroy-start
Fragment-1: onDetach-start

// Click back again
Fragment-2: onPause-start
Fragment-2: onStop-start
Fragment-2: onDestroyView-start
Fragment-2: onDestroy-start
Fragment-2: onDetach-start
Copy the code
  • When you hit the back key the first time. Since the Replace transaction was not added to the rollback and the Add operation was, it is the Add operation that responds to the return event. Hitting the return key undoes the Add operation, so the Fragment 1 will executeonDetachFragment 2 is not affected
  • When you click the return key the second time, because the FragmentTransaction rollback is empty, the Activity that responded to the return event will exit, destroying Fragment 2 along with it

After reading these two examples, the reader should understand? In a rollback stack, the transaction that calls the addToBackStack(String) method is important, not the Fragment

3, FragmentLifecycle

In the earliest days, the term lifecycle for activities and fragments referred to whether their particular callback methods had already been executed, such as onCreate, onStart, onDestroy, etc. What is the current value of Lifecycle.State for both today

Both activities and Fragments implement the LifecycleOwner interface and both contain a Lifecycle object to mark their Lifecycle state, so we can listen on LiveData in both cases in a life-cycle binding manner. As in the following code, the this associated with textLiveData is the LifecycleOwner object, ensuring that data call-back is only received when the Fragment is active in the foreground

class PageFragment : Fragment() {

    private val pageViewModel by lazy {
        ViewModelProvider(this@PageFragment).get(PageViewModel::class.java).apply {
            textLiveData.observe(this@PageFragment, {})}}}Copy the code

Lifecycle.State contains five values, and FragmentLifecycle will flow through these five values, for example when switching to DESTROYED, which means onDestory(), onDetach() etc are called, The Fragment’s life cycle is now over

    public enum State {
        DESTROYED,
        INITIALIZED,
        CREATED,
        STARTED,
        RESUMED;
    }
Copy the code

4, FragmentViewLifecycle

Fragments are special to activities because the View object associated with them can be loaded and destroyed many times during a single FragmentLifecycle, so the life of a FragmentView is not actually in sync with the life of the Fragment

The Fragment also declares nine state values to mark its own lifecycle state, including a VIEW_CREATED one that indicates that the FragmentView has been created. This also shows that the life cycle density maintained inside the Fragment is much lower than that maintained within the FragmentLifecycle

static final int INITIALIZING = -1;          // Not yet attached.
static final int ATTACHED = 0;               // Attached to the host.
static final int CREATED = 1;                // Created.
static final int VIEW_CREATED = 2;           // View Created.
static final int AWAITING_EXIT_EFFECTS = 3;  // Downward state, awaiting exit effects
static final int ACTIVITY_CREATED = 4;       // Fully created, not started.
static final int STARTED = 5;                // Created and started, not resumed.
static final int AWAITING_ENTER_EFFECTS = 6; // Upward state, awaiting enter effects
static final int RESUMED = 7;                // Created started and resumed.
Copy the code

The state value switches to DESTROYED, which for FragmentViewLifecycle means that onDestroy, onDetach and other methods are called, and for FragmentViewLifecycle means that onDestroyView is called. Therefore FragmentViewLifecycle has a smaller span than FragmentLifecycle. In addition, FragmentViewLifecycle will be irreversible and cannot be changed again after switching to DESTROYED, whereas FragmentViewLifecycle will have a chance to change again after switching to DESTROYED. Because View objects can be loaded and destroyed multiple times, each load means the life cycle starts over

Fragment provides a getViewLifecycleOwner() method. Since FragmentViewLifecycle is provided, this method can only be called between onCreateView() and onDestroyView(). This can only be used before the FragmentView is created and destroyed, otherwise an exception will be thrown

    @Nullable
    FragmentViewLifecycleOwner mViewLifecycleOwner;

    @MainThread
    @NonNull
    public LifecycleOwner getViewLifecycleOwner(a) {
        if (mViewLifecycleOwner == null) {
            throw new IllegalStateException("Can't access the Fragment View's LifecycleOwner when "
                    + "getView() is null i.e., before onCreateView() or after onDestroyView()");
        }
        return mViewLifecycleOwner;
    }

Copy the code

FragmentViewLifecycle is very useful and we can use FragmentViewLifecycle in place of FragmentLifecycle in everyday development, Because FragmentLifecycle has some pitfalls that are not obvious, it can easily become buggy

Let me give you an example. Suppose our Fragment needs to listen for changes in a LiveData value in the ViewModel and set up the interface based on that value. Now it’s time to consider where in the Fragment to subscribe to LiveData. We already know that the onCreateView method can be executed multiple times from onDestoryView, so we should not put the listener in there, otherwise it will cause repeated subscriptions. At this point, one of two ways to think about it is to listen while declaring the global variable ViewModel or to listen in the onCreate method

    private val pageViewModel by lazy {
        ViewModelProvider(this).get(PageViewModel::class.java).apply {
            textLiveData.observe(this@PageFragment, Observer {
                //TODO}}})Copy the code
    private val pageViewModel by lazy {
        ViewModelProvider(this).get(PageViewModel::class.java)
    }

    override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState)
        pageViewModel.textLiveData.observe(this@PageFragment, Observer {
            //TODO})}Copy the code

Both of these methods avoid the double subscription problem, but there is another, more subtle problem: if the FragmentView is actually destroyed and rebuilt, the rebuilt FragmentView will not receive the data that textLiveData already has!

Again, the root cause of this problem is that the FragmentView life cycle is not synchronized with the Fragment. If the Fragment has already received a callback from textLiveData, the value of textLiveData has not changed after FragmentView is destroyed and rebuilt. The LifecycleOwner bound to textLiveData will still exist, so the rebuilt FragmentView will not receive textLiveData’s callbacks and will not be able to rebuild the page based on those callbacks

To solve this problem, use FragmentViewLifecycle. Since FragmentViewLifecycle ends at onDestoryView, the Observer is automatically removed at this point, So we can use viewLifecycleOwner directly in the onViewCreated method to listen for textLiveData and ensure that every time the FragmentView is rebuilt it will receive a callback

    private val pageViewModel by lazy {
        ViewModelProvider(this).get(PageViewModel::class.java)
    }
    
    override fun onViewCreated(view: View, savedInstanceState: Bundle?). {
        super.onViewCreated(view, savedInstanceState)
        pageViewModel.textLiveData.observe(viewLifecycleOwner, Observer {
            //TODO})}Copy the code

In most cases, the operations that we perform in fragments are strongly associated with FragmentView manipulation, and we can use FragmentViewLifecycle instead of FragmentLifecycle This ensures that events are called back only if the FragmentView is alive and active, and that the latest data is available every time the FragmentView is destroyed and rebuilt. For events that depend on the full life of the Fragment, continue to use FragmentLifecycle

The source code for Lifecycle and LiveData can be found in these two articles:

  • Jetpack (1) -Lifecycle
  • Jetpack (3) -LiveData source code in detail

5. Future plans

As you can see from the above, the Fragment life cycle is very complex. Whether or not to add a rollback stack has a big impact on the life cycle. There are also FragmentLifecycle and FragmentViewLifecycle for individual Fragment instances, which is one of the biggest reasons why fragments have been ridiculed. Fortunately, Google officials are now aware of the problem and have plans to simplify the process by merging the two

The following is a quote from Google:

Finally, there is the Fragment lifecycle. The current Fragment life cycle is very complex and contains two different sets of life cycles. The Fragment’s own life cycle starts when it is added to the FragmentManager and continues until it is removed and destroyed by the FragmentManager; Views contained in fragments have a completely separate life cycle. When your Fragment goes on the rollback stack, the view will be destroyed. But fragments will live on

So we came up with a bold idea: what if we put the two together? Destroying the Fragment when it is destroyed and rebuilding the Fragment directly when you want to rebuild the view greatly reduces the complexity of the Fragment. Methods such as FragmentFactory and state saving, previously used for onConfigrationChange, process death, and recovery, will be the default options in this case

Of course, the change will be huge. The way we’re handling it right now is by adding it to the FragmentActivity as an optional API. With this new API, you can open up a new world of simplified life cycles

3, setMaxLifecycle

In the past, fragments were used in conjunction with ViewPager. Due to the preloading mechanism of ViewPager, adjacent fragments would execute onResume when not visible to the user, triggering unnecessary business logic. Excess traffic consumption and performance consumption result

To solve these problems and implement lazy Fragment loading, we need to add token bits to onViewCreated, onResume, onHiddenChanged, setUserVisibleHint and other methods. Combine tag bits to know exactly if the Fragment is currently visible to the user, and only fire the associated business logic if it is visible. This implementation has long been popular and perfectly usable, but it’s really not elegant, we need to combine multiple conditions to get there, and calling back the onResume method when the Fragment is not visible is a common sense violation

The setUserVisibleHint method is now deprecated, and the setMaxLifecycle method is now officially recommended for more precise control over the Fragment lifecycle

    / * * *@deprecated If you are manually calling this method, use
     * {@link FragmentTransaction#setMaxLifecycle(Fragment, Lifecycle.State)} instead. If
     * overriding this method, behavior implemented when passing in <code>true</code> should be
     * moved to {@link Fragment#onResume()}, and behavior implemented when passing in
     * <code>false</code> should be moved to {@link Fragment#onPause()}.
     */
    @Deprecated
    public void setUserVisibleHint(boolean isVisibleToUser) {
        if(! mUserVisibleHint && isVisibleToUser && mState < STARTED && mFragmentManager ! =null && isAdded() && mIsCreated) {
            mFragmentManager.performPendingDeferredStart(
                    mFragmentManager.createOrGetFragmentStateManager(this)); } mUserVisibleHint = isVisibleToUser; mDeferStart = mState < STARTED && ! isVisibleToUser;if(mSavedFragmentState ! =null) {
            // Ensure that if the user visible hint is set before the Fragment has
            // restored its state that we don't lose the new valuemSavedUserVisibleHint = isVisibleToUser; }}Copy the code

The setMaxLifecycle method, as its name suggests, is used to set a maximum life-cycle state for your Fragment, and it is. The state parameter can only be CREATED, STARTED, or RESUMED

    @NonNull
    public FragmentTransaction setMaxLifecycle(@NonNull Fragment fragment,
            @NonNull Lifecycle.State state) {
        addOp(new Op(OP_SET_MAX_LIFECYCLE, fragment, state));
        return this;
    }
Copy the code

Under normal circumstances, we add a Fragment to an Activity and its lifecycle is executed directly into the onResume method

supportFragmentManager.commit {
    val fragment = FragmentLifecycleFragment.newInstance(
        fragmentTag = (tagIndex++).toString(),
        bgColor = Color.parseColor("#0091EA")
    )
    add(
        R.id.fragmentContainerView, fragment
    )
    setReorderingAllowed(true)
    addToBackStack(null)}Copy the code
Fragment-1: onAttach
Fragment-1: onCreate
Fragment-1: onCreateView
Fragment-1: onViewCreated
Fragment-1: onActivityCreated
Fragment-1: onViewStateRestored
Fragment-1: onStart
Fragment-1: onResume
Copy the code

If setMaxLifecycle(fragment, Lifecycle.state.created) is set, you will find that the fragment will only execute into the onCreate method and the fragment will not be displayed. After all, FragmentView is not created and is not mounted into the Activity

supportFragmentManager.commit {
    val fragment = FragmentLifecycleFragment.newInstance(
        fragmentTag = (tagIndex++).toString(),
        bgColor = Color.parseColor("#0091EA")
    )
    add(
        R.id.fragmentContainerView, fragment
    )
    setMaxLifecycle(fragment, Lifecycle.State.CREATED)
    setReorderingAllowed(true)
    addToBackStack(null)}Copy the code
Fragment-1: onAttach
Fragment-1: onCreate
Copy the code

If the Fragment has already called back onResume and setMaxLifecycle(Fragment, Lifecycle.state. STARTED) is set, You’ll see that the Fragment calls back onPause

Fragment-1: onAttach
Fragment-1: onCreate
Fragment-1: onCreateView
Fragment-1: onViewCreated
Fragment-1: onActivityCreated
Fragment-1: onViewStateRestored
Fragment-1: onStart
Fragment-1: onResume

Fragment-1: onPause
Copy the code

How do you understand the difference between these three cases?

In fact, setMaxLifecycle controls FragmentLifecycle, and the same FragmentLifecycle state value may correspond to different lifecycle methods, as shown in the following figure. OnResume corresponds to RESUMED, and we want FragmentLifecycle to be at its maximum value STARTED, so the Fragment can only perform onPause to change the RESUMED state to STARTED. If we start with a maximum value of STARTED, the Fragment will only execute from onAttach to onStart, and FragmentLifecycle will be STARTED as well

So, the setMaxLifecycle method will scale up or roll back the current FragmentLifecycle depending on the current state of the Fragment, choosing the appropriate callback route for the state switch

The setMaxLifecycle method is currently applied to the FragmentStateAdapter in ViewPager2. When a Fragment is cut out, its maximum state is set to STARTED, and when a Fragment is cut in, its maximum state is set to RESUMED, so that only visible fragments are called back to the onResume method. Fragments that are cut out call back onPause, ensuring that each Fragment is in the correct lifecycle state

        void updateFragmentMaxLifecycle(boolean dataSetChanged) {··· Fragment toResume =null;
            for (int ix = 0; ix < mFragments.size(); ix++) {
                long itemId = mFragments.keyAt(ix);
                Fragment fragment = mFragments.valueAt(ix);

                if(! fragment.isAdded()) {continue;
                }

                if(itemId ! = mPrimaryItemId) {/ / the key
                    transaction.setMaxLifecycle(fragment, STARTED);
                } else {
                    toResume = fragment; // itemId map key, so only one can match the predicate
                }
                fragment.setMenuVisibility(itemId == mPrimaryItemId);
            }
            if(toResume ! =null) { // in case the Fragment wasn't added yet
                / / the key
                transaction.setMaxLifecycle(toResume, RESUMED);
            }

            if (!transaction.isEmpty()) {
                transaction.commitNow();
            }
        }
Copy the code

4, FragmentFactory

Previously, a Fragment required developers to declare a constructor with no arguments for each subclass. This is because the system needs to restore and rebuild each Fragment when Configuration changes occur. If the Fragment contains one or more constructors with arguments, The system does not know which constructor to call and cannot automatically generate construction parameters, so it has to do the instantiation by reflecting the no-argument constructor, which requires that each subclass must have a no-argument constructor

To solve this problem, we have traditionally provided the instantiation entry by declaring a static factory method and passing the request parameters through the Bundle. When the system is rebuilding the Fragment, it will automatically inject the Bundle before reconstruction into the new instance to ensure that the request parameters are not missing

class FragmentFactoryFragment : BaseFragment(R.layout.fragment_fragment_factory) {

    companion object {

        private const val KEY_BG_COLOR = "keyBgColor"

        fun newInstance(bgColor: Int): FragmentFactoryFragment {
            return FragmentFactoryFragment().apply {
                arguments = Bundle().apply {
                    putInt(KEY_BG_COLOR, bgColor)
                }
            }
        }

    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?). {
        valbgColor = arguments? .getInt(KEY_BG_COLOR) ? :throw IllegalArgumentException()
        val flRoot = view.findViewById<View>(R.id.flRoot)
        flRoot.setBackgroundColor(bgColor)
    }

}
Copy the code

Instantiate the Fragment by reflecting the no-argument constructor and injecting arguments corresponds to the instantiate method in the Fragment

    public static Fragment instantiate(@NonNull Context context, @NonNull String fname,
            @Nullable Bundle args) {
        try {
            Class<? extends Fragment> clazz = FragmentFactory.loadFragmentClass(
                    context.getClassLoader(), fname);
            // reflection takes no arguments constructor
            Fragment f = clazz.getConstructor().newInstance();
            if(args ! =null) {
                args.setClassLoader(f.getClass().getClassLoader());
                / / into the Bundle
                f.setArguments(args);
            }
            return f;
        } catch (java.lang.InstantiationException e) {
            throw new InstantiationException("Unable to instantiate fragment " + fname
                    + ": make sure class name exists, is public, and has an"
                    + " empty constructor that is public", e);
        } catch (IllegalAccessException e) {
            throw new InstantiationException("Unable to instantiate fragment " + fname
                    + ": make sure class name exists, is public, and has an"
                    + " empty constructor that is public", e);
        } catch (NoSuchMethodException e) {
            throw new InstantiationException("Unable to instantiate fragment " + fname
                    + ": could not find Fragment constructor", e);
        } catch (InvocationTargetException e) {
            throw new InstantiationException("Unable to instantiate fragment " + fname
                    + ": calling Fragment constructor caused an exception", e); }}Copy the code

To solve the problem of not being able to freely define a parameter constructor, fragments now also provide a FragmentFactory to participate in the Fragment instantiation process

First, assume that the Fragment constructor needs a construction parameter of type int

class FragmentFactoryFragment(private val bgColor: Int) :
    BaseFragment(R.layout.fragment_fragment_factory) {

    override fun onViewCreated(view: View, savedInstanceState: Bundle?). {
        val flRoot = view.findViewById<View>(R.id.flRoot)
        flRoot.setBackgroundColor(bgColor)
    }

}
Copy the code

Inherit FragmentFactory, determine in the Instantiate method which Fragment is currently being instantiated, instantiate the Fragment based on bgColor and return

class MyFragmentFactory(private val bgColor: Int) : FragmentFactory() {

    override fun instantiate(classLoader: ClassLoader, className: String): Fragment {
        val clazz = loadFragmentClass(classLoader, className)
        if (clazz == FragmentFactoryFragment::class.java) {
            return FragmentFactoryFragment(bgColor)
        }
        return super.instantiate(classLoader, className)
    }

}
Copy the code

We then declare the Fragment Class to supportFragmentManager in our code, without explicitly instantiating it. The instantiation is done by MyFragmentFactory

class FragmentFactoryActivity : BaseActivity() {

    override val bind by getBind<ActivityFragmentFactoryBinding>()

    override fun onCreate(savedInstanceState: Bundle?). {
        // It needs to be called before super.oncreate because the Activity relies on the FragmentFactory to restore and rebuild the Fragment
        supportFragmentManager.fragmentFactory = MyFragmentFactory(Color.parseColor("#00BCD4"))
        super.onCreate(savedInstanceState)
        supportFragmentManager.commit {
            add(R.id.fragmentContainerView, FragmentFactoryFragment::class.java, null)
            setReorderingAllowed(true)
            disallowAddToBackStack()
        }
    }

}
Copy the code

The benefits of FragmentFactory are:

  • Pass construction parameters that would have been passed directly to the Fragment to the FragmentFactory so that the system can pass through the rebuildinstantiateMethod to re-instantiate a Fragment without worrying about the Fragment constructor
  • As long as the FragmentFactory contains the construction parameters required by all fragments, the same FragmentFactory can be used to instantiate many different fragments, eliminating the need to declare static factory methods for each Fragment. Fragment also eliminates the need to assign values to bundles, reducing the developer’s workload

FragmentFactory also has limitations:

  • Since we need to consider the Fragment recovery and reconstruction scenario, we are insuper.onCreateYou have to initialize it beforesupportFragmentManager.fragmentFactory, so that the Activity can re-instantiate the Fragment based on existing parameters when it is being rebuilt. This requires that we determine the FragmentFactory construction parameters at the beginning of the Fragment construction. This is not always possible in everyday development, as Fragment construction parameters may need to be generated dynamically

5. Activity Result API

There are two long-standing apis for activities and Fragments that are now obsolete:

  • startActivityForResult(),onActivityResult()
  • requestPermissions(),onRequestPermissionsResult()

Although this operation is not performed by two set of apis, but belong to initiate the request and await the outcome of the callback operation type, use new registerForActivityResult official recommendations to implement this kind of demand, Because the code implemented through the Activity Result API is more reusable, there is less template code and it is easier to test

Let’s do a little example. Assuming that there is a ActivityResultApiFragment and a ActivityResultApiTestActivity. The Fragment is used to enter the user name. After receiving the user name, it needs to pass the value to the Activity, which is responsible for querying the user’s location, and then sends the value back to the Fragment

As required, both userName and Location need to be stored in the Intent so that data can be exchanged between the two. Both userName and Location are stored in the ActivityResultContract. Corresponding GetLocation

  • The two generic declarations of GetLocation correspond to the data type of the request parameter and the data type of the return parameter
  • createIntentMethod is used to save the userName request parameters, and specify the ActivityResultApiTestActivity to start
  • parseResultGet ActivityResultApiTestActivity () method returns the Intent object, if judgment for successful state directly remove the location and return
class GetLocation : ActivityResultContract<String, String>() {

    companion object {

        private const val KEY_REQ_LOCATION = "keyReqLocation"

        private const val KEY_RES_LOCATION = "keyResLocation"

        fun getUserName(intent: Intent): String {
            returnintent.getStringExtra(KEY_REQ_LOCATION) ? :throw RuntimeException("userName must not be null")}fun putResultOk(location: String): Intent {
            return Intent().apply {
                putExtra(KEY_RES_LOCATION, location)
            }
        }

    }

    override fun createIntent(context: Context, input: String): Intent {
        val intent = Intent(context, ActivityResultApiTestActivity::class.java)
        intent.putExtra(KEY_REQ_LOCATION, input)
        return intent
    }

    override fun parseResult(resultCode: Int, intent: Intent?).: String? {
        if (resultCode == Activity.RESULT_OK) {
            returnintent? .getStringExtra(KEY_RES_LOCATION) }return null}}Copy the code

ActivityResultApiTestActivity according to our usual way to get to the request parameters, return values RESULT_OK status code and results

class ActivityResultApiTestActivity : BaseActivity() {

    override val bind by getBind<ActivityActivityResultApiTestBinding>()

    override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState)
        bind.btnFinish.setOnClickListener {
            val userName = GetLocation.getUserName(intent)
            setResult(Activity.RESULT_OK, GetLocation.putResultOk("$userName- guangzhou"))
            finish()
        }
    }

}
Copy the code

ActivityResultApiFragment registerForActivityResult way to register the GetLocation, If needed, the user name is passed in via getLocation.launch and getLocation is launched, and the result of the request is retrieved in the onActivityResult method

class ActivityResultApiFragment :
    Fragment(R.layout.fragment_activity_result_api) {

    private val getLocation: ActivityResultLauncher<String> =
        registerForActivityResult(
            GetLocation(),
            object : ActivityResultCallback<String> {
                override fun onActivityResult(result: String) {
                    Toast.makeText(activity, result, Toast.LENGTH_SHORT).show()
                }
            })

    override fun onViewCreated(view: View, savedInstanceState: Bundle?). {
        val btnStartActivity = view.findViewById<Button>(R.id.btnStartActivity)
        btnStartActivity.setOnClickListener {
            getLocation.launch("Yip Chi Chan")}}}Copy the code

Similarly, the official also provides one for the request permissions ActivityResultContract implementation class: RequestMultiplePermissions, through the Contract before we can finish in very simple way split and trouble to apply for the operation

class ActivityResultApiFragment :
    Fragment(R.layout.fragment_activity_result_api) {

    private val requestPermissions: ActivityResultLauncher<Array<String>> =
        registerForActivityResult(
            ActivityResultContracts.RequestMultiplePermissions(),
            object : ActivityResultCallback<Map<String, Boolean> > {override fun onActivityResult(result: Map<String.Boolean>) {
                    for (entry in result) {
                        Toast.makeText(activity, entry.key + "" + entry.value, Toast.LENGTH_SHORT)
                            .show()
                    }
                }
            })

    override fun onViewCreated(view: View, savedInstanceState: Bundle?). {
        valbtnRequestPermissions = view.findViewById<Button>(R.id.btnRequestPermissions) btnRequestPermissions.setOnClickListener {  requestPermissions.launch( arrayOf( Manifest.permission.ACCESS_NETWORK_STATE, Manifest.permission.ACCESS_FINE_LOCATION ) ) } } }Copy the code

The benefits of using the Activity Result API include:

  • The reference relationship between the originator and handler of the request is completely isolated. The originator is directly exposed to the ActivityResultContract without knowing who ultimately handles the request, so that the developer can replace the implementation logic of the ActivityResultContract at any time. Avoids coupling and is easy to test
  • Both the request origination logic and the processing logic can be reused, reducing repetitive code
  • Unified thestartActivityForResult å’Œ requestPermissionsRequest method, and provides a more concise and convenient callback method, avoiding the previous application logic and callback logic is completely separated

Fragment Result API

The Activity Result API is suitable for data communication between fragments and non-carrier activities. If there are multiple fragments or data communication between fragments and carrier activities, There are a number of possible implementations:

  • Fragments use the FragmentManager to get an instance of another Fragment and communicate with each other by calling its methods directly
  • The Fragment and Activity register callback interfaces with each other to implement data communication between the Fragment and the Activity carrier
  • Fragments use activities as a hub to pass events to other fragments to enable data communication between multiple fragments
  • Fragments and activities hold ViewModel instances of the same Activity level at the same time, so as to realize data communication between multiple fragments and between fragments and their host activities

All of the above four methods can fulfill the requirements, but there are some problems:

  • The first three methods result in strong coupling between fragments and between fragments and activities, and may lead to NPE because it is not clear what life cycle state the Fragment is in
  • The last approach is life-cycle safe, but viewModels are overkill for simple data communication scenarios, which are better suited for business scenarios with slightly larger data volumes

Now there’s a new option for Fragments: FragmentResult. Data communication using FragmentResult does not need to hold any Fragment or Activity references, just use the FragmentManager

Let’s do a little example. Declare an Activity and two fragments, and send data to the requestKey that the other party is listening to. The data is transmitted through the Bundle. Activities and fragments only need to send or listen to data for a specific requestKey, regardless of the source or destination of the data

  • The setFragmentResult method represents sending data to the requestKey
  • Is to monitor requestKey setFragmentResultListener said
const val requestKeyToActivity = "requestKeyToActivity"

private const val requestKeyFirst = "requestKeyFirst"

private const val requestKeySecond = "requestKeySecond"

class FragmentResultApiAFragment : Fragment(R.layout.fragment_fragment_result_api_a) {

    private val requestKeyFirst = "requestKeyFirst"

    private val requestKeySecond = "requestKeySecond"

    override fun onViewCreated(view: View, savedInstanceState: Bundle?). {
        super.onViewCreated(view, savedInstanceState)
        val btnSend = view.findViewById<Button>(R.id.btnSend)
        val btnSendMsgToActivity = view.findViewById<Button>(R.id.btnSendMsgToActivity)
        val tvMessage = view.findViewById<TextView>(R.id.tvMessage)
        btnSend.setOnClickListener {
            val bundle = Bundle()
            bundle.putString("fromFragmentResultApiAFragment", Random.nextInt().toString())
            // Pass data to the Fragment listening on requestKeyFirst
            parentFragmentManager.setFragmentResult(requestKeyFirst, bundle)
        }
        btnSendMsgToActivity.setOnClickListener {
            val bundle = Bundle()
            bundle.putString("fromFragmentResultApiAFragment", Random.nextInt().toString())
            // Pass data to the Activity listening on requestKeyToActivity
            parentFragmentManager.setFragmentResult(requestKeyToActivity, bundle)
        }
        // Listen on requestKeySecond
        parentFragmentManager.setFragmentResultListener(
            requestKeySecond,
            this,
            { requestKey, result ->
                tvMessage.text = "requestKey: $requestKey \n result: $result"}}})class FragmentResultApiBFragment : Fragment(R.layout.fragment_fragment_result_api_b) {

    override fun onViewCreated(view: View, savedInstanceState: Bundle?). {
        super.onViewCreated(view, savedInstanceState)
        val btnSend = view.findViewById<Button>(R.id.btnSend)
        val tvMessage = view.findViewById<TextView>(R.id.tvMessage)
        btnSend.setOnClickListener {
            val bundle = Bundle()
            bundle.putString("fromFragmentResultApiBFragment", Random.nextInt().toString())
            // Pass data to the Fragment listening on requestKeySecond
            parentFragmentManager.setFragmentResult(requestKeySecond, bundle)
        }
        // Listen on requestKeyFirst
        parentFragmentManager.setFragmentResultListener(
            requestKeyFirst,
            this,
            { requestKey, result ->
                tvMessage.text = "requestKey: $requestKey \n result: $result"}}})class FragmentResultApiActivity : BaseActivity() {

    override val bind by getBind<ActivityFragmentResultApiBinding>()

    override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState)
        supportFragmentManager.setFragmentResultListener(
            requestKeyToActivity,
            this,
            { requestKey, result ->
                bind.tvMessage.text = "requestKey: $requestKey \n result: $result"}}})Copy the code

The benefits of using the Fragment Result API are:

  • The Fragment Result API is not limited to fragments. As long as the Activity also has the same FragmentManager instance as the Fragment, You can also use the above methods to communicate data between activities and fragments. Note, however, that a requestKey can only be accessed by a single consumer. The later registered FragmentResultListener overwrites the first one
  • The Fragment Result API can also be used to pass data between parent and child fragments, but only if both hold the same FragmentManager instance. For example, if you want to pass data from a child Fragment to a parent Fragment, the parent Fragment should passgetChildFragmentManager()To invoke thesetFragmentResultListener()Rather thangetParentFragmentManager()
  • The sender of the data is only responsible for sending the data, and does not care or know how many recipients there are or whether the data is consumed. The receiver of the data is only responsible for receiving the data and does not care about or know how many data sources there are. Avoid referential relationships between activities and fragments, making each individual more independent
  • The data callback will be received only when the Activity and Fragment are at least in the STARTED state, that is, only when they are active. In the inactive state, only the latest values will be retained. When switching to the DESTROYED state, the listening will be automatically removed, ensuring the safety of the life cycle

Each carrier (Activity or Fragment) contains a FragmentManager of its own level to manage sub-fragments. Corresponding to supportFragmentManager for the Activity and childFragmentManager for the Fragment; Each sub-fragment also contains a FragmentManager from the carrier, corresponding to the Fragment’s parentFragmentManager

7, OnBackPressed

The Activity includes an onBackPressed method in response to the user hitting the return key, which controls whether to allow the user to exit the current page, but the Fragment does not. If you want the Fragment to be able to intercept the user’s return operation, you would have to create a callback between the Activity and the Fragment to do so, which undoubtedly creates a coupling between the two

Developers now have a better way to do this: OnBackPressedDispatcher allows any code logic that can access the Activity to intercept the onBackPressed method

We can add an OnBackPressedCallback callback to the Fragment. The value true means that the Fragment will intercept every return from the user and call back. We need to set it to false when appropriate, depending on the business logic, to release control over onBackPressed. In addition, the first argument to the addCallback method is the LifecycleOwner type, the current Fragment object, which ensures that the callback is only done if the Fragment’s life cycle is at least ON_START, The listener is automatically removed on ON_DESTROY, thus ensuring lifecycle safety

class PlaceholderFragment(private val sectionNumber: Int) :
    Fragment(R.layout.fragment_placeholder) {

    private val onBackPressedCallback = object : OnBackPressedCallback(true) {
        override fun handleOnBackPressed(a) {
            //TODO       }}override fun onAttach(context: Context) {
        super.onAttach(context)
        requireActivity().onBackPressedDispatcher.addCallback(this, onBackPressedCallback)
    }
    
}
Copy the code

Fragment 8

1. Listen for the lifecycle

When using fragments, we often specify a layout file for them to display a particular view. The Fragment can also not load any layout files, so it has no UI at all. However, if the Fragment is also attached to the Activity or parent Fragment, it can receive the corresponding lifecycle callback. At this point, the carrier’s lifecycle callback notification can be obtained indirectly in the form of complete ignorance of the upper layer

Some of the better known components and open source libraries implemented by this Fragment feature are Jetpack Lifecycle and Glide

Jetpack Lifecycle

Those of you familiar with Jetpack will know that we receive notification of changes in the state of an Activity’s Lifecycle through Lifecycle, Lifecycle will know the state of the Activity Lifecycle by injecting a uI-free Fragment into the Activity

Now the AppCompatActivity we use in everyday development will eventually inherit to ComponentActivity. The onCreate method for ComponentActivity looks like this:

    @SuppressLint("RestrictedApi")
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ReportFragment.injectIfNeededIn(this);
    }
Copy the code

The ReportFragment injectIfNeededIn() is a static method that takes the Android.app.Activity object as an input parameter, Inside this method, a ReportFragment without a UI is added to the Activity

     public static void injectIfNeededIn(Activity activity) {
        if (Build.VERSION.SDK_INT >= 29) {
            // On API 29+, we can register for the correct Lifecycle callbacks directly
            activity.registerActivityLifecycleCallbacks(
                    new LifecycleCallbacks());
        }
        // Add an invisible fragment to the activity to get a callback to the activity life cycle event
        android.app.FragmentManager manager = activity.getFragmentManager();
        if (manager.findFragmentByTag(REPORT_FRAGMENT_TAG) == null) {
            manager.beginTransaction().add(new ReportFragment(), REPORT_FRAGMENT_TAG).commit();
            // Hopefully, we are the first to make a transaction.manager.executePendingTransactions(); }}Copy the code

The ReportFragment indirectly gets callback notifications of Activity lifecycle events through its own callback methods

	@Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState); ... dispatch (Lifecycle. Event. ON_CREATE); }@Override
    public void onStart(a) {
        super.onStart(); ... dispatch (Lifecycle. Event. ON_START); }@Override
    public void onDestroy(a) {
        super.onDestroy(); dispatch(Lifecycle.Event.ON_DESTROY); ...}Copy the code

See this article for more details: Jetpack (1) -Lifecycle from the source

Glide

The images we load are usually displayed in the ImageView, which is mounted on an Activity or Fragment container. Loading images should be cancelled or stopped when the container is in the background or finished. Otherwise, precious system and network resources are wasted, and memory leaks or NPES may occur. So, the obvious question is, how does Glide know if the container is still active?

Similar to the Lifecycle approach used in Jetpack components, Glide indirectly retrits the Lifecycle state of the container via a UI-less Fragment

First, Glide includes a LifecycleListener that defines three event notification callbacks to inform the container of its active state: whether it is in the foreground, in the background, or out. Specific implementation class is ActivityFragmentLifecycle

public interface LifecycleListener {
  void onStart(a);
  void onStop(a);
  void onDestroy(a);
}
Copy the code

ActivityFragmentLifecycle used in SupportRequestManagerFragment the fragments to use (omitted part of the code). As you can see, in the three fragments lifecycle callback event, will inform ActivityFragmentLifecycle accordingly. So, whether or not the ImageView is the carrier of the Activity or fragments, we can to inject a UI SupportRequestManagerFragment, to monitor the carrier changes in the active state in the entire life cycle

public class SupportRequestManagerFragment extends Fragment {
    
  private static final String TAG = "SupportRMFragment";
    
  private final ActivityFragmentLifecycle lifecycle;

  public SupportRequestManagerFragment(a) {
    this(new ActivityFragmentLifecycle());
  }

  @VisibleForTesting
  @SuppressLint("ValidFragment")
  public SupportRequestManagerFragment(@NonNull ActivityFragmentLifecycle lifecycle) {
    this.lifecycle = lifecycle;
  }

  @NonNull
  ActivityFragmentLifecycle getGlideLifecycle(a) {
    return lifecycle;
  }

  @Override
  public void onStart(a) {
    super.onStart();
    lifecycle.onStart();
  }

  @Override
  public void onStop(a) {
    super.onStop();
    lifecycle.onStop();
  }

  @Override
  public void onDestroy(a) {
    super.onDestroy();
    lifecycle.onDestroy();
    unregisterFragmentWithRoot();
  }

  @Override
  public String toString(a) {
    return super.toString() + "{parent=" + getParentFragmentUsingHint() + "}"; }}Copy the code

See this article for more details: tripartite library source note (9) -Glide source details

2. Method proxy

Fragment also has a special use. We usually is through requestPermissions (), onRequestPermissionsResult () method to apply permissions, such as in a local authority to apply for operation, on the other, getting the result of application in a specific way the whole process is asynchronous and detached. Fragment contains many apis exactly the same as Activity, including these two permission application methods, so we can automatically mount a UI-free Fragment to the carrier and let the Fragment proxy the whole permission application process. The carrier can complete the whole operation in the way of streaming application

Here is a simple little example, the code is very simple, I won’t go into too much

/ * * *@Author: leavesC
 * @Date: 2021/9/5 * have closed@Desc:
 * @Github: https://github.com/leavesC * /
class RequestPermissionsFragment : Fragment() {

    companion object {

        private const val EXTRA_PERMISSIONS = "github.leavesC.extra.PERMISSIONS"

        private const val REQUEST_CODE = 100

        fun request(
            activity: FragmentActivity,
            permissions: Array<String>,
            onRequestResult: ((Array<String>, IntArray) - >Unit)) {
            val bundle = Bundle()
            bundle.putStringArray(EXTRA_PERMISSIONS, permissions)
            val requestPermissionsFragment = RequestPermissionsFragment()
            requestPermissionsFragment.arguments = bundle
            requestPermissionsFragment.onRequestResult = onRequestResult
            activity.supportFragmentManager.commit {
                setReorderingAllowed(true)
                addToBackStack(null)
                add(requestPermissionsFragment, null)}}}private val permissions bylazy { requireArguments().getStringArray(EXTRA_PERMISSIONS) ? :throw IllegalArgumentException()
    }

    private var onRequestResult: ((Array<String>, IntArray) -> Unit)? =
        null

    override fun onAttach(context: Context) {
        super.onAttach(context)
        requestPermissions(permissions, REQUEST_CODE)
    }

    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<String>,
        grantResults: IntArray
    ){ onRequestResult? .invoke(permissions, grantResults) parentFragmentManager.popBackStack() }override fun onDestroyView(a) {
        super.onDestroyView()
        onRequestResult = null}}Copy the code

You can then apply for the entire permission in the FragmentActivity or Fragment using the following method: retrieve the result directly in the callback

        RequestPermissionsFragment.request(
            fragmentActivity,
            arrayOf(
                Manifest.permission.ACCESS_FINE_LOCATION,
                Manifest.permission.CAMERA
            )
        ) { permissions: Array<String>,
            grantResults: IntArray ->
            permissions.forEachIndexed { index, s ->
                showToast("permission:" + s + "GrantResult." + grantResults[index])
            }
        }
Copy the code

9, the end

I feel that this article has clarified most of the knowledge points in Fragment. I have written more than 10,000 words one after another. There are basic knowledge and new knowledge, and maybe some knowledge points you have not known. After watching it, do you feel Fragment is becoming more and more useful now 🤣🤣

And finally, of course, all the sample code for this article: AndroidOpenSourceDemo