Skip to content

Commit c0c3b9d

Browse files
Fix ViewPager2 FragmentStateAdapter based memory leaks
Note: There is another memory leak with BottomNavigationView to fix
1 parent 52fc3a0 commit c0c3b9d

File tree

62 files changed

+427
-186
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

62 files changed

+427
-186
lines changed

Tutorial6-0NavigationUI-ViewPager2/src/main/java/com/smarttoolfactory/tutorial6_0navigationui_viewpager2/viewpagerfragment/ViewPagerContainerFragment.kt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,4 +48,20 @@ class ViewPagerContainerFragment : Fragment() {
4848

4949
}
5050

51+
override fun onDestroyView() {
52+
53+
val viewPager2 = dataBinding?.viewPager
54+
55+
/*
56+
Without setting ViewPager2 Adapter it causes memory leak
57+
58+
https://stackoverflow.com/questions/62851425/viewpager2-inside-a-fragment-leaks-after-replacing-the-fragment-its-in-by-navig
59+
*/
60+
viewPager2?.let {
61+
it.adapter = null
62+
}
63+
64+
super.onDestroyView()
65+
}
66+
5167
}

Tutorial6-2NavigationUI-ViewPager2-NestedNavhost/src/main/java/com/smarttoolfactory/tutorial6_2navigationui_viewpager2_nestednavhost/viewpagerfragment/ViewPagerContainerFragment.kt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,22 @@ class ViewPagerContainerFragment : BaseDataBindingFragment<FragmentViewpagerCont
3838

3939
}
4040

41+
override fun onDestroyView() {
42+
43+
val viewPager2 = dataBinding?.viewPager
44+
45+
/*
46+
Without setting ViewPager2 Adapter it causes memory leak
47+
48+
https://stackoverflow.com/questions/62851425/viewpager2-inside-a-fragment-leaks-after-replacing-the-fragment-its-in-by-navig
49+
*/
50+
viewPager2?.let {
51+
it.adapter = null
52+
}
53+
54+
super.onDestroyView()
55+
}
56+
4157
override fun getLayoutRes(): Int = R.layout.fragment_viewpager_container
4258

4359

Tutorial6-4NavigationUI-ViewPager2-FragmentToolbar-NestedNavigation/src/main/java/com/smarttoolfactory/tutorial6_4_navigationui_viewpager_fragmenttoolbar_nested_navigation/viewpagerfragment/ViewPagerContainerFragment.kt

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import com.smarttoolfactory.tutorial6_4_navigationui_viewpager_fragmenttoolbar_n
1010

1111
class ViewPagerContainerFragment : BaseDataBindingFragment<FragmentViewpagerContainerBinding>() {
1212

13+
override fun getLayoutRes(): Int = R.layout.fragment_viewpager_container
14+
1315
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
1416
super.onViewCreated(view, savedInstanceState)
1517

@@ -24,7 +26,20 @@ class ViewPagerContainerFragment : BaseDataBindingFragment<FragmentViewpagerCont
2426

2527
}
2628

27-
override fun getLayoutRes(): Int = R.layout.fragment_viewpager_container
29+
override fun onDestroyView() {
2830

31+
val viewPager2 = dataBinding?.viewPager
32+
33+
/*
34+
Without setting ViewPager2 Adapter it causes memory leak
35+
36+
https://stackoverflow.com/questions/62851425/viewpager2-inside-a-fragment-leaks-after-replacing-the-fragment-its-in-by-navig
37+
*/
38+
viewPager2?.let {
39+
it.adapter = null
40+
}
41+
42+
super.onDestroyView()
43+
}
2944

3045
}

Tutorial6-5NavigationUI-ViewPager2-FragmentToolbar-MixedNavigation/src/main/java/com/smarttoolfactory/tutorial6_5navigationui_viewpager2_fragmenttoolbar_mixednavigation/viewpagerfragment/ViewPagerContainerFragment.kt

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ import com.smarttoolfactory.tutorial6_5navigationui_viewpager2_fragmenttoolbar_m
3232
*/
3333
class ViewPagerContainerFragment : BaseDataBindingFragment<FragmentViewpagerContainerBinding>() {
3434

35+
override fun getLayoutRes(): Int = R.layout.fragment_viewpager_container
36+
3537
private val appbarViewModel by activityViewModels<AppbarViewModel>()
3638

3739
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@@ -59,7 +61,20 @@ class ViewPagerContainerFragment : BaseDataBindingFragment<FragmentViewpagerCont
5961

6062
}
6163

62-
override fun getLayoutRes(): Int = R.layout.fragment_viewpager_container
64+
override fun onDestroyView() {
6365

66+
val viewPager2 = dataBinding?.viewPager
67+
68+
/*
69+
Without setting ViewPager2 Adapter it causes memory leak
70+
71+
https://stackoverflow.com/questions/62851425/viewpager2-inside-a-fragment-leaks-after-replacing-the-fragment-its-in-by-navig
72+
*/
73+
viewPager2?.let {
74+
it.adapter = null
75+
}
76+
77+
super.onDestroyView()
78+
}
6479

6580
}

Tutorial6-6NavigationUI-ViewPager2-Appbar-MixedNavigation-ViewModel/src/main/java/com/smarttoolfactory/tutorial6_6navigationui_viewpager2_appbar_mixednavigation_viewmodel/viewpagerfragment/ViewPagerContainerFragment.kt

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import com.smarttoolfactory.tutorial6_6navigationui_viewpager2_appbar_mixednavig
1414

1515
class ViewPagerContainerFragment : BaseDataBindingFragment<FragmentViewpagerContainerBinding>() {
1616

17+
override fun getLayoutRes(): Int = R.layout.fragment_viewpager_container
18+
1719
val appbarViewModel by activityViewModels<AppbarViewModel>()
1820

1921
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@@ -53,7 +55,20 @@ class ViewPagerContainerFragment : BaseDataBindingFragment<FragmentViewpagerCont
5355

5456
}
5557

56-
override fun getLayoutRes(): Int = R.layout.fragment_viewpager_container
58+
override fun onDestroyView() {
5759

60+
val viewPager2 = dataBinding?.viewPager
61+
62+
/*
63+
Without setting ViewPager2 Adapter it causes memory leak
64+
65+
https://stackoverflow.com/questions/62851425/viewpager2-inside-a-fragment-leaks-after-replacing-the-fragment-its-in-by-navig
66+
*/
67+
viewPager2?.let {
68+
it.adapter = null
69+
}
70+
71+
super.onDestroyView()
72+
}
5873

5974
}

Tutorial6-7NavigationUI-MemoryLeakCheck/src/main/java/com/smarttoolfactory/tutorial6_7navigationui_memoryleakcheck/viewpagerfragment/ViewPagerContainerFragment.kt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,17 @@ class ViewPagerContainerFragment : Fragment() {
4747
}
4848
}.attach()
4949
}
50+
51+
override fun onDestroyView() {
52+
53+
val viewPager2 = view?.findViewById<ViewPager2>(R.id.viewPager)
54+
55+
viewPager2?.let {
56+
it.adapter = null
57+
}
58+
59+
super.onDestroyView()
60+
61+
}
62+
5063
}

Tutorial7-2BNV-ViewPager2-ComplexArchitecture/src/main/java/com/smarttoolfactory/tutorial7_2bnv_viewpager2_complexarchitecture/adapter/ChildFragmentStateAdapter.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ class ChildFragmentStateAdapter(private val fragment: Fragment) :
3232
}
3333
}
3434

35+
36+
3537
} else {
3638
super.onFragmentMaxLifecyclePreUpdated(fragment, maxLifecycleState)
3739
}
@@ -42,8 +44,6 @@ class ChildFragmentStateAdapter(private val fragment: Fragment) :
4244

4345
override fun createFragment(position: Int): Fragment {
4446

45-
println("✅ ChildFragmentStateAdapter createFragment() position: $position")
46-
4747
return when (position) {
4848
0 -> HomeNavHostFragment()
4949
1 -> DashboardNavHostFragment()

Tutorial7-2BNV-ViewPager2-ComplexArchitecture/src/main/java/com/smarttoolfactory/tutorial7_2bnv_viewpager2_complexarchitecture/fragment/blankfragment/BaseDataBindingFragment.kt

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,12 @@ import androidx.fragment.app.Fragment
3030
abstract class BaseDataBindingFragment<ViewBinding : ViewDataBinding> : Fragment() {
3131

3232
/**
33-
* ## 🔥️ Data binding that is not null(or non-nullable) after [Fragment.onDestroyView] is causing leak canary to show **MEMORY LEAK** for this fragment when used in ViewPager2
33+
* * 🔥️ Data binding that is not null(or non-nullable) after [Fragment.onDestroyView]
34+
* causing leak canary to show data binding related **MEMORY LEAK**
35+
* for this fragment when used in [ViewPager2]
36+
*
37+
* * Even with null data binding [ViewPager2] still leaks with FragmentMaxLifecycleEnforcer
38+
* or it's false positive, not confirmed
3439
*/
3540
var dataBinding: ViewBinding? = null
3641

@@ -69,6 +74,11 @@ abstract class BaseDataBindingFragment<ViewBinding : ViewDataBinding> : Fragment
6974
override fun onDestroyView() {
7075
super.onDestroyView()
7176
println("🥵 ${this.javaClass.simpleName} #${this.hashCode()} onDestroyView()")
77+
78+
/*
79+
🔥 Without nullifying dataBinding ViewPager2 gets data binding related MEMORY LEAKS
80+
*/
81+
dataBinding = null
7282
}
7383

7484
override fun onDestroy() {

Tutorial7-2BNV-ViewPager2-ComplexArchitecture/src/main/java/com/smarttoolfactory/tutorial7_2bnv_viewpager2_complexarchitecture/fragment/viewpagerfragment/ViewPagerContainerFragment.kt

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,16 @@ package com.smarttoolfactory.tutorial7_2bnv_viewpager2_complexarchitecture.fragm
22

33
import android.os.Bundle
44
import android.view.View
5-
import androidx.fragment.app.activityViewModels
65
import com.google.android.material.tabs.TabLayoutMediator
76
import com.smarttoolfactory.tutorial7_2bnv_viewpager2_complexarchitecture.R
87
import com.smarttoolfactory.tutorial7_2bnv_viewpager2_complexarchitecture.adapter.ChildFragmentStateAdapter
9-
import com.smarttoolfactory.tutorial7_2bnv_viewpager2_complexarchitecture.fragment.blankfragment.BaseDataBindingFragment
108
import com.smarttoolfactory.tutorial7_2bnv_viewpager2_complexarchitecture.databinding.FragmentViewpagerContainerBinding
11-
import com.smarttoolfactory.tutorial7_2bnv_viewpager2_complexarchitecture.viewmodel.AppbarViewModel
9+
import com.smarttoolfactory.tutorial7_2bnv_viewpager2_complexarchitecture.fragment.blankfragment.BaseDataBindingFragment
1210

1311

1412
class ViewPagerContainerFragment : BaseDataBindingFragment<FragmentViewpagerContainerBinding>() {
1513

16-
val appbarViewModel by activityViewModels<AppbarViewModel>()
14+
override fun getLayoutRes(): Int = R.layout.fragment_viewpager_container
1715

1816
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
1917
super.onViewCreated(view, savedInstanceState)
@@ -40,16 +38,22 @@ class ViewPagerContainerFragment : BaseDataBindingFragment<FragmentViewpagerCont
4038
}
4139
}.attach()
4240

43-
// viewPager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
44-
//
45-
// override fun onPageSelected(position: Int) {
46-
// super.onPageSelected(position)
47-
// appbarViewModel.currentNavController.value =
48-
// appbarViewModel.currentNavController.value
49-
// }
50-
// })
5141

5242
}
5343

54-
override fun getLayoutRes(): Int = R.layout.fragment_viewpager_container
44+
override fun onDestroyView() {
45+
46+
val viewPager2 = dataBinding?.viewPager
47+
48+
/*
49+
Without setting ViewPager2 Adapter it causes memory leak
50+
51+
https://stackoverflow.com/questions/62851425/viewpager2-inside-a-fragment-leaks-after-replacing-the-fragment-its-in-by-navig
52+
*/
53+
viewPager2?.let {
54+
it.adapter = null
55+
}
56+
57+
super.onDestroyView()
58+
}
5559
}

Tutorial7-3BNV-ViewPager2-FragmentToolbar-MixedNavigation/src/main/java/com/smarttoolfactory/tutorial7_3bnv_viewpager2_fragmenttoolbar_mixednavigation/fragment/blankfragment/DashboardFragment1.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@ class DashboardFragment1 : BaseDataBindingFragment<FragmentDashboard1Binding>()
1313
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
1414
super.onViewCreated(view, savedInstanceState)
1515

16-
dataBinding!!.btnNextPage.setOnClickListener {
16+
val binding = dataBinding!!
17+
18+
binding!!.btnNextPage.setOnClickListener {
1719
findNavController().navigate(R.id.action_dashboardFragment1_to_dashboardFragment2)
1820
}
1921
}

Tutorial7-3BNV-ViewPager2-FragmentToolbar-MixedNavigation/src/main/java/com/smarttoolfactory/tutorial7_3bnv_viewpager2_fragmenttoolbar_mixednavigation/fragment/blankfragment/DashboardFragment2.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@ class DashboardFragment2 : BaseDataBindingFragment<FragmentDashboard2Binding>()
1313
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
1414
super.onViewCreated(view, savedInstanceState)
1515

16-
dataBinding!!.btnNextPage.setOnClickListener {
16+
val binding = dataBinding!!
17+
18+
binding.btnNextPage.setOnClickListener {
1719
findNavController().navigate(R.id.action_dashboardFragment2_to_dashboardFragment3)
1820
}
1921
}

Tutorial7-3BNV-ViewPager2-FragmentToolbar-MixedNavigation/src/main/java/com/smarttoolfactory/tutorial7_3bnv_viewpager2_fragmenttoolbar_mixednavigation/fragment/blankfragment/DashboardFragment3.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,9 @@ class DashboardFragment3 : BaseDataBindingFragment<FragmentDashboard3Binding>()
1313
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
1414
super.onViewCreated(view, savedInstanceState)
1515

16+
val binding = dataBinding!!
1617

17-
dataBinding!!.btnGoToStart.setOnClickListener {
18+
binding.btnGoToStart.setOnClickListener {
1819
findNavController().navigate(R.id.action_dashboardFragment3_to_dashboardFragment1)
1920
}
2021

Tutorial7-3BNV-ViewPager2-FragmentToolbar-MixedNavigation/src/main/java/com/smarttoolfactory/tutorial7_3bnv_viewpager2_fragmenttoolbar_mixednavigation/fragment/blankfragment/LoginFragment1.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@ class LoginFragment1 : BaseDataBindingFragment<FragmentLogin1Binding>() {
2424
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
2525
super.onViewCreated(view, savedInstanceState)
2626

27-
dataBinding!!.buttonLogin.setOnClickListener {
27+
val binding = dataBinding!!
28+
29+
binding.buttonLogin.setOnClickListener {
2830
findNavController().navigate(R.id.action_view_pager_dest_to_loginFragment2)
2931
}
3032

Tutorial7-3BNV-ViewPager2-FragmentToolbar-MixedNavigation/src/main/java/com/smarttoolfactory/tutorial7_3bnv_viewpager2_fragmenttoolbar_mixednavigation/fragment/blankfragment/NotificationFragment1.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@ class NotificationFragment1 : BaseDataBindingFragment<FragmentNotification1Bindi
1414
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
1515
super.onViewCreated(view, savedInstanceState)
1616

17-
dataBinding!!.btnNextPage.setOnClickListener {
17+
val binding = dataBinding!!
18+
19+
binding.btnNextPage.setOnClickListener {
1820
findNavController().navigate(R.id.action_notificationFragment1_to_notificationFragment2)
1921
}
2022

Tutorial7-3BNV-ViewPager2-FragmentToolbar-MixedNavigation/src/main/java/com/smarttoolfactory/tutorial7_3bnv_viewpager2_fragmenttoolbar_mixednavigation/fragment/blankfragment/NotificationFragment2.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@ class NotificationFragment2 : BaseDataBindingFragment<FragmentNotification2Bindi
1414
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
1515
super.onViewCreated(view, savedInstanceState)
1616

17-
dataBinding!!.btnNextPage.setOnClickListener {
17+
val binding =dataBinding!!
18+
19+
binding.btnNextPage.setOnClickListener {
1820
findNavController().navigate(R.id.action_notificationFragment2_to_notificationFragment3)
1921
}
2022
}

Tutorial7-3BNV-ViewPager2-FragmentToolbar-MixedNavigation/src/main/java/com/smarttoolfactory/tutorial7_3bnv_viewpager2_fragmenttoolbar_mixednavigation/fragment/blankfragment/NotificationFragment3.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@ class NotificationFragment3 : BaseDataBindingFragment<FragmentNotification3Bindi
1313
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
1414
super.onViewCreated(view, savedInstanceState)
1515

16-
dataBinding!!.btnGoToStart.setOnClickListener {
16+
val binding = dataBinding!!
17+
18+
binding.btnGoToStart.setOnClickListener {
1719
findNavController().navigate(R.id.action_notificationFragment3_to_notificationFragment1)
1820
}
1921
}

Tutorial7-3BNV-ViewPager2-FragmentToolbar-MixedNavigation/src/main/java/com/smarttoolfactory/tutorial7_3bnv_viewpager2_fragmenttoolbar_mixednavigation/fragment/blankfragment/PostHorizontalFragment.kt

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,6 @@ class PostHorizontalFragment : BaseDataBindingFragment<FragmentPostListHorizonta
2222
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
2323
super.onViewCreated(view, savedInstanceState)
2424

25-
dataBinding!!.recyclerView1.apply {
26-
layoutManager = LinearLayoutManager(requireContext())
27-
}
28-
29-
3025
bindViews()
3126
}
3227

Tutorial7-3BNV-ViewPager2-FragmentToolbar-MixedNavigation/src/main/java/com/smarttoolfactory/tutorial7_3bnv_viewpager2_fragmenttoolbar_mixednavigation/fragment/viewpagerfragment/ViewPagerContainerFragment.kt

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import com.smarttoolfactory.tutorial7_3bnv_viewpager2_fragmenttoolbar_mixednavig
1313

1414
class ViewPagerContainerFragment : BaseDataBindingFragment<FragmentViewpagerContainerBinding>() {
1515

16-
16+
override fun getLayoutRes(): Int = R.layout.fragment_viewpager_container
1717

1818
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
1919
super.onViewCreated(view, savedInstanceState)
@@ -45,5 +45,19 @@ class ViewPagerContainerFragment : BaseDataBindingFragment<FragmentViewpagerCont
4545

4646
}
4747

48-
override fun getLayoutRes(): Int = R.layout.fragment_viewpager_container
48+
override fun onDestroyView() {
49+
50+
val viewPager2 = dataBinding?.viewPager
51+
52+
/*
53+
Without setting ViewPager2 Adapter it causes memory leak
54+
55+
https://stackoverflow.com/questions/62851425/viewpager2-inside-a-fragment-leaks-after-replacing-the-fragment-its-in-by-navig
56+
*/
57+
viewPager2?.let {
58+
it.adapter = null
59+
}
60+
61+
super.onDestroyView()
62+
}
4963
}

0 commit comments

Comments
 (0)