Open
Conversation
…, 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
Contributor
There was a problem hiding this comment.
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/OnHandlerChangingwhere 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. |
src/library/DIPS.Mobile.UI/Components/Pickers/SegmentedControl/SegmentedControl.cs
Show resolved
Hide resolved
...library/DIPS.Mobile.UI/Components/Pickers/NullableDatePickerShared/BaseNullableDatePicker.cs
Show resolved
Hide resolved
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
SizeChangeddelegates (API/Camera)Replaced 2 anonymous
SizeChangedlambda delegates with named methods (OnRotatedImageSizeChanged,OnCarouselViewSizeChanged). Anonymous delegates capturethisand cannot be individually unsubscribed, preventing GC when views are removed/recreated.2. CameraSession — Missing
OnTapToFocusunsubscription (iOS)Added
PreviewView.OnTapToFocus -= TapToFocusinStopSession()to prevent the static-ish preview view from pinning the session.3. SystemMessage — Timer not disposed
Added
m_timer.Dispose()inDispose(). Previously only stopped and unsubscribed the timer event, but the underlyingSystem.Timers.Timernative resources were never released.4. BottomSheetHandler — Native Android callback GC roots
Stored
BottomSheetCallbackandKeyListenerJava objects as fields and explicitly remove/null them inDisconnectHandler. Previously, these anonymous JavaObjectsubclass 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
thisreference after disconnectAdded
OnHandlerChangingoverride that callsStopAnimation()when the handler disconnects. Theanimation.Commit(... () => IsLoading)repeat callback capturesthis, and without explicit abort, the MAUI animation framework keeps the view alive indefinitely.6. StateView — Previous ViewModel event not unsubscribed
Added tracking of previous
StateViewModelviam_previousStateViewModelfield. When the state view model changes, the old one'sOnStateChangedevent is now properly unsubscribed before subscribing to the new one.7. FloatingNavigationButtonHandler (Android) — Anonymous Click lambda
Replaced anonymous
aView.Click += (_, _) => ...lambda with a namedOnNativeViewClickmethod. Added unsub-before-sub pattern to prevent duplicate subscriptions.8. FloatingNavigationButtonService — Static field never nulled
Added
FloatingNavigationButton = nullinRemove(). The static property previously kept the entire FAB view graph alive after removal.9. iOS BaseDatePickerHandler — Missing
ValueChangedunsubscriptionAdded
platformView.ValueChanged -= OnValueChangedinDisconnectHandler. The handler subscribed toValueChanged,EditingDidBegin, andEditingDidEndinConnectHandler, but only unsubscribed the latter two inDisconnectHandler.10. ItemPicker — Old
CollectionChangednot unsubscribedWhen
ItemsSourcechanges, the old collection'sCollectionChangedevent is now unsubscribed before subscribing to the new one. Previously, switching ItemsSource would leak the old collection subscription.11. BaseNullableDatePicker —
Toggledevent never unsubscribedAdded
DateEnabledSwitch.Toggled -= OnSwitchToggledinOnHandlerChangingwhen the handler disconnects. The switch field held the subscription indefinitely.12. ScrollPickerHandler (Android) — ViewModel not disposed
Added
m_scrollPickerViewModel.Dispose()inDisconnectHandlerto release the ViewModel's resources when the handler disconnects.13. SegmentedControl — Anonymous
SizeChangedlambda capturesthisConverted the anonymous
border.SizeChanged += ((sender, _) => { ... })lambda to a namedOnBorderSizeChangedmethod. The lambda capturedthis(the SegmentedControl), and whenBindableLayoutrecycled borders onItemsSourcechanges, old borders kept back-references to the control.14. SearchPage — Event lifecycle mismatch
Moved
TextChangedsubscription from constructor toOnHandlerChanged()and unsubscription fromOnDisappearing()toOnHandlerChanging(), aligning with the handler lifecycle.15. Shell —
Navigatedevent never unsubscribedAdded
OnHandlerChangingoverride that unsubscribesNavigated -= OnNavigatedwhen the handler disconnects. The event was subscribed in the constructor with no corresponding cleanup.16. SlidableLayout — Missing
TapGestureRecognizer.TappedunsubscriptionFixed unsubscription of
TapGestureRecognizer.Tappedby storing the recognizer as a field. Previously the cleanup code could not reference the correct instance.17. iOS TabView — Anonymous
Tappedlambda + missing cleanup inClearItemsReplaced anonymous
tab.Tapped += (sender, args) => ...lambda with namedOnTabTappedmethod.ClearItems()now iterates all tabs and unsubscribes before clearing. Previously, eachItemsSourceupdate leaked all old Tab instances through their Tapped subscriptions.