diff --git a/maui/src/NumericEntry/SfNumericEntry.Methods.cs b/maui/src/NumericEntry/SfNumericEntry.Methods.cs index 32aaf8df..9c4652f1 100644 --- a/maui/src/NumericEntry/SfNumericEntry.Methods.cs +++ b/maui/src/NumericEntry/SfNumericEntry.Methods.cs @@ -2756,6 +2756,25 @@ void WindowEntryHandler(object? sender) } } + /// + /// Cleans up Android entry event handlers to prevent memory leaks. + /// + void CleanupAndroidEntryEvents() + { +#if ANDROID + if (_androidEntry != null) + { + _androidEntry.EditorAction -= AndroidEntry_EditorAction; + _androidEntry.TextChanged -= OnTextBoxTextChanged; + _androidEntry.BeforeTextChanged -= AndroidEntry_BeforeTextChanged; + _androidEntry.AfterTextChanged -= AndroidEntry_AfterTextChanged; + _androidEntry.Click -= AndroidEntry_Click; + _androidEntry.FocusChange -= AndroidEntry_FocusChange; + _androidEntry = null; + } +#endif + } + /// /// Configures platform-specific behavior for Android Entry controls. /// This method is called when the Entry's handler changes. @@ -2764,16 +2783,27 @@ void WindowEntryHandler(object? sender) void AndroidEntryHandler(object? sender) { #if ANDROID - if ((sender is SfEntryView textBox) && textBox.Handler != null && textBox.Handler.PlatformView is AndroidX.AppCompat.Widget.AppCompatEditText androidEntry) + if (sender is SfEntryView textBox) { - androidEntry.EditorAction += AndroidEntry_EditorAction; - androidEntry.TextChanged += OnTextBoxTextChanged; - androidEntry.BeforeTextChanged += AndroidEntry_BeforeTextChanged; - androidEntry.AfterTextChanged += AndroidEntry_AfterTextChanged; - androidEntry.KeyListener = global::Android.Text.Method.DigitsKeyListener.GetInstance(KEYS); - androidEntry.Click += AndroidEntry_Click; - // Disable EmojiCompat to prevent the IllegalArgumentException crash on Android when start typing text. - androidEntry.EmojiCompatEnabled = false; + // Clean up existing event handlers if we have a previous Android entry + CleanupAndroidEntryEvents(); + + // Subscribe to new handler's platform view if available + if (textBox.Handler != null && textBox.Handler.PlatformView is AndroidX.AppCompat.Widget.AppCompatEditText androidEntry) + { + // Store reference to the Android entry for cleanup + _androidEntry = androidEntry; + + androidEntry.EditorAction += AndroidEntry_EditorAction; + androidEntry.TextChanged += OnTextBoxTextChanged; + androidEntry.BeforeTextChanged += AndroidEntry_BeforeTextChanged; + androidEntry.AfterTextChanged += AndroidEntry_AfterTextChanged; + androidEntry.KeyListener = global::Android.Text.Method.DigitsKeyListener.GetInstance(KEYS); + androidEntry.Click += AndroidEntry_Click; + androidEntry.FocusChange += AndroidEntry_FocusChange; + // Disable EmojiCompat to prevent the IllegalArgumentException crash on Android when start typing text. + androidEntry.EmojiCompatEnabled = false; + } } #endif } @@ -2877,6 +2907,58 @@ void AndroidEntry_Click(object? sender, EventArgs e) } } } + + /// + /// Handles the focus change event for an Android Entry control. + /// Ensures proper value update and formatting when focus is lost. + /// This fixes the issue where clicking a button doesn't update the value + /// or dismiss the keyboard because the entry doesn't properly lose focus. + /// + /// The object that triggered the event, typically an Android Entry component. + /// The focus change event arguments. + void AndroidEntry_FocusChange(object? sender, Android.Views.View.FocusChangeEventArgs e) + { + // Capture local references to avoid race conditions + // The _androidEntry and _textBox could be set to null on another thread + var androidEntry = _androidEntry; + var textBox = _textBox; + + // Verify references are valid before processing + if (sender == null || androidEntry == null || textBox == null) + { + return; + } + + // When focus is lost (HasFocus is false), ensure the MAUI layer is notified + // This is crucial for Android where tapping outside (e.g., on a button) + // doesn't always properly sync the native focus state with MAUI's focus state + if (!e.HasFocus) + { + // The native Android entry has lost focus + // Check if MAUI's Entry is still reporting as focused + // If so, we need to trigger the unfocus to ensure proper event handling + MainThread.BeginInvokeOnMainThread(() => + { + try + { + // Use the local captured references to avoid race conditions + // Only proceed if the textBox is still focused + if (textBox.IsFocused) + { + // MAUI still thinks the entry is focused, but the native control has lost focus + // This can happen when clicking outside (e.g., on a button) + // Calling Unfocus() will trigger TextBoxOnLostFocus -> OnLostFocus() -> UpdateValue() -> FormatValue() + textBox.Unfocus(); + } + } + catch (ObjectDisposedException) + { + // The control was disposed between capturing the reference and using it + // This is safe to ignore as the control no longer needs to be unfocused + } + }); + } + } #endif /// @@ -2895,6 +2977,9 @@ void UnHookEvents() _textBox.Unfocused -= TextBoxOnLostFocus; } + + // Clean up Android-specific event handlers + CleanupAndroidEntryEvents(); } #endregion diff --git a/maui/src/NumericEntry/SfNumericEntry.cs b/maui/src/NumericEntry/SfNumericEntry.cs index e4451e22..47e49af0 100644 --- a/maui/src/NumericEntry/SfNumericEntry.cs +++ b/maui/src/NumericEntry/SfNumericEntry.cs @@ -300,6 +300,11 @@ public partial class SfNumericEntry : SfView, ITextElement, ITouchListener, IKey /// bool _isFirstFocus = true; + /// + /// Represents the underlying Android AppCompatEditText used in the control. + /// + AndroidX.AppCompat.Widget.AppCompatEditText? _androidEntry; + #endif #if !WINDOWS