Skip to content

Commit 4bb3c51

Browse files
CopilottroZee
andcommitted
Fix memory leaks on component disposal for iOS and Android
Co-authored-by: troZee <[email protected]>
1 parent d130cb3 commit 4bb3c51

File tree

4 files changed

+62
-2
lines changed

4 files changed

+62
-2
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ class NestedScrollableHost : FrameLayout {
2525
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
2626
public var initialIndex: Int? = null
2727
public var didSetInitialIndex = false
28+
public var pageChangeCallback: ViewPager2.OnPageChangeCallback? = null
2829
private var touchSlop = 0
2930
private var initialX = 0f
3031
private var initialY = 0f

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

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ class PagerViewViewManager : ViewGroupManager<NestedScrollableHost>(), RNCViewPa
5252
vp.isSaveEnabled = false
5353

5454
vp.post {
55-
vp.registerOnPageChangeCallback(object : OnPageChangeCallback() {
55+
val callback = object : OnPageChangeCallback() {
5656
override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
5757
super.onPageScrolled(position, positionOffset, positionOffsetPixels)
5858
UIManagerHelper.getEventDispatcherForReactTag(reactContext, host.id)?.dispatchEvent(
@@ -79,7 +79,9 @@ class PagerViewViewManager : ViewGroupManager<NestedScrollableHost>(), RNCViewPa
7979
PageScrollStateChangedEvent(host.id, pageScrollState)
8080
)
8181
}
82-
})
82+
}
83+
host.pageChangeCallback = callback
84+
vp.registerOnPageChangeCallback(callback)
8385
UIManagerHelper.getEventDispatcherForReactTag(reactContext, host.id)?.dispatchEvent(
8486
PageSelectedEvent(host.id, vp.currentItem)
8587
)
@@ -200,6 +202,20 @@ class PagerViewViewManager : ViewGroupManager<NestedScrollableHost>(), RNCViewPa
200202
}
201203
}
202204

205+
override fun onDropViewInstance(view: NestedScrollableHost) {
206+
// Unregister the page change callback to prevent memory leaks
207+
val viewPager = PagerViewViewManagerImpl.getViewPager(view)
208+
view.pageChangeCallback?.let { callback ->
209+
viewPager.unregisterOnPageChangeCallback(callback)
210+
view.pageChangeCallback = null
211+
}
212+
213+
// Clear the adapter to release references to child views
214+
viewPager.adapter = null
215+
216+
super.onDropViewInstance(view)
217+
}
218+
203219
override fun getExportedCustomDirectEventTypeConstants(): MutableMap<String, Map<String, String>> {
204220
return MapBuilder.of(
205221
PageScrollEvent.EVENT_NAME, MapBuilder.of("registrationName", "onPageScroll"),

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,12 @@ class ViewPagerAdapter() : Adapter<ViewPagerViewHolder>() {
3030
container.addView(child)
3131
}
3232

33+
override fun onViewRecycled(holder: ViewPagerViewHolder) {
34+
super.onViewRecycled(holder)
35+
// Clean up the holder's container to prevent memory leaks
36+
holder.container.removeAllViews()
37+
}
38+
3339
override fun getItemCount(): Int {
3440
return childrenViews.size
3541
}

ios/RNCPagerViewComponentView.mm

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,10 +81,32 @@ - (instancetype)initWithFrame:(CGRect)frame
8181
return self;
8282
}
8383

84+
- (void)dealloc {
85+
// Clean up delegates to prevent memory leaks
86+
if (_nativePageViewController) {
87+
_nativePageViewController.dataSource = nil;
88+
_nativePageViewController.delegate = nil;
89+
}
90+
91+
if (scrollView) {
92+
scrollView.delegate = nil;
93+
}
94+
}
95+
8496
- (void)willMoveToSuperview:(UIView *)newSuperview {
8597
if (newSuperview != nil) {
8698
[self initializeNativePageViewController];
8799
[self goTo:_currentIndex animated:NO];
100+
} else {
101+
// Component is being removed from view hierarchy, clean up delegates
102+
if (_nativePageViewController) {
103+
_nativePageViewController.dataSource = nil;
104+
_nativePageViewController.delegate = nil;
105+
}
106+
107+
if (scrollView) {
108+
scrollView.delegate = nil;
109+
}
88110
}
89111
}
90112

@@ -124,6 +146,21 @@ -(void)updateLayoutMetrics:(const facebook::react::LayoutMetrics &)layoutMetrics
124146

125147
-(void)prepareForRecycle {
126148
[super prepareForRecycle];
149+
150+
// Clear delegates to prevent memory leaks
151+
if (_nativePageViewController) {
152+
_nativePageViewController.dataSource = nil;
153+
_nativePageViewController.delegate = nil;
154+
}
155+
156+
if (scrollView) {
157+
scrollView.delegate = nil;
158+
scrollView = nil;
159+
}
160+
161+
// Clear view controllers array
162+
[_nativeChildrenViewControllers removeAllObjects];
163+
127164
_nativePageViewController = nil;
128165
_currentIndex = -1;
129166
}

0 commit comments

Comments
 (0)