Skip to content

Fixed multiple memory leaks across components#795

Open
Copilot wants to merge 5 commits intomainfrom
copilot/identify-memory-leaks
Open

Fixed multiple memory leaks across components#795
Copilot wants to merge 5 commits intomainfrom
copilot/identify-memory-leaks

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Feb 27, 2026

Memory Leak Fixes — Comprehensive /Components Audit

A thorough, sequential analysis of all 36 component directories (~450+ files) identified multiple memory leak vectors. This PR fixes 17 files across the library.


Fixes

1. GalleryBottomSheet — Anonymous SizeChanged delegates (API/Camera)

Replaced 2 anonymous SizeChanged lambda delegates with named methods (OnRotatedImageSizeChanged, OnCarouselViewSizeChanged). Anonymous delegates capture this and cannot be individually unsubscribed, preventing GC when views are removed/recreated.

2. CameraSession — Missing OnTapToFocus unsubscription (iOS)

Added PreviewView.OnTapToFocus -= TapToFocus in StopSession() to prevent the static-ish preview view from pinning the session.

3. SystemMessage — Timer not disposed

Added m_timer.Dispose() in Dispose(). Previously only stopped and unsubscribed the timer event, but the underlying System.Timers.Timer native resources were never released.

4. BottomSheetHandler — Native Android callback GC roots

Stored BottomSheetCallback and KeyListener Java objects as fields and explicitly remove/null them in DisconnectHandler. Previously, these anonymous Java Object subclass instances held strong references to the handler through their constructor, creating GC root cycles on the Java side that prevented the entire handler + bottom sheet graph from being collected.

5. SkeletonView — Animation holds this reference after disconnect

Added OnHandlerChanging override that calls StopAnimation() when the handler disconnects. The animation.Commit(... () => IsLoading) repeat callback captures this, and without explicit abort, the MAUI animation framework keeps the view alive indefinitely.

6. StateView — Previous ViewModel event not unsubscribed

Added tracking of previous StateViewModel via m_previousStateViewModel field. When the state view model changes, the old one's OnStateChanged event is now properly unsubscribed before subscribing to the new one.

7. FloatingNavigationButtonHandler (Android) — Anonymous Click lambda

Replaced anonymous aView.Click += (_, _) => ... lambda with a named OnNativeViewClick method. Added unsub-before-sub pattern to prevent duplicate subscriptions.

8. FloatingNavigationButtonService — Static field never nulled

Added FloatingNavigationButton = null in Remove(). The static property previously kept the entire FAB view graph alive after removal.

9. iOS BaseDatePickerHandler — Missing ValueChanged unsubscription

Added platformView.ValueChanged -= OnValueChanged in DisconnectHandler. The handler subscribed to ValueChanged, EditingDidBegin, and EditingDidEnd in ConnectHandler, but only unsubscribed the latter two in DisconnectHandler.

10. ItemPicker — Old CollectionChanged not unsubscribed

When ItemsSource changes, the old collection's CollectionChanged event is now unsubscribed before subscribing to the new one. Previously, switching ItemsSource would leak the old collection subscription.

11. BaseNullableDatePicker — Toggled event never unsubscribed

Added DateEnabledSwitch.Toggled -= OnSwitchToggled in OnHandlerChanging when the handler disconnects. The switch field held the subscription indefinitely.

12. ScrollPickerHandler (Android) — ViewModel not disposed

Added m_scrollPickerViewModel.Dispose() in DisconnectHandler to release the ViewModel's resources when the handler disconnects.

13. SegmentedControl — Anonymous SizeChanged lambda captures this

Converted the anonymous border.SizeChanged += ((sender, _) => { ... }) lambda to a named OnBorderSizeChanged method. The lambda captured this (the SegmentedControl), and when BindableLayout recycled borders on ItemsSource changes, old borders kept back-references to the control.

14. SearchPage — Event lifecycle mismatch

Moved TextChanged subscription from constructor to OnHandlerChanged() and unsubscription from OnDisappearing() to OnHandlerChanging(), aligning with the handler lifecycle.

15. Shell — Navigated event never unsubscribed

Added OnHandlerChanging override that unsubscribes Navigated -= OnNavigated when the handler disconnects. The event was subscribed in the constructor with no corresponding cleanup.

16. SlidableLayout — Missing TapGestureRecognizer.Tapped unsubscription

Fixed unsubscription of TapGestureRecognizer.Tapped by storing the recognizer as a field. Previously the cleanup code could not reference the correct instance.

17. iOS TabView — Anonymous Tapped lambda + missing cleanup in ClearItems

Replaced anonymous tab.Tapped += (sender, args) => ... lambda with named OnTabTapped method. ClearItems() now iterates all tabs and unsubscribes before clearing. Previously, each ItemsSource update leaked all old Tab instances through their Tapped subscriptions.

…, SearchBarHandler, and SlidableLayout

Co-authored-by: Vetle444 <35739538+Vetle444@users.noreply.github.com>
Copilot AI changed the title [WIP] Identify possible memory leaks in the application Fix memory leaks from missing event unsubscriptions Feb 27, 2026
@Vetle444 Vetle444 marked this pull request as ready for review April 7, 2026 14:04
Copilot AI review requested due to automatic review settings April 7, 2026 14:04
@Vetle444 Vetle444 changed the title Fix memory leaks from missing event unsubscriptions Fixed multiple memory leaks across components Apr 7, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR addresses multiple memory-leak vectors across DIPS.Mobile.UI components/handlers by ensuring event subscriptions, timers, animations, and native callbacks are properly cleaned up when views/handlers are disconnected or rebuilt.

Changes:

  • Replaced anonymous event handlers (lambdas/delegates) with named methods to enable reliable unsubscription and prevent captured references.
  • Added missing event unsubscriptions / disposal (e.g., iOS camera preview events, timers, Android native callbacks, gesture recognizers).
  • Improved handler-lifecycle alignment for subscriptions (moving subscribe/unsubscribe to OnHandlerChanged / OnHandlerChanging where appropriate).

Reviewed changes

Copilot reviewed 19 out of 19 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/library/DIPS.Mobile.UI/Components/TabView/iOS/TabView.cs Replaces anonymous tab tap handler with named method and unsubscribes in ClearItems().
src/library/DIPS.Mobile.UI/Components/Slidable/SlidableLayout.cs Stores tap recognizer as a field and unsubscribes on handler disconnect.
src/library/DIPS.Mobile.UI/Components/Shell/Shell.cs Unsubscribes Navigated on handler disconnect to avoid lingering references.
src/library/DIPS.Mobile.UI/Components/Searching/SearchPage.cs Moves TextChanged subscription to handler lifecycle and unsubscribes on handler disconnect.
src/library/DIPS.Mobile.UI/Components/Pickers/SegmentedControl/SegmentedControl.cs Replaces anonymous SizeChanged handler with named method.
src/library/DIPS.Mobile.UI/Components/Pickers/ScrollPicker/Android/ScrollPickerHandler.cs Disposes ScrollPickerViewModel on handler disconnect.
src/library/DIPS.Mobile.UI/Components/Pickers/NullableDatePickerShared/BaseNullableDatePicker.cs Unsubscribes switch Toggled on handler disconnect.
src/library/DIPS.Mobile.UI/Components/Pickers/ItemPicker/ItemPicker.cs Unsubscribes CollectionChanged from old ItemsSource before subscribing to new.
src/library/DIPS.Mobile.UI/Components/Pickers/DatePickerShared/iOS/BaseDatePickerHandler.cs Adds missing ValueChanged unsubscription on disconnect.
src/library/DIPS.Mobile.UI/Components/Navigation/FloatingNavigationButton/FloatingNavigationButtonService.cs Clears static FAB reference on Remove() to allow GC.
src/library/DIPS.Mobile.UI/Components/Navigation/FloatingNavigationButton/Android/FloatingNavigationButtonHandler.cs Replaces anonymous click handler with named method and prevents duplicate subscriptions.
src/library/DIPS.Mobile.UI/Components/Loading/StateView/StateView.cs Tracks previous StateViewModel to unsubscribe old OnStateChanged.
src/library/DIPS.Mobile.UI/Components/Loading/Skeleton/SkeletonView.cs Stops animation when handler disconnects to avoid repeat callbacks retaining the view.
src/library/DIPS.Mobile.UI/Components/BottomSheets/Android/BottomSheetHandler.cs Stores/removes native callback/listener instances to prevent Java-side GC roots.
src/library/DIPS.Mobile.UI/Components/Alerting/SystemMessage/SystemMessage.cs Disposes the timer in Dispose().
src/library/DIPS.Mobile.UI/API/Camera/Shared/iOS/CameraSession.cs Unsubscribes PreviewView.OnTapToFocus when stopping session.
src/library/DIPS.Mobile.UI/API/Camera/Gallery/BottomSheet/GalleryBottomSheet.cs Replaces anonymous SizeChanged handlers with named methods and unsubscribes appropriately.
CHANGELOG.md Adds a patch release entry documenting leak fixes.
build/bootstrapper/bootstrapper.sh Fixes Azure CLI presence check to silence stderr.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants