Skip to content

Commit 5b1261f

Browse files
authored
fix: Add re-attach to android in pseudoselectors (#9743)
## Summary This PR adds re-attach mechanism for listeners also to the Android side of pseudoselectors. Previously I added re-attaching only on the iOS side as I was convinced it's simply not necessary on the Andoid side. Now after testing with different devices than previously I realized it's also an issue on Android. ## Test Run on old andoid device and check repeatedly if all examples under `CSS -> Transitions -> Pseudoselectors -> Active` react to touch.
1 parent 93ee990 commit 5b1261f

1 file changed

Lines changed: 82 additions & 9 deletions

File tree

packages/react-native-reanimated/android/src/main/java/com/swmansion/reanimated/pseudoSelectors/PseudoSelectorManager.kt

Lines changed: 82 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,15 @@ package com.swmansion.reanimated.pseudoSelectors
33
import android.view.MotionEvent
44
import android.view.View
55
import android.view.ViewParent
6+
import com.facebook.react.bridge.UIManager
7+
import com.facebook.react.bridge.UIManagerListener
68
import com.facebook.react.bridge.UiThreadUtil
9+
import com.facebook.react.common.annotations.UnstableReactNativeAPI
710
import com.facebook.react.fabric.FabricUIManager
11+
import com.facebook.react.uimanager.IllegalViewOperationException
812
import com.swmansion.reanimated.nativeProxy.PseudoSelectorCallback
913

14+
@OptIn(UnstableReactNativeAPI::class)
1015
class PseudoSelectorManager(
1116
private val fabricUIManager: FabricUIManager,
1217
) {
@@ -25,24 +30,88 @@ class PseudoSelectorManager(
2530
// Reused by updateHoverStates to avoid allocating on every hover event (UI thread only).
2631
private val hoverLocationBuffer = IntArray(2)
2732

33+
private val pendingAttaches = LinkedHashMap<String, PendingAttach>()
34+
private var mountListenerRegistered = false
35+
36+
private data class PendingAttach(
37+
val tag: Int,
38+
val selector: Int,
39+
val callback: PseudoSelectorCallback,
40+
)
41+
42+
private val mountListener =
43+
object : UIManagerListener {
44+
override fun didMountItems(uiManager: UIManager) = flushPendingAttaches()
45+
46+
override fun willMountItems(uiManager: UIManager) = Unit
47+
48+
override fun willDispatchViewUpdates(uiManager: UIManager) = Unit
49+
50+
override fun didDispatchMountItems(uiManager: UIManager) = Unit
51+
52+
override fun didScheduleMountItems(uiManager: UIManager) = Unit
53+
}
54+
2855
fun attach(
2956
tag: Int,
3057
selector: Int,
3158
callback: PseudoSelectorCallback,
3259
) {
3360
UiThreadUtil.runOnUiThread {
34-
val view = fabricUIManager.resolveView(tag) ?: return@runOnUiThread
35-
val key = "$tag:$selector"
36-
when (selector) {
37-
0 -> attachFocusWithin(view, key, callback)
38-
1 -> attachFocus(view, key, callback)
39-
2 -> attachHover(view, key, callback)
40-
3 -> attachActive(view, key, callback)
41-
4 -> attachActiveDeepest(view, key, callback)
61+
val view = tryResolveView(tag)
62+
if (view != null) {
63+
attachToView(view, tag, selector, callback)
64+
} else {
65+
pendingAttaches["$tag:$selector"] = PendingAttach(tag, selector, callback)
66+
ensureMountListener()
4267
}
4368
}
4469
}
4570

71+
private fun attachToView(
72+
view: View,
73+
tag: Int,
74+
selector: Int,
75+
callback: PseudoSelectorCallback,
76+
) {
77+
val key = "$tag:$selector"
78+
when (selector) {
79+
0 -> attachFocusWithin(view, key, callback)
80+
1 -> attachFocus(view, key, callback)
81+
2 -> attachHover(view, key, callback)
82+
3 -> attachActive(view, key, callback)
83+
4 -> attachActiveDeepest(view, key, callback)
84+
}
85+
}
86+
87+
private fun tryResolveView(tag: Int): View? =
88+
try {
89+
fabricUIManager.resolveView(tag)
90+
} catch (e: IllegalViewOperationException) {
91+
null
92+
}
93+
94+
private fun ensureMountListener() {
95+
if (mountListenerRegistered) {
96+
return
97+
}
98+
mountListenerRegistered = true
99+
fabricUIManager.addUIManagerEventListener(mountListener)
100+
}
101+
102+
private fun flushPendingAttaches() {
103+
if (pendingAttaches.isEmpty()) {
104+
return
105+
}
106+
val iterator = pendingAttaches.values.iterator()
107+
while (iterator.hasNext()) {
108+
val pending = iterator.next()
109+
val view = tryResolveView(pending.tag) ?: continue
110+
iterator.remove()
111+
attachToView(view, pending.tag, pending.selector, pending.callback)
112+
}
113+
}
114+
46115
private fun attachFocusWithin(
47116
view: View,
48117
key: String,
@@ -178,7 +247,11 @@ class PseudoSelectorManager(
178247
tag: Int,
179248
selector: Int,
180249
) {
181-
UiThreadUtil.runOnUiThread { detachActions.remove("$tag:$selector")?.run() }
250+
UiThreadUtil.runOnUiThread {
251+
val key = "$tag:$selector"
252+
pendingAttaches.remove(key)
253+
detachActions.remove(key)?.run()
254+
}
182255
}
183256

184257
/**

0 commit comments

Comments
 (0)