react-native-screens 4.2.0 → 4.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,12 +1,15 @@
1
1
  package com.swmansion.rnscreens
2
2
 
3
+ import android.util.Log
3
4
  import android.view.View
4
5
  import androidx.core.view.OnApplyWindowInsetsListener
5
6
  import androidx.core.view.ViewCompat
6
7
  import androidx.core.view.WindowInsetsCompat
8
+ import com.facebook.react.bridge.LifecycleEventListener
9
+ import com.facebook.react.bridge.ReactApplicationContext
7
10
  import java.lang.ref.WeakReference
8
11
 
9
- object InsetsObserverProxy : OnApplyWindowInsetsListener {
12
+ object InsetsObserverProxy : OnApplyWindowInsetsListener, LifecycleEventListener {
10
13
  private val listeners: ArrayList<OnApplyWindowInsetsListener> = arrayListOf()
11
14
  private var eventSourceView: WeakReference<View> = WeakReference(null)
12
15
 
@@ -15,8 +18,16 @@ object InsetsObserverProxy : OnApplyWindowInsetsListener {
15
18
  // whether this observer has been initially registered.
16
19
  private var hasBeenRegistered: Boolean = false
17
20
 
21
+ // Mainly debug variable to log warnings in case we missed some code path regarding
22
+ // context lifetime handling.
23
+ private var isObservingContextLifetime: Boolean = false
24
+
18
25
  private var shouldForwardInsetsToView = true
19
26
 
27
+ // Allow only when we have not been registered yet or the view we're observing has been
28
+ // invalidated due to some lifecycle we have not observed.
29
+ private val allowRegistration get() = !hasBeenRegistered || eventSourceView.get() == null
30
+
20
31
  override fun onApplyWindowInsets(
21
32
  v: View,
22
33
  insets: WindowInsetsCompat,
@@ -34,6 +45,34 @@ object InsetsObserverProxy : OnApplyWindowInsetsListener {
34
45
  return rollingInsets
35
46
  }
36
47
 
48
+ // Call this method to ensure that the observer proxy is
49
+ // unregistered when apps is destroyed or we change activity.
50
+ fun registerWithContext(context: ReactApplicationContext) {
51
+ if (isObservingContextLifetime) {
52
+ Log.w(
53
+ "[RNScreens]",
54
+ "InsetObserverProxy registers on new context while it has not been invalidated on the old one. Please report this as issue at https://github.com/software-mansion/react-native-screens/issues",
55
+ )
56
+ }
57
+
58
+ isObservingContextLifetime = true
59
+ context.addLifecycleEventListener(this)
60
+ }
61
+
62
+ override fun onHostResume() = Unit
63
+
64
+ override fun onHostPause() = Unit
65
+
66
+ override fun onHostDestroy() {
67
+ val observedView = getObservedView()
68
+ if (hasBeenRegistered && observedView != null) {
69
+ ViewCompat.setOnApplyWindowInsetsListener(observedView, null)
70
+ hasBeenRegistered = false
71
+ eventSourceView.clear()
72
+ }
73
+ isObservingContextLifetime = false
74
+ }
75
+
37
76
  fun addOnApplyWindowInsetsListener(listener: OnApplyWindowInsetsListener) {
38
77
  listeners.add(listener)
39
78
  }
@@ -42,16 +81,17 @@ object InsetsObserverProxy : OnApplyWindowInsetsListener {
42
81
  listeners.remove(listener)
43
82
  }
44
83
 
45
- fun registerOnView(view: View) {
46
- if (!hasBeenRegistered) {
84
+ /**
85
+ * @return boolean whether the proxy registered as a listener on a view
86
+ */
87
+ fun registerOnView(view: View): Boolean {
88
+ if (allowRegistration) {
47
89
  ViewCompat.setOnApplyWindowInsetsListener(view, this)
48
90
  eventSourceView = WeakReference(view)
49
91
  hasBeenRegistered = true
50
- } else if (getObservedView() != view) {
51
- throw IllegalStateException(
52
- "[RNScreens] Attempt to register InsetsObserverProxy on $view while it has been already registered on ${getObservedView()}",
53
- )
92
+ return true
54
93
  }
94
+ return false
55
95
  }
56
96
 
57
97
  fun unregister() {
@@ -29,6 +29,10 @@ class RNScreensPackage : TurboReactPackage() {
29
29
  screenDummyLayoutHelper = ScreenDummyLayoutHelper(reactContext)
30
30
  }
31
31
 
32
+ // Proxy needs to register for lifecycle events in order to unregister itself
33
+ // on activity restarts.
34
+ InsetsObserverProxy.registerWithContext(reactContext)
35
+
32
36
  return listOf<ViewManager<*, *>>(
33
37
  ScreenContainerViewManager(),
34
38
  ScreenViewManager(),
@@ -17,6 +17,7 @@ import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
17
17
  import com.facebook.react.bridge.GuardedRunnable
18
18
  import com.facebook.react.bridge.ReactContext
19
19
  import com.facebook.react.uimanager.PixelUtil
20
+ import com.facebook.react.uimanager.ReactClippingViewGroup
20
21
  import com.facebook.react.uimanager.UIManagerHelper
21
22
  import com.facebook.react.uimanager.UIManagerModule
22
23
  import com.facebook.react.uimanager.events.EventDispatcher
@@ -383,6 +384,7 @@ class Screen(
383
384
  parent?.let {
384
385
  for (i in 0 until it.childCount) {
385
386
  val child = it.getChildAt(i)
387
+
386
388
  if (parent is SwipeRefreshLayout && child is ImageView) {
387
389
  // SwipeRefreshLayout class which has CircleImageView as a child,
388
390
  // does not handle `startViewTransition` properly.
@@ -394,20 +396,35 @@ class Screen(
394
396
  } else {
395
397
  child?.let { view -> it.startViewTransition(view) }
396
398
  }
399
+
397
400
  if (child is ScreenStackHeaderConfig) {
398
401
  // we want to start transition on children of the toolbar too,
399
402
  // which is not a child of ScreenStackHeaderConfig
400
403
  startTransitionRecursive(child.toolbar)
401
404
  }
405
+
402
406
  if (child is ViewGroup) {
403
407
  // The children are miscounted when there's removeClippedSubviews prop
404
408
  // set to true (which is the default for FlatLists).
405
409
  // Unless the child is a ScrollView it's safe to assume that it's true
406
410
  // and add a simple view for each possibly clipped item to make it work as expected.
407
411
  // See https://github.com/software-mansion/react-native-screens/pull/2495
408
- if (child !is ReactScrollView && child !is ReactHorizontalScrollView) {
409
- for (j in 0 until child.childCount) {
410
- child.addView(View(context))
412
+
413
+ if (child is ReactClippingViewGroup &&
414
+ child.removeClippedSubviews &&
415
+ child !is ReactScrollView &&
416
+ child !is ReactHorizontalScrollView
417
+ ) {
418
+ // We need to workaround the issue until our changes land in core.
419
+ // Some views do not accept any children or have set amount and they throw
420
+ // when we want to brute-forcefully manipulate that.
421
+ // Is this ugly? Very. Do we have better option before changes land in core?
422
+ // I'm not aware of any.
423
+ try {
424
+ for (j in 0 until child.childCount) {
425
+ child.addView(View(context))
426
+ }
427
+ } catch (_: Exception) {
411
428
  }
412
429
  }
413
430
  startTransitionRecursive(child)
@@ -208,7 +208,7 @@ class DimmingFragment(
208
208
  override fun onStart() {
209
209
  // This is the earliest we can access child fragment manager & present another fragment
210
210
  super.onStart()
211
- insetsProxy.registerOnView(requireRootView())
211
+ insetsProxy.registerOnView(requireDecorView())
212
212
  presentNestedFragment()
213
213
  }
214
214
 
@@ -307,7 +307,7 @@ class DimmingFragment(
307
307
  }
308
308
  }
309
309
 
310
- private fun requireRootView(): View =
310
+ private fun requireDecorView(): View =
311
311
  checkNotNull(screen.reactContext.currentActivity) { "[RNScreens] Attempt to access activity on detached context" }
312
312
  .window.decorView
313
313
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-screens",
3
- "version": "4.2.0",
3
+ "version": "4.3.0",
4
4
  "description": "Native navigation primitives for your React Native app.",
5
5
  "scripts": {
6
6
  "submodules": "git submodule update --init --recursive && (cd react-navigation && yarn)",