Skip to content

Commit 9bfc35c

Browse files
authored
fix(Android, Stack v5): fix lifecycle of prevent native dismiss callback (#3635)
## Description There is no memory leak currently, because PreventNativeDismissCallback does not retain the fragment (lifecycleOwner), it registers itself as a listener on it, so it's the fragment who retains it x2 (explicitly & in listener array). The callback unregisters when fragment is destroyed. The problem is however, that the callback registers as a listener in its init block, effectively doing it once per object lifetime, even if the fragment is reattached to the fragment manager the callback won't initialize again effectively breaking the prevent-native-dismiss behavior. ## Changes I fix this by setting up the callback in onCreate & destroying it in onDestroy. ## Visual documentation N/A ## Checklist - [x] Ensured that CI passes
1 parent cc2a3b3 commit 9bfc35c

1 file changed

Lines changed: 22 additions & 8 deletions

File tree

android/src/main/java/com/swmansion/rnscreens/gamma/stack/screen/StackScreenFragment.kt

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,15 @@ internal class StackScreenFragment(
1818
* Since each StackScreenFragment owns a PreventNativeDismissCallback & adds it to the
1919
* OnBackPressedDispatcher the callback should be enabled only when the top fragment is this fragment.
2020
*/
21-
private val preventNativeDismissBackPressedCallback =
22-
PreventNativeDismissCallback(this, stackScreen, canBeEnabled = true)
21+
private var preventNativeDismissBackPressedCallback: PreventNativeDismissCallback? = null
22+
private val requireNativeDismissBackPressedCallback
23+
get() = checkNotNull(preventNativeDismissBackPressedCallback) { "[RNScreens] Attempt to require nullish OnBackPressedCallback" }
2324

2425
private var isTopFragment: Boolean = false
2526

2627
override fun onCreate(savedInstanceState: Bundle?) {
2728
super.onCreate(savedInstanceState)
28-
requireActivity().onBackPressedDispatcher.addCallback(
29-
preventNativeDismissBackPressedCallback,
30-
)
29+
setupPreventNativeDismissCallback()
3130
}
3231

3332
override fun onCreateView(
@@ -53,30 +52,45 @@ internal class StackScreenFragment(
5352
super.onDestroy()
5453
Log.i("StackScreenFragment", "onDestroy")
5554
stackScreen.onDismiss()
56-
preventNativeDismissBackPressedCallback.remove()
55+
teardownPreventNativeDismissCallback()
5756
}
5857

5958
/**
6059
* Notifies this fragment that it has become "top fragment" in its fragment manager.
60+
* Call this only if the lifecycle of the fragment is at least at CREATED.
6161
*
6262
* This function should be idempotent.
6363
*/
6464
internal fun onBecomeTopFragment() {
6565
if (isTopFragment) return
6666

6767
isTopFragment = true
68-
preventNativeDismissBackPressedCallback.canBeEnabled = true
68+
requireNativeDismissBackPressedCallback.canBeEnabled = true
6969
}
7070

7171
/**
7272
* Notifies this fragment that it is not longer the "top fragment" in its fragment manager.
73+
* Call this only if the lifecycle of the fragment is at least at CREATED.
7374
*
7475
* This function should be idempotent.
7576
*/
7677
internal fun onResignTopFragment() {
7778
if (!isTopFragment) return
7879

7980
isTopFragment = false
80-
preventNativeDismissBackPressedCallback.canBeEnabled = false
81+
requireNativeDismissBackPressedCallback.canBeEnabled = false
82+
}
83+
84+
private fun setupPreventNativeDismissCallback() {
85+
preventNativeDismissBackPressedCallback =
86+
PreventNativeDismissCallback(this, stackScreen, canBeEnabled = false)
87+
requireActivity().onBackPressedDispatcher.addCallback(
88+
requireNativeDismissBackPressedCallback,
89+
)
90+
}
91+
92+
private fun teardownPreventNativeDismissCallback() {
93+
requireNativeDismissBackPressedCallback.remove()
94+
preventNativeDismissBackPressedCallback = null
8195
}
8296
}

0 commit comments

Comments
 (0)