Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.reactnativepagerview

import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager2.widget.ViewPager2
import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback
import com.facebook.infer.annotation.Assertions
Expand Down Expand Up @@ -88,6 +89,21 @@ class PagerViewViewManager : ViewGroupManager<NestedScrollableHost>(), RNCViewPa
return host
}

private fun stopScrollIfNeeded(host: NestedScrollableHost) {
val recyclerView = (host.getChildAt(0) as? ViewPager2)?.getChildAt(0) as? RecyclerView
recyclerView?.stopScroll()
}

override fun onDropViewInstance(view: NestedScrollableHost) {
val viewPager = view.getChildAt(0) as? ViewPager2
val recyclerView = viewPager?.getChildAt(0) as? RecyclerView
recyclerView?.let {
it.stopScroll()
it.swapAdapter(null, false)
}
super.onDropViewInstance(view)
}

override fun addView(host: NestedScrollableHost, child: View, index: Int) {
PagerViewViewManagerImpl.addView(host, child, index)
}
Expand All @@ -99,14 +115,17 @@ class PagerViewViewManager : ViewGroupManager<NestedScrollableHost>(), RNCViewPa
}

override fun removeView(parent: NestedScrollableHost, view: View) {
stopScrollIfNeeded(parent)
PagerViewViewManagerImpl.removeView(parent, view)
}

override fun removeAllViews(parent: NestedScrollableHost) {
stopScrollIfNeeded(parent)
PagerViewViewManagerImpl.removeAllViews(parent)
}

override fun removeViewAt(parent: NestedScrollableHost, index: Int) {
stopScrollIfNeeded(parent)
PagerViewViewManagerImpl.removeViewAt(parent, index)
}

Expand Down Expand Up @@ -206,4 +225,4 @@ class PagerViewViewManager : ViewGroupManager<NestedScrollableHost>(), RNCViewPa
PageScrollStateChangedEvent.EVENT_NAME, MapBuilder.of("registrationName", "onPageScrollStateChanged"),
PageSelectedEvent.EVENT_NAME, MapBuilder.of("registrationName", "onPageSelected"))
}
}
}
30 changes: 21 additions & 9 deletions android/src/main/java/com/reactnativepagerview/ViewPagerAdapter.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,28 @@ class ViewPagerAdapter() : Adapter<ViewPagerViewHolder>() {
override fun onBindViewHolder(holder: ViewPagerViewHolder, index: Int) {
val container: FrameLayout = holder.container
val child = getChildAt(index)
holder.setIsRecyclable(false)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it should be reverted, since we don't want to have a recyclable view

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should not revert it, thats the reason why we have a crash there - RecyclerView's internal lifecycle assumes holders CAN be recycled. When they can't, they accumulate in limbo — still attached to the layout, but unable to be cleaned up. Eventually RecyclerView tries to force-recycle them during scroll/layout passes and hits the isAttached:true crash. It's a contract violation.

Also the React Native child views are NOT destroyed when a holder is recycled — they're stored in the childrenViews list and survive independently. Only the FrameLayout container is reused. When RecyclerView rebinds a recycled holder to a new position, onBindViewHolder already handles this correctly:


if (container.childCount > 0) {
container.removeAllViews()
}

if (child.parent != null) {
(child.parent as FrameLayout).removeView(child)
(child.parent as ViewGroup).removeView(child)
}

container.addView(child)
}

override fun onViewRecycled(holder: ViewPagerViewHolder) {
super.onViewRecycled(holder)
holder.container.removeAllViews()
}

override fun onFailedToRecycleView(holder: ViewPagerViewHolder): Boolean {
holder.container.removeAllViews()
return true
}

override fun getItemCount(): Int {
return childrenViews.size
}
Expand All @@ -45,17 +54,16 @@ class ViewPagerAdapter() : Adapter<ViewPagerViewHolder>() {

fun removeChild(child: View) {
val index = childrenViews.indexOf(child)
if(index > -1) {

if (index > -1) {
removeChildAt(index)
}
}

fun removeAll() {
for (index in 1..childrenViews.size) {
val child = childrenViews[index-1]
if (child.parent?.parent != null) {
(child.parent.parent as ViewGroup).removeView(child.parent as View)
for (child in childrenViews) {
if (child.parent != null) {
(child.parent as ViewGroup).removeView(child)
}
}
val removedChildrenCount = childrenViews.size
Expand All @@ -64,7 +72,11 @@ class ViewPagerAdapter() : Adapter<ViewPagerViewHolder>() {
}

fun removeChildAt(index: Int) {
if (index >= 0 && index < childrenViews.size) {
if (index >= 0 && index < childrenViews.size) {
val child = childrenViews[index]
if (child.parent != null) {
(child.parent as ViewGroup).removeView(child)
}
childrenViews.removeAt(index)
notifyItemRemoved(index)
}
Expand Down
Loading
Loading