From 6416e503e21e662a22434bbb90e02af50ce71f6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Ska=C5=82ka?= Date: Thu, 31 Jul 2025 12:14:48 +0200 Subject: [PATCH 1/8] fix: replace workaround for handling config changes on Android Co-authored-by: bojanlozanovski77 --- .../java/com/fabricexample/MainActivity.kt | 7 +++++ .../com/swmansion/rnscreens/NoOpFragment.kt | 27 +++++++++++++++++++ .../rnscreens/RNScreensFragmentFactory.kt | 15 +++++++++++ 3 files changed, 49 insertions(+) create mode 100644 android/src/main/java/com/swmansion/rnscreens/NoOpFragment.kt create mode 100644 android/src/main/java/com/swmansion/rnscreens/RNScreensFragmentFactory.kt diff --git a/FabricExample/android/app/src/main/java/com/fabricexample/MainActivity.kt b/FabricExample/android/app/src/main/java/com/fabricexample/MainActivity.kt index 235fa3d487..0ff5782961 100644 --- a/FabricExample/android/app/src/main/java/com/fabricexample/MainActivity.kt +++ b/FabricExample/android/app/src/main/java/com/fabricexample/MainActivity.kt @@ -1,9 +1,11 @@ package com.fabricexample +import android.os.Bundle import com.facebook.react.ReactActivity import com.facebook.react.ReactActivityDelegate import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled import com.facebook.react.defaults.DefaultReactActivityDelegate +import com.swmansion.rnscreens.RNScreensFragmentFactory class MainActivity : ReactActivity() { @@ -19,4 +21,9 @@ class MainActivity : ReactActivity() { */ override fun createReactActivityDelegate(): ReactActivityDelegate = DefaultReactActivityDelegate(this, mainComponentName, fabricEnabled) + + override fun onCreate(savedInstanceState: Bundle?) { + supportFragmentManager.fragmentFactory = RNScreensFragmentFactory() + super.onCreate(savedInstanceState) + } } diff --git a/android/src/main/java/com/swmansion/rnscreens/NoOpFragment.kt b/android/src/main/java/com/swmansion/rnscreens/NoOpFragment.kt new file mode 100644 index 0000000000..8cf053b2ea --- /dev/null +++ b/android/src/main/java/com/swmansion/rnscreens/NoOpFragment.kt @@ -0,0 +1,27 @@ +package com.swmansion.rnscreens.gamma.helpers + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment + +class NoOpFragment: Fragment() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + // Immediately remove the fragment when it's created, to prevent Android system from trying to + // restore it later, which can cause crashes on configuration changes. + // Fixes: https://github.com/software-mansion/react-native-screens/issues/17 + parentFragmentManager.beginTransaction() + .remove(this) + .commitAllowingStateLoss() + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? = null +} diff --git a/android/src/main/java/com/swmansion/rnscreens/RNScreensFragmentFactory.kt b/android/src/main/java/com/swmansion/rnscreens/RNScreensFragmentFactory.kt new file mode 100644 index 0000000000..0a9bfb6807 --- /dev/null +++ b/android/src/main/java/com/swmansion/rnscreens/RNScreensFragmentFactory.kt @@ -0,0 +1,15 @@ +package com.swmansion.rnscreens + +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentFactory +import com.swmansion.rnscreens.gamma.helpers.NoOpFragment + +class RNScreensFragmentFactory : FragmentFactory() { + override fun instantiate(classLoader: ClassLoader, className: String): Fragment { + return if (className.startsWith(BuildConfig.LIBRARY_PACKAGE_NAME)) { + NoOpFragment(); + } else { + super.instantiate(classLoader, className) + } + } +} \ No newline at end of file From 494d0d7fed6e59336daa85c4237dcdac18e6f407 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Ska=C5=82ka?= Date: Thu, 31 Jul 2025 12:15:15 +0200 Subject: [PATCH 2/8] fix: add fix to the example app Co-authored-by: bojanlozanovski77 --- .../main/java/com/swmansion/rnscreens/example/MainActivity.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Example/android/app/src/main/java/com/swmansion/rnscreens/example/MainActivity.kt b/Example/android/app/src/main/java/com/swmansion/rnscreens/example/MainActivity.kt index 46b437ea07..97a4b3242b 100644 --- a/Example/android/app/src/main/java/com/swmansion/rnscreens/example/MainActivity.kt +++ b/Example/android/app/src/main/java/com/swmansion/rnscreens/example/MainActivity.kt @@ -5,6 +5,7 @@ import com.facebook.react.ReactActivity import com.facebook.react.ReactActivityDelegate import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled import com.facebook.react.defaults.DefaultReactActivityDelegate +import com.swmansion.rnscreens.RNScreensFragmentFactory class MainActivity : ReactActivity() { @@ -22,6 +23,7 @@ class MainActivity : ReactActivity() { DefaultReactActivityDelegate(this, mainComponentName, fabricEnabled) override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(null) + supportFragmentManager.fragmentFactory = RNScreensFragmentFactory() + super.onCreate(savedInstanceState) } } From dc17f55007646e2b02e1a60da66dc8d148c999ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Ska=C5=82ka?= Date: Thu, 31 Jul 2025 12:17:23 +0200 Subject: [PATCH 3/8] docs: update README installation steps Co-authored-by: bojanlozanovski77 --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 0538f6bb9f..fb82ad272d 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,7 @@ Please note that the override code should not be placed inside `MainActivityDele ```java import android.os.Bundle; +import com.swmansion.rnscreens.RNScreensFragmentFactory; public class MainActivity extends ReactActivity { @@ -43,7 +44,8 @@ public class MainActivity extends ReactActivity { //react-native-screens override @Override protected void onCreate(Bundle savedInstanceState) { - super.onCreate(null); + getSupportFragmentManager().setFragmentFactory(new RNScreensFragmentFactory()); + super.onCreate(savedInstanceState); } public static class MainActivityDelegate extends ReactActivityDelegate { @@ -59,6 +61,7 @@ public class MainActivity extends ReactActivity { ```kotlin import android.os.Bundle; +import com.swmansion.rnscreens.RNScreensFragmentFactory; class MainActivity: ReactActivity() { @@ -66,6 +69,7 @@ class MainActivity: ReactActivity() { //react-native-screens override override fun onCreate(savedInstanceState: Bundle?) { + supportFragmentManager.fragmentFactory = RNScreensFragmentFactory() super.onCreate(null); } } From d2eebc9d4377f6de2a18b922f216db223d71cf2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Ska=C5=82ka?= Date: Fri, 1 Aug 2025 14:03:18 +0200 Subject: [PATCH 4/8] fix: add review changes --- .../rnscreens/AutoRemovingFragment.kt | 38 +++++++++++++++++++ .../com/swmansion/rnscreens/NoOpFragment.kt | 27 ------------- .../rnscreens/RNScreensFragmentFactory.kt | 5 +-- 3 files changed, 40 insertions(+), 30 deletions(-) create mode 100644 android/src/main/java/com/swmansion/rnscreens/AutoRemovingFragment.kt delete mode 100644 android/src/main/java/com/swmansion/rnscreens/NoOpFragment.kt diff --git a/android/src/main/java/com/swmansion/rnscreens/AutoRemovingFragment.kt b/android/src/main/java/com/swmansion/rnscreens/AutoRemovingFragment.kt new file mode 100644 index 0000000000..8ae22ccfc3 --- /dev/null +++ b/android/src/main/java/com/swmansion/rnscreens/AutoRemovingFragment.kt @@ -0,0 +1,38 @@ +package com.swmansion.rnscreens + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment + +/** +* This class serves as a workaround to https://github.com/software-mansion/react-native-screens/issues/17. +* +* This fragment, when attached to the fragment manager & its state is progressed +* to `ON_CREATED`, attempts to detach itself from the parent fragment manager +* as soon as possible. +* +* Instances of this type should be created in place of regular screen fragments +* when Android restores fragments after activity / application restart. +* If done so, it's behaviour can prevent duplicated fragment instances, +* as React will render new ones on activity restart. +*/ +class AutoRemovingFragment: Fragment() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + // This is the first moment where we have access to non-null parent fragment manager, + // so that we can remove the fragment from the hierarchy. + parentFragmentManager.beginTransaction() + .remove(this) + .commitAllowingStateLoss() + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? = null +} diff --git a/android/src/main/java/com/swmansion/rnscreens/NoOpFragment.kt b/android/src/main/java/com/swmansion/rnscreens/NoOpFragment.kt deleted file mode 100644 index 8cf053b2ea..0000000000 --- a/android/src/main/java/com/swmansion/rnscreens/NoOpFragment.kt +++ /dev/null @@ -1,27 +0,0 @@ -package com.swmansion.rnscreens.gamma.helpers - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.fragment.app.Fragment - -class NoOpFragment: Fragment() { - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - // Immediately remove the fragment when it's created, to prevent Android system from trying to - // restore it later, which can cause crashes on configuration changes. - // Fixes: https://github.com/software-mansion/react-native-screens/issues/17 - parentFragmentManager.beginTransaction() - .remove(this) - .commitAllowingStateLoss() - } - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View? = null -} diff --git a/android/src/main/java/com/swmansion/rnscreens/RNScreensFragmentFactory.kt b/android/src/main/java/com/swmansion/rnscreens/RNScreensFragmentFactory.kt index 0a9bfb6807..1f18660835 100644 --- a/android/src/main/java/com/swmansion/rnscreens/RNScreensFragmentFactory.kt +++ b/android/src/main/java/com/swmansion/rnscreens/RNScreensFragmentFactory.kt @@ -2,14 +2,13 @@ package com.swmansion.rnscreens import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentFactory -import com.swmansion.rnscreens.gamma.helpers.NoOpFragment class RNScreensFragmentFactory : FragmentFactory() { override fun instantiate(classLoader: ClassLoader, className: String): Fragment { return if (className.startsWith(BuildConfig.LIBRARY_PACKAGE_NAME)) { - NoOpFragment(); + AutoRemovingFragment(); } else { super.instantiate(classLoader, className) } } -} \ No newline at end of file +} From 29de0d8dfffc2291f14c4be9646543ca702dd114 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Ska=C5=82ka?= Date: Thu, 28 Aug 2025 10:34:22 +0200 Subject: [PATCH 5/8] Fix lint --- .../rnscreens/AutoRemovingFragment.kt | 22 +++++++++---------- .../rnscreens/RNScreensFragmentFactory.kt | 10 +++++---- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/android/src/main/java/com/swmansion/rnscreens/AutoRemovingFragment.kt b/android/src/main/java/com/swmansion/rnscreens/AutoRemovingFragment.kt index 8ae22ccfc3..1a7ddb627a 100644 --- a/android/src/main/java/com/swmansion/rnscreens/AutoRemovingFragment.kt +++ b/android/src/main/java/com/swmansion/rnscreens/AutoRemovingFragment.kt @@ -8,24 +8,24 @@ import androidx.fragment.app.Fragment /** * This class serves as a workaround to https://github.com/software-mansion/react-native-screens/issues/17. -* -* This fragment, when attached to the fragment manager & its state is progressed -* to `ON_CREATED`, attempts to detach itself from the parent fragment manager +* +* This fragment, when attached to the fragment manager & its state is progressed +* to `ON_CREATED`, attempts to detach itself from the parent fragment manager * as soon as possible. -* -* Instances of this type should be created in place of regular screen fragments +* +* Instances of this type should be created in place of regular screen fragments * when Android restores fragments after activity / application restart. * If done so, it's behaviour can prevent duplicated fragment instances, * as React will render new ones on activity restart. */ -class AutoRemovingFragment: Fragment() { - +class AutoRemovingFragment : Fragment() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - // This is the first moment where we have access to non-null parent fragment manager, - // so that we can remove the fragment from the hierarchy. - parentFragmentManager.beginTransaction() + // This is the first moment where we have access to non-null parent fragment manager, + // so that we can remove the fragment from the hierarchy. + parentFragmentManager + .beginTransaction() .remove(this) .commitAllowingStateLoss() } @@ -33,6 +33,6 @@ class AutoRemovingFragment: Fragment() { override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle? + savedInstanceState: Bundle?, ): View? = null } diff --git a/android/src/main/java/com/swmansion/rnscreens/RNScreensFragmentFactory.kt b/android/src/main/java/com/swmansion/rnscreens/RNScreensFragmentFactory.kt index 1f18660835..67763622db 100644 --- a/android/src/main/java/com/swmansion/rnscreens/RNScreensFragmentFactory.kt +++ b/android/src/main/java/com/swmansion/rnscreens/RNScreensFragmentFactory.kt @@ -4,11 +4,13 @@ import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentFactory class RNScreensFragmentFactory : FragmentFactory() { - override fun instantiate(classLoader: ClassLoader, className: String): Fragment { - return if (className.startsWith(BuildConfig.LIBRARY_PACKAGE_NAME)) { - AutoRemovingFragment(); + override fun instantiate( + classLoader: ClassLoader, + className: String, + ): Fragment = + if (className.startsWith(BuildConfig.LIBRARY_PACKAGE_NAME)) { + AutoRemovingFragment() } else { super.instantiate(classLoader, className) } - } } From ac2799ce782a106fed0aded062de1d2cfc2d25ff Mon Sep 17 00:00:00 2001 From: Kacper Kafara Date: Sat, 30 Aug 2025 15:28:20 +0200 Subject: [PATCH 6/8] Move the factory & AutoRemovingFragment to dedicated package So that these do not reside in top level library namespace --- .../rnscreens/RNScreensFragmentFactory.kt | 16 ---------------- .../restoration}/AutoRemovingFragment.kt | 2 +- .../restoration/RNScreensFragmentFactory.kt | 13 +++++++++++++ 3 files changed, 14 insertions(+), 17 deletions(-) delete mode 100644 android/src/main/java/com/swmansion/rnscreens/RNScreensFragmentFactory.kt rename android/src/main/java/com/swmansion/rnscreens/{ => fragment/restoration}/AutoRemovingFragment.kt (96%) create mode 100644 android/src/main/java/com/swmansion/rnscreens/fragment/restoration/RNScreensFragmentFactory.kt diff --git a/android/src/main/java/com/swmansion/rnscreens/RNScreensFragmentFactory.kt b/android/src/main/java/com/swmansion/rnscreens/RNScreensFragmentFactory.kt deleted file mode 100644 index 67763622db..0000000000 --- a/android/src/main/java/com/swmansion/rnscreens/RNScreensFragmentFactory.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.swmansion.rnscreens - -import androidx.fragment.app.Fragment -import androidx.fragment.app.FragmentFactory - -class RNScreensFragmentFactory : FragmentFactory() { - override fun instantiate( - classLoader: ClassLoader, - className: String, - ): Fragment = - if (className.startsWith(BuildConfig.LIBRARY_PACKAGE_NAME)) { - AutoRemovingFragment() - } else { - super.instantiate(classLoader, className) - } -} diff --git a/android/src/main/java/com/swmansion/rnscreens/AutoRemovingFragment.kt b/android/src/main/java/com/swmansion/rnscreens/fragment/restoration/AutoRemovingFragment.kt similarity index 96% rename from android/src/main/java/com/swmansion/rnscreens/AutoRemovingFragment.kt rename to android/src/main/java/com/swmansion/rnscreens/fragment/restoration/AutoRemovingFragment.kt index 1a7ddb627a..c475e86ea5 100644 --- a/android/src/main/java/com/swmansion/rnscreens/AutoRemovingFragment.kt +++ b/android/src/main/java/com/swmansion/rnscreens/fragment/restoration/AutoRemovingFragment.kt @@ -1,4 +1,4 @@ -package com.swmansion.rnscreens +package com.swmansion.rnscreens.fragment.restoration import android.os.Bundle import android.view.LayoutInflater diff --git a/android/src/main/java/com/swmansion/rnscreens/fragment/restoration/RNScreensFragmentFactory.kt b/android/src/main/java/com/swmansion/rnscreens/fragment/restoration/RNScreensFragmentFactory.kt new file mode 100644 index 0000000000..9eede9b005 --- /dev/null +++ b/android/src/main/java/com/swmansion/rnscreens/fragment/restoration/RNScreensFragmentFactory.kt @@ -0,0 +1,13 @@ +package com.swmansion.rnscreens.fragment.restoration + +class RNScreensFragmentFactory : androidx.fragment.app.FragmentFactory() { + override fun instantiate( + classLoader: ClassLoader, + className: String, + ): androidx.fragment.app.Fragment = + if (className.startsWith(com.swmansion.rnscreens.BuildConfig.LIBRARY_PACKAGE_NAME)) { + AutoRemovingFragment() + } else { + super.instantiate(classLoader, className) + } +} From 929a42e1c7772a7f21fdcb15360b781c0566374f Mon Sep 17 00:00:00 2001 From: Kacper Kafara Date: Sat, 30 Aug 2025 15:29:07 +0200 Subject: [PATCH 7/8] Update imports in example apps --- .../main/java/com/swmansion/rnscreens/example/MainActivity.kt | 2 +- .../android/app/src/main/java/com/fabricexample/MainActivity.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Example/android/app/src/main/java/com/swmansion/rnscreens/example/MainActivity.kt b/Example/android/app/src/main/java/com/swmansion/rnscreens/example/MainActivity.kt index 97a4b3242b..5fbec0223e 100644 --- a/Example/android/app/src/main/java/com/swmansion/rnscreens/example/MainActivity.kt +++ b/Example/android/app/src/main/java/com/swmansion/rnscreens/example/MainActivity.kt @@ -5,7 +5,7 @@ import com.facebook.react.ReactActivity import com.facebook.react.ReactActivityDelegate import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled import com.facebook.react.defaults.DefaultReactActivityDelegate -import com.swmansion.rnscreens.RNScreensFragmentFactory +import com.swmansion.rnscreens.fragment.restoration.RNScreensFragmentFactory class MainActivity : ReactActivity() { diff --git a/FabricExample/android/app/src/main/java/com/fabricexample/MainActivity.kt b/FabricExample/android/app/src/main/java/com/fabricexample/MainActivity.kt index 0ff5782961..55745c2121 100644 --- a/FabricExample/android/app/src/main/java/com/fabricexample/MainActivity.kt +++ b/FabricExample/android/app/src/main/java/com/fabricexample/MainActivity.kt @@ -5,7 +5,7 @@ import com.facebook.react.ReactActivity import com.facebook.react.ReactActivityDelegate import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled import com.facebook.react.defaults.DefaultReactActivityDelegate -import com.swmansion.rnscreens.RNScreensFragmentFactory +import com.swmansion.rnscreens.fragment.restoration.RNScreensFragmentFactory class MainActivity : ReactActivity() { From 40c4e481fd9a3ab23f5e968b9a7b8dac1de57ff3 Mon Sep 17 00:00:00 2001 From: Kacper Kafara Date: Sat, 30 Aug 2025 15:34:32 +0200 Subject: [PATCH 8/8] Update installation instructions --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index fb82ad272d..34ed488bc8 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ Please note that the override code should not be placed inside `MainActivityDele ```java import android.os.Bundle; -import com.swmansion.rnscreens.RNScreensFragmentFactory; +import com.swmansion.rnscreens.fragment.restoration.RNScreensFragmentFactory; public class MainActivity extends ReactActivity { @@ -61,7 +61,7 @@ public class MainActivity extends ReactActivity { ```kotlin import android.os.Bundle; -import com.swmansion.rnscreens.RNScreensFragmentFactory; +import com.swmansion.rnscreens.fragment.restoration.RNScreensFragmentFactory; class MainActivity: ReactActivity() {