Can you really use fragment? Fragment FAQs and the new posture of fragment in Android x

Posted by BloodyMind on Tue, 04 Jan 2022 22:43:12 +0100

In the Android Jetpack component, fragment plays a very important role as one of the view controllers. However, due to its numerous bug s and dark pits, Square has such a blog: Advocating Against Android Fragments . On github Fragmentation It has a 9.4k star.

Now, the stable version of Android x fragment has come to 1.2.2. Let's summarize the common problems of fragment and the new gestures using fragment

Fragment FAQ

  • getSupportFragmentManager, getParentFragmentManager and getChildFragmentManager

  • FragmentStateAdapter and FragmentPagerAdapter

  • add and replace

  • Is this or viewlifecycle owner passed in when observing livedata

  • What are the risks of using simpleName as a fragment tag?

  • How to use Fragment to add multiple times in BottomBarNavigation and drawer?

  • Return stack

Getsupportfragmentmanager, getparentfragmentmanager and getChildFragmentManager

Fragment manager is Android X fragment. Abstract classes under app (deprecated ones are not considered) to create transaction s for adding, removing and replacing fragments

First, confirm that getSupportFragmentManager() is the method under FragmentActivity

getParentFragmentManager and getChildFragmentManager are Android X fragment. app. The method under fragment,

Including Android X getFragmentManager and requireFragmentManager have been deprecated after fragment 1.2.0

When this matter is clear, the next is very clear

  • getSupportFragmentManager is associated with an activity and can be regarded as the FragmentManager of an activity
  • getChildFragmentManager is associated with a fragment and can be regarded as the FragmentManager of a fragment
  • getParentFragmentManager is a little complicated. Normally, the FragmentManager of the activity to which the fragment is attached is returned. If the fragment is a child fragment of another fragment, the getChildFragmentManager of its parent fragment is returned

If we don't understand that, we can do a practice.

Create an activity, a parent fragment and a child fragment

// activity
class MyActivity : AppCompatActivity(R.layout.activity_main) {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        supportFragmentManager.commit {
            add<ParentFragment>(R.id.content)
        }
        Log.i("MyActivity", "supportFragmentManager $supportFragmentManager")
    }
}

class ParentFragment : Fragment(R.layout.fragment_parent) {
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        childFragmentManager.commit {
            add<ChildFragment>(R.id.content)
        }
        Log.i("ParentFragment", "parentFragmentManager $parentFragmentManager")
        Log.i("ParentFragment", "childFragmentManager $childFragmentManager")
    }
}

class ChildFragment : Fragment(R.layout.fragment_child) {
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        Log.i("ChildFragment", "parentFragmentManager $parentFragmentManager")
        Log.i("ChildFragment", "childFragmentManager $childFragmentManager")
    }
}
//log
I/MyActivity: supportFragmentManager FragmentManager{825dcef in HostCallbacks{14a13fc}}}
I/ParentFragment: parentFragmentManager FragmentManager{825dcef in HostCallbacks{14a13fc}}}
I/ParentFragment: childFragmentManager FragmentManager{df5de83 in ParentFragment{7cdd800}}}
I/ChildFragment: parentFragmentManager FragmentManager{df5de83 in ParentFragment{7cdd800}}}
I/ChildFragment: childFragmentManager FragmentManager{aba9afb in ChildFragment{5cea718}}}

therefore

  • getSupportFragmentManager should be used when using ViewPager, BottomSheetFragment and DialogFragment in an activity

  • getChildFragmentManager should be used when using ViewPager in a fragment

Incorrect use of activity's FragmentManager in a fragment will cause a memory leak. Why? Suppose you have some sub fragments managed by ViewPager in your fragments, and all these fragments are in the activity, because you are using the activity's fragment manager. Now, if you close your parent fragment, it will be closed, but will not be destroyed, because all child fragments are active and they are still in memory, resulting in a leak. It will leak not only the parent fragment, but also all the child fragments, because they cannot be purged from heap memory.

FragmentStateAdapter and FragmentPagerAdapter

The FragmentPagerAdapter stores the entire fragment in memory. If a large number of fragments are used in the ViewPager, memory overhead may increase. FragmentStatePagerAdapter only saves the savedInstanceState of the fragment and destroys all fragments when the focus is lost.

Let's look at two common questions

1. Refreshing the ViewPager does not take effect

Fragments in viewpager are managed through activity or fragment's fragment manager, which contains all instances of fragments in viewpager

Therefore, when the ViewPager is not refreshed, it is just an old fragment instance that the fragment manager still retains. You need to find out why fragment manger holds fragment instances.

2. Access the current fragment in the Viewpager

This is also a very common problem we encounter. If this happens, we usually create an array list of fragments inside the adapter, or try to access fragments with some tags. But there is another option. Both FragmentStateAdapter and FragmentPagerAdapter provide the method setPrimaryItem. It can be used to set the current fragment as follows:

  var fragment: ChildFragment? = null
  override fun setPrimaryItem(container: ViewGroup, position: Int, any: Any) {
    if (getChildFragment() != any)
    	fragment = any as ChildFragment
    super.setPrimaryItem(container, position, any)
   }
   fun getChildFragment(): ChildFragment? = fragment

	//use
	mAapter.getChildFragment()

How to select add and replace?

In our activity, we have a container containing fragment s.

Add adds only one fragment to the container. Suppose you add FragmentA and FragmentB to the container. The container will have FragmentA and FragmentB. If the container is FrameLayout, add one fragment on top of the other.

Replace will simply replace a fragment at the top of the container, so if I create fragment C and replace fragment B at the top, fragment B will be deleted from the container (onDestroy, unless you call addToBackStack and only onDestroyView), and fragment C will be at the top.

So how to choose? Replace deletes the existing fragment and adds a new one. This means that when you press the back button, the replaced fragment will be created and its onCreateView will be called. On the other hand, add keeps the existing fragment and adds a new fragment, which means that the existing fragments will be active and they will not be in the "paid" state. Therefore, when the back button is pressed, the existing fragment (the fragment before adding the new fragment) does not call onCreateView. For fragment lifecycle events, onPause, onResume, onCreateView and other lifecycle events will be called in the case of replace, but not in the case of add.

If you do not need to revisit the current fragment and the current fragment is no longer needed, use replace. In addition, if your application has memory limitations, consider using replace.

Is this or viewlifecycle owner passed in when observing livedata

Since Android fragment 1.2.0, a new Lint check has been added to ensure that you use getviewlifecycle owner() when viewing LiveData from onCreateView(), onViewCreated(), or onActivityCreated()

What are the risks of using simpleName as a fragment tag?

In general, we will use the simpleName of calss as the tag of the fragment

supportFragmentManager.commit {
	replace(R.id.content,MyFragment.newInstance("Fragment"),
            MyFragment::class.java.simpleName)
    addToBackStack(null)
}

It won't be a problem, but

val fragment = supportFragmentManager.findFragmentByTag(tag)

The fragment obtained in this way may not be the desired result.

Why?

There are two fragment s added. After confusion, they become

com.mypackage.FragmentA → com.mypackage.c.a
com.mypackage.FragmentB → com.mypackage.c.a.a

The above is confused with full name. What if it is simpleName?

com.mypackage.FragmentA → a
com.mypackage.FragmentB → a

WTF!

So try to use the full name or constant when setting the tag

How to use Fragment to add multiple times in BottomBarNavigation and drawer?

When we use bottombar navigation and NavigationDrawer, we usually see problems such as fragment reconstruction or adding the same fragment multiple times.

In this case, you can use show / hide instead of add or replace.

Return stack

If you want to press the return key to the previous fragment in a series of jumps in fragment, you should call the addToBackStack method before commit transaction.

//Use this extension androidx Fragment: fragment KTX: above 1.2.0
parentFragmentManager.commit {
	addToBackStack(null)
  	add<SecondFragment>(R.id.content)
}

Fragment uses a new pose

  • What are the useful extension functions of fragment KTX

  • Communication between fragment s and activities

  • Use FragmentContainerView as the fragment container

  • Use of FragmentFactory

  • Fragment return key interception

  • Fragment using ViewBinding

  • Fragment uses ViewPager2

  • No need to override onCreateView?

  • Use require_ () method

What are the useful extension functions of fragment KTX

1. FragmentManagerKt

//before
supportFragmentManager
    .beginTransaction()
    .add(R.id.content,Fragment1())
    .commit()

//after
supportFragmentManager.commit {
	add<Fragment1>(R.id.content)
}

2. FragmentViewModelLazyKt

//before
//Shared scope activity
val mViewMode1l = ViewModelProvider(requireActivity()).get(UpdateAppViewModel::class.java)
//Shared scope fragment internal
val mViewMode1l = ViewModelProvider(this).get(UpdateAppViewModel::class.java)

//after
//Shared scope activity
private val mViewModel by activityViewModels<MyViewModel>()
//Shared scope fragment internal
private val mViewModel by viewModel<MyViewModel>()

Note: viewmodelproviders of(this). get(MyViewModel.class); The method of is deprecated

Lifecycle extensions dependency package deprecated

Communication between fragment s and activities

There are many ways to communicate between fragments and between fragments and activities. android jetpack recommends that we use ViewModel + LiveData for processing

For the communication between fragments in the same activity, you can use the ViewModel with the scope of activity. The communication between activity and fragment is the same. Details can be moved Official Android Application Architecture Guide

Use FragmentContainerView as the fragment container

In the past, we used FrameLayout as the container of fragments. After AndroidX Fragment 1.2.0, we can use framecontainerview instead of FrameLayout.

It fixes some animation z-axis index order problems and window insertion scheduling, which means that the exit and entry transitions between two fragments will not overlap each other. Using FragmentContainerView will turn on the exit animation before entering the animation.

FragmentContainerView is a custom View designed specifically for fragment s, which inherits from FrameLayout

The android:name attribute allows you to add fragments, and the android:tag attribute can set the tag for fragments

 <androidx.fragment.app.FragmentContainerView
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:id="@+id/fragment_container_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:name="com.example.MyFragment"
        android:tag="my_tag">
 </androidx.fragment.app.FragmentContainerView>

Use of FragmentFactory

In the past, we could only instantiate the Fragment instance with its default empty constructor. This is because in some cases, such as configuration changes and application process re creation, the system needs to be re initialized. If it is not the default construction method, the system will not know how to reinitialize the Fragment instance.

Create a FragmentFactory to address this limitation. By providing it with the necessary parameters / dependencies required to instantiate the Fragment, it can help the system create the Fragment instance.

In the past, we instantiated the fragment and passed parameters using code similar to the following

class MyFragment : Fragment() {
    private lateinit var arg: String
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        arguments?.getString(ARG) ?: ""
    }
    companion object {
        fun newInstance(arg: String) =
            MyFragment().apply {
                arguments = Bundle().apply {
                    putString(ARG, arg)
                }
            }
    }
}

//use
val fragment = MyFragment.newInstance("my argument")

If your Fragment has a non empty constructor, you need to create a FragmentFactory to handle its initialization.

class MyFragmentFactory(private val arg: String) : FragmentFactory() {
    override fun instantiate(classLoader: ClassLoader, className: String): Fragment {
        if (className == MyFragment::class.java.name) {
            return MyFragment(arg)
        }
        return super.instantiate(classLoader, className)
    }
}

Fragments are managed by the FragmentManager, so naturally, the FragmentFactory needs to be added to the FragmentManager before it can be used.

So when to add the FragmentFactory to the FragmentManager?

Before the parent class calls Activity#onCreate() and Fragment#onCreate()

class HostActivity : AppCompatActivity() {
    private val customFragmentFactory = CustomFragmentFactory(Dependency())

    override fun onCreate(savedInstanceState: Bundle?) {
        supportFragmentManager.fragmentFactory = customFragmentFactory
        super.onCreate(savedInstanceState)
        // ...
    }
}

class ParentFragment : Fragment() {
    private val customFragmentFactory = CustomFragmentFactory(Dependency())

    override fun onCreate(savedInstanceState: Bundle?) {
        childFragmentManager.fragmentFactory = customFragmentFactory
        super.onCreate(savedInstanceState)
        // ...
    }
}

If your Fragment has a default empty constructor, you do not need to use the FragmentFactory. However, if your Fragment accepts parameters in its constructor, you must use FragmentFactory, otherwise it will throw a Fragment Instantiationexception because the default FragmentFactory that will be used will not know how to instantiate an instance of the Fragment.

Fragment return key interception

Sometimes you need to prevent users from returning to the previous level. In this case, you need to override the onbackpressed () method in the Activity. However, when you use Fragment, there is no direct way to intercept the return. There is no onBackPressed() method available in the Fragment class to prevent unexpected behavior when multiple fragments exist at the same time.

However, starting with AndroidX Activity 1.0.0, you can use OnBackPressedDispatcher to register OnBackPressedCallback anywhere you can access the Activity's code (for example, in a Fragment).

class MyFragment : Fragment() {
  override fun onAttach(context: Context) {
    super.onAttach(context)
    val callback = object : OnBackPressedCallback(true) {
      override fun handleOnBackPressed() {
        // Do something
      }
    }
    requireActivity().onBackPressedDispatcher.addCallback(this, callback)
  }
}

Fragment using ViewBinding

Viewbind is supported after Android Studio 3.6.0. See for the complete use process Deeply study the use of ViewBinding in include, merge, adapter, fragment and activity

class HomeFragment : Fragment() {
    private var _binding: FragmentHomeBinding? = null
    private val binding get() = _binding!!

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        _binding = FragmentHomeBinding.inflate(inflater, container, false)
        return binding.root
    }
    override fun onDestroyView() {
        _binding = null
    }
}

Fragment uses ViewPager2

ViewPager uses three abstract classes of adapter s, while ViewPager2 has only two

  • Pageradapter is used in ViewPager and recyclerview is used in ViewPager2 Adapter
  • FragmentPagerAdapter is used in ViewPager and FragmentStateAdapter is used in ViewPager2
  • FragmentStatePagerAdapter is used in ViewPager and FragmentStatePagerAdapter is used in ViewPager2
// A simple ViewPager adapter class for paging through fragments
class ScreenSlidePagerAdapter(fm: FragmentManager) : FragmentStatePagerAdapter(fm) {
    override fun getCount(): Int = NUM_PAGES

    override fun getItem(position: Int): Fragment = ScreenSlidePageFragment()
}

// An equivalent ViewPager2 adapter class
class ScreenSlidePagerAdapter(fa: FragmentActivity) : FragmentStateAdapter(fa) {
    override fun getItemCount(): Int = NUM_PAGES

    override fun createFragment(position: Int): Fragment = ScreenSlidePageFragment()
}

Using the change of TabLayout, TabLayout has been decoupled from ViewPager2. If TabLayout is used, dependency needs to be introduced

implementation "com.google.android.material:material:1.1.0"

For ViewPager2, the TabLayout layout should be at the same level as ViewPager2

<!-- A ViewPager element with a TabLayout -->
<androidx.viewpager.widget.ViewPager
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/pager"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.google.android.material.tabs.TabLayout
        android:id="@+id/tab_layout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

</androidx.viewpager.widget.ViewPager>

<!-- A ViewPager2 element with a TabLayout -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <com.google.android.material.tabs.TabLayout
        android:id="@+id/tab_layout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <androidx.viewpager2.widget.ViewPager2
        android:id="@+id/pager"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1" />

</LinearLayout>

When using ViewPager, TabLayout interacts with ViewPager. You need to call setupWithViewPager and override the getPageTitle method. ViewPager2 uses the TabLayoutMediator object instead

// Integrating TabLayout with ViewPager
class CollectionDemoFragment : Fragment() {
    ...
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        val tabLayout = view.findViewById(R.id.tab_layout)
        tabLayout.setupWithViewPager(viewPager)
    }
    ...
}

class DemoCollectionPagerAdapter(fm: FragmentManager) : FragmentStatePagerAdapter(fm) {

    override fun getCount(): Int  = 4

    override fun getPageTitle(position: Int): CharSequence {
        return "OBJECT ${(position + 1)}"
    }
    ...
}

// Integrating TabLayout with ViewPager2
class CollectionDemoFragment : Fragment() {
    ...
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        val tabLayout = view.findViewById(R.id.tab_layout)
        TabLayoutMediator(tabLayout, viewPager) { tab, position ->
            tab.text = "OBJECT ${(position + 1)}"
        }.attach()
    }
    ...
}

No need to override onCreateView?

After Android x fragment 1.1.0, you can use the constructor with layoutId as a parameter, so you don't need to override the onCreateView method

class MyActivity : AppCompatActivity(R.layout.my_activity)
class MyFragmentActivity: FragmentActivity(R.layout.my_fragment_activity)
class MyFragment : Fragment(R.layout.my_fragment)

Use require_ () method

Since Android fragment 1.2.2, a lint check has been added. It is recommended to use the associated require for fragment_ () method to get more descriptive error messages, instead of using checkNotNull(get_ ()), requinonnull (get_ ()), or get()! Applicable to all APIs including get and require fragment

For example, use requireActivity() instead of getActivity()

Android hot fix framework, plug-in framework, component framework, picture loading framework, network access framework, RxJava responsive programming framework, IOC dependency injection framework, recent architecture component Jetpack and other Android open source frameworks. System tutorial knowledge notes have been sorted into PDF e-books, see [GitHub]

end of document

In fact, it's not difficult to be a good programmer.

But how can I become a good programmer?

I think the biggest obstacle is that it is difficult to balance breadth and depth.

Basic courses of computer major, such as OS, database, network, algorithm, etc., are abstract and difficult to understand. If you don't learn them in college, it will be difficult to pick them up in the future.

It emphasizes both hands-on and abstraction, both of which are indispensable. But people who are good at thinking often like to plan and then move; People who are good at action often have no time to review and think.

It's torture for those who have to understand before they start. It often takes a year or two to suddenly understand a concept.

For beginners, it is difficult to distinguish between learning knowledge and configuration.

There is too much noise. I don't know what to learn.

In general, the simplest parts of programming are often of low value. If you avoid the difficult parts this time, you should understand them next time. You can't escape.