Skip to content

Commit d186fd9

Browse files
author
Piotr Trocki
committed
implement onDropViewInstance
1 parent 9ea6b49 commit d186fd9

1 file changed

Lines changed: 152 additions & 51 deletions

File tree

android/src/main/java/com/reactnativepagerview/PagerViewViewManager.kt

Lines changed: 152 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import androidx.recyclerview.widget.RecyclerView
66
import androidx.viewpager2.widget.ViewPager2
77
import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback
88
import com.facebook.infer.annotation.Assertions
9-
import com.facebook.react.bridge.ReactContext
109
import com.facebook.react.bridge.ReadableArray
1110
import com.facebook.react.common.MapBuilder
1211
import com.facebook.react.module.annotations.ReactModule
@@ -19,9 +18,10 @@ import com.reactnativepagerview.event.PageScrollEvent
1918
import com.reactnativepagerview.event.PageScrollStateChangedEvent
2019
import com.reactnativepagerview.event.PageSelectedEvent
2120

22-
2321
@ReactModule(name = PagerViewViewManagerImpl.NAME)
24-
class PagerViewViewManager : ViewGroupManager<NestedScrollableHost>(), RNCViewPagerManagerInterface<NestedScrollableHost> {
22+
class PagerViewViewManager :
23+
ViewGroupManager<NestedScrollableHost>(),
24+
RNCViewPagerManagerInterface<NestedScrollableHost> {
2525
companion object {
2626
init {
2727
if (BuildConfig.CODEGEN_MODULE_REGISTRATION != null) {
@@ -30,60 +30,89 @@ class PagerViewViewManager : ViewGroupManager<NestedScrollableHost>(), RNCViewPa
3030
}
3131
}
3232

33-
private val mDelegate: ViewManagerDelegate<NestedScrollableHost> = RNCViewPagerManagerDelegate(this)
33+
private val mDelegate: ViewManagerDelegate<NestedScrollableHost> =
34+
RNCViewPagerManagerDelegate(this)
3435

3536
override fun getDelegate() = mDelegate
3637

3738
override fun getName(): String {
3839
return PagerViewViewManagerImpl.NAME
3940
}
4041

41-
override fun receiveCommand(root: NestedScrollableHost, commandId: String, args: ReadableArray?) {
42+
override fun receiveCommand(
43+
root: NestedScrollableHost,
44+
commandId: String,
45+
args: ReadableArray?
46+
) {
4247
mDelegate.receiveCommand(root, commandId, args)
4348
}
4449

4550
public override fun createViewInstance(reactContext: ThemedReactContext): NestedScrollableHost {
4651
val host = NestedScrollableHost(reactContext)
4752
host.id = View.generateViewId()
48-
host.layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
53+
host.layoutParams =
54+
ViewGroup.LayoutParams(
55+
ViewGroup.LayoutParams.MATCH_PARENT,
56+
ViewGroup.LayoutParams.MATCH_PARENT
57+
)
4958
host.isSaveEnabled = false
5059
val vp = ViewPager2(reactContext)
60+
61+
// Access private mRecyclerView field using reflection to disable animations
62+
val recyclerViewField = ViewPager2::class.java.getDeclaredField("mRecyclerView")
63+
recyclerViewField.isAccessible = true
64+
val recyclerView = recyclerViewField.get(vp) as RecyclerView
65+
66+
// Disable all animations to prevent layout change issues
67+
recyclerView.itemAnimator = null
68+
recyclerView.layoutTransition = null
69+
5170
vp.adapter = ViewPagerAdapter()
52-
//https://github.com/callstack/react-native-viewpager/issues/183
71+
// https://github.com/callstack/react-native-viewpager/issues/183
5372
vp.isSaveEnabled = false
5473

5574
vp.post {
56-
vp.registerOnPageChangeCallback(object : OnPageChangeCallback() {
57-
override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
58-
super.onPageScrolled(position, positionOffset, positionOffsetPixels)
59-
UIManagerHelper.getEventDispatcherForReactTag(reactContext, host.id)?.dispatchEvent(
60-
PageScrollEvent(host.id, position, positionOffset)
61-
)
62-
}
63-
64-
override fun onPageSelected(position: Int) {
65-
super.onPageSelected(position)
66-
UIManagerHelper.getEventDispatcherForReactTag(reactContext, host.id)?.dispatchEvent(
67-
PageSelectedEvent(host.id, position)
68-
)
69-
}
70-
71-
override fun onPageScrollStateChanged(state: Int) {
72-
super.onPageScrollStateChanged(state)
73-
val pageScrollState: String = when (state) {
74-
ViewPager2.SCROLL_STATE_IDLE -> "idle"
75-
ViewPager2.SCROLL_STATE_DRAGGING -> "dragging"
76-
ViewPager2.SCROLL_STATE_SETTLING -> "settling"
77-
else -> throw IllegalStateException("Unsupported pageScrollState")
75+
vp.registerOnPageChangeCallback(
76+
object : OnPageChangeCallback() {
77+
override fun onPageScrolled(
78+
position: Int,
79+
positionOffset: Float,
80+
positionOffsetPixels: Int
81+
) {
82+
super.onPageScrolled(position, positionOffset, positionOffsetPixels)
83+
UIManagerHelper.getEventDispatcherForReactTag(reactContext, host.id)
84+
?.dispatchEvent(
85+
PageScrollEvent(host.id, position, positionOffset)
86+
)
87+
}
88+
89+
override fun onPageSelected(position: Int) {
90+
super.onPageSelected(position)
91+
UIManagerHelper.getEventDispatcherForReactTag(reactContext, host.id)
92+
?.dispatchEvent(PageSelectedEvent(host.id, position))
93+
}
94+
95+
override fun onPageScrollStateChanged(state: Int) {
96+
super.onPageScrollStateChanged(state)
97+
val pageScrollState: String =
98+
when (state) {
99+
ViewPager2.SCROLL_STATE_IDLE -> "idle"
100+
ViewPager2.SCROLL_STATE_DRAGGING -> "dragging"
101+
ViewPager2.SCROLL_STATE_SETTLING -> "settling"
102+
else ->
103+
throw IllegalStateException(
104+
"Unsupported pageScrollState"
105+
)
106+
}
107+
UIManagerHelper.getEventDispatcherForReactTag(reactContext, host.id)
108+
?.dispatchEvent(
109+
PageScrollStateChangedEvent(host.id, pageScrollState)
110+
)
111+
}
78112
}
79-
UIManagerHelper.getEventDispatcherForReactTag(reactContext, host.id)?.dispatchEvent(
80-
PageScrollStateChangedEvent(host.id, pageScrollState)
81-
)
82-
}
83-
})
84-
UIManagerHelper.getEventDispatcherForReactTag(reactContext, host.id)?.dispatchEvent(
85-
PageSelectedEvent(host.id, vp.currentItem)
86113
)
114+
UIManagerHelper.getEventDispatcherForReactTag(reactContext, host.id)
115+
?.dispatchEvent(PageSelectedEvent(host.id, vp.currentItem))
87116
}
88117
host.addView(vp)
89118
return host
@@ -95,16 +124,78 @@ class PagerViewViewManager : ViewGroupManager<NestedScrollableHost>(), RNCViewPa
95124
}
96125

97126
override fun onDropViewInstance(view: NestedScrollableHost) {
98-
val vp = view.getChildAt(0) as? ViewPager2
99-
val recyclerView = vp?.getChildAt(0) as? RecyclerView
100-
recyclerView?.stopScroll()
101127
try {
102-
// Clear adapter to prevent post-teardown fling callbacks.
103-
// setAdapter(null) internally calls removeAndRecycleAllViews which
104-
// can throw if views are still attached during mid-scroll teardown.
105-
recyclerView?.adapter = null
106-
} catch (_: IllegalArgumentException) {
107-
// Safe to ignore during teardown — view is being destroyed
128+
val viewPager = PagerViewViewManagerImpl.getViewPager(view)
129+
// Access private mRecyclerView field using reflection
130+
val recyclerViewField = ViewPager2::class.java.getDeclaredField("mRecyclerView")
131+
recyclerViewField.isAccessible = true
132+
val recyclerView = recyclerViewField.get(viewPager) as RecyclerView
133+
134+
// Stop any scroll/drag in progress first
135+
recyclerView.stopScroll()
136+
137+
// Suppress layout to prevent any pending layout operations from executing
138+
recyclerView.suppressLayout(true)
139+
140+
// Access and stop the ViewFlinger directly via reflection - do this early
141+
try {
142+
val viewFlingerField = RecyclerView::class.java.getDeclaredField("mViewFlinger")
143+
viewFlingerField.isAccessible = true
144+
val viewFlinger = viewFlingerField.get(recyclerView) as? Runnable
145+
viewFlinger?.let {
146+
recyclerView.removeCallbacks(it)
147+
// Also try to stop it via reflection if it has a stop method
148+
try {
149+
val stopMethod = viewFlinger.javaClass.getDeclaredMethod("stop")
150+
stopMethod.isAccessible = true
151+
stopMethod.invoke(viewFlinger)
152+
} catch (ignored: Exception) {}
153+
}
154+
} catch (ignored: Exception) {
155+
// ViewFlinger reflection may fail on some versions
156+
}
157+
158+
// Clear the recycler's scrap views before clearing adapter
159+
try {
160+
val recyclerField = RecyclerView::class.java.getDeclaredField("mRecycler")
161+
recyclerField.isAccessible = true
162+
val recycler = recyclerField.get(recyclerView)
163+
164+
// Clear scrap heaps
165+
val clearScrapMethod = recycler.javaClass.getDeclaredMethod("clear")
166+
clearScrapMethod.isAccessible = true
167+
clearScrapMethod.invoke(recycler)
168+
} catch (ignored: Exception) {
169+
// Recycler reflection may fail on some versions
170+
}
171+
172+
// Clear any pending animations and layout transitions
173+
recyclerView.clearAnimation()
174+
recyclerView.itemAnimator = null
175+
recyclerView.layoutTransition = null
176+
recyclerView.recycledViewPool.clear()
177+
178+
// Remove all pending operations and choreographer callbacks
179+
recyclerView.removeCallbacks(null)
180+
181+
// Try to clear any pending adapter updates
182+
try {
183+
val adapterHelperField = RecyclerView::class.java.getDeclaredField("mAdapterHelper")
184+
adapterHelperField.isAccessible = true
185+
val adapterHelper = adapterHelperField.get(recyclerView)
186+
val resetMethod = adapterHelper.javaClass.getDeclaredMethod("reset")
187+
resetMethod.isAccessible = true
188+
resetMethod.invoke(adapterHelper)
189+
} catch (ignored: Exception) {}
190+
191+
// Clear the adapter to prevent recycling during teardown
192+
viewPager.adapter = null
193+
194+
// Unsuppress layout after clearing adapter (won't do anything as view is being
195+
// destroyed)
196+
recyclerView.suppressLayout(false)
197+
} catch (e: Exception) {
198+
// View might already be in an invalid state
108199
}
109200
super.onDropViewInstance(view)
110201
}
@@ -113,7 +204,8 @@ class PagerViewViewManager : ViewGroupManager<NestedScrollableHost>(), RNCViewPa
113204
PagerViewViewManagerImpl.addView(host, child, index)
114205
}
115206

116-
override fun getChildCount(parent: NestedScrollableHost) = PagerViewViewManagerImpl.getChildCount(parent)
207+
override fun getChildCount(parent: NestedScrollableHost) =
208+
PagerViewViewManagerImpl.getChildCount(parent)
117209

118210
override fun getChildAt(parent: NestedScrollableHost, index: Int): View {
119211
return PagerViewViewManagerImpl.getChildAt(parent, index)
@@ -204,7 +296,11 @@ class PagerViewViewManager : ViewGroupManager<NestedScrollableHost>(), RNCViewPa
204296
val view = PagerViewViewManagerImpl.getViewPager(root)
205297
Assertions.assertNotNull(view)
206298
val childCount = view.adapter?.itemCount
207-
val canScroll = childCount != null && childCount > 0 && selectedPage >= 0 && selectedPage < childCount
299+
val canScroll =
300+
childCount != null &&
301+
childCount > 0 &&
302+
selectedPage >= 0 &&
303+
selectedPage < childCount
208304
if (canScroll) {
209305
PagerViewViewManagerImpl.setCurrentItem(view, selectedPage, scrollWithAnimation)
210306
}
@@ -224,10 +320,15 @@ class PagerViewViewManager : ViewGroupManager<NestedScrollableHost>(), RNCViewPa
224320
}
225321
}
226322

227-
override fun getExportedCustomDirectEventTypeConstants(): MutableMap<String, Map<String, String>> {
323+
override fun getExportedCustomDirectEventTypeConstants():
324+
MutableMap<String, Map<String, String>> {
228325
return MapBuilder.of(
229-
PageScrollEvent.EVENT_NAME, MapBuilder.of("registrationName", "onPageScroll"),
230-
PageScrollStateChangedEvent.EVENT_NAME, MapBuilder.of("registrationName", "onPageScrollStateChanged"),
231-
PageSelectedEvent.EVENT_NAME, MapBuilder.of("registrationName", "onPageSelected"))
326+
PageScrollEvent.EVENT_NAME,
327+
MapBuilder.of("registrationName", "onPageScroll"),
328+
PageScrollStateChangedEvent.EVENT_NAME,
329+
MapBuilder.of("registrationName", "onPageScrollStateChanged"),
330+
PageSelectedEvent.EVENT_NAME,
331+
MapBuilder.of("registrationName", "onPageSelected")
332+
)
232333
}
233334
}

0 commit comments

Comments
 (0)