From 02e2cf58ea9a6ef661e456b8b4fd42fe6f20bdcf Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 23 Nov 2025 15:33:59 +0000
Subject: [PATCH 1/8] Initial plan
From c21eb67bcfc4d362de8f251187b6402cb7b90eff Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 23 Nov 2025 15:41:18 +0000
Subject: [PATCH 2/8] Fix keyboard dismiss and value update issue on Android
for SfNumericEntry
Co-authored-by: PaulAndersonS <42271912+PaulAndersonS@users.noreply.github.com>
---
.../NumericEntry/SfNumericEntry.Methods.cs | 36 +++++++++++++++++++
1 file changed, 36 insertions(+)
diff --git a/maui/src/NumericEntry/SfNumericEntry.Methods.cs b/maui/src/NumericEntry/SfNumericEntry.Methods.cs
index 32aaf8df..e890ac60 100644
--- a/maui/src/NumericEntry/SfNumericEntry.Methods.cs
+++ b/maui/src/NumericEntry/SfNumericEntry.Methods.cs
@@ -2772,6 +2772,7 @@ void AndroidEntryHandler(object? sender)
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;
}
@@ -2877,6 +2878,41 @@ 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)
+ {
+ // When focus is lost (HasFocus is false), ensure proper cleanup
+ // This is crucial for Android where tapping outside (e.g., on a button)
+ // doesn't always trigger the proper unfocus sequence in MAUI
+ if (!e.HasFocus && _textBox != null)
+ {
+ // The native Android entry has lost focus
+ // Ensure the value is updated if ValueChangeMode is OnLostFocus (default)
+ // or if there are pending changes that need to be committed
+ MainThread.BeginInvokeOnMainThread(() =>
+ {
+ // Only process if the text box still exists and lost focus
+ if (_textBox != null)
+ {
+ // Update the value to ensure it's synchronized with the bound property
+ // This handles the case where a button is clicked before the value was updated
+ if (ValueChangeMode == ValueChangeMode.OnLostFocus)
+ {
+ UpdateValue();
+ FormatValue();
+ }
+ }
+ });
+ }
+ }
#endif
///
From 51bcd5b5e07779132e4eb7b21ad3efe584d81251 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 23 Nov 2025 15:42:02 +0000
Subject: [PATCH 3/8] Refine Android focus change handler to avoid duplicate
processing
Co-authored-by: PaulAndersonS <42271912+PaulAndersonS@users.noreply.github.com>
---
.../NumericEntry/SfNumericEntry.Methods.cs | 22 ++++++++-----------
1 file changed, 9 insertions(+), 13 deletions(-)
diff --git a/maui/src/NumericEntry/SfNumericEntry.Methods.cs b/maui/src/NumericEntry/SfNumericEntry.Methods.cs
index e890ac60..71d19646 100644
--- a/maui/src/NumericEntry/SfNumericEntry.Methods.cs
+++ b/maui/src/NumericEntry/SfNumericEntry.Methods.cs
@@ -2889,26 +2889,22 @@ void AndroidEntry_Click(object? sender, EventArgs e)
/// The focus change event arguments.
void AndroidEntry_FocusChange(object? sender, Android.Views.View.FocusChangeEventArgs e)
{
- // When focus is lost (HasFocus is false), ensure proper cleanup
+ // 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 trigger the proper unfocus sequence in MAUI
+ // doesn't always properly sync the native focus state with MAUI's focus state
if (!e.HasFocus && _textBox != null)
{
// The native Android entry has lost focus
- // Ensure the value is updated if ValueChangeMode is OnLostFocus (default)
- // or if there are pending changes that need to be committed
+ // 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(() =>
{
- // Only process if the text box still exists and lost focus
- if (_textBox != null)
+ if (_textBox != null && _textBox.IsFocused)
{
- // Update the value to ensure it's synchronized with the bound property
- // This handles the case where a button is clicked before the value was updated
- if (ValueChangeMode == ValueChangeMode.OnLostFocus)
- {
- UpdateValue();
- FormatValue();
- }
+ // 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();
}
});
}
From fa2285461c3aaf2522091afda72c3dc05f570da5 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 23 Nov 2025 15:44:31 +0000
Subject: [PATCH 4/8] Add proper cleanup for Android event handlers to prevent
memory leaks
Co-authored-by: PaulAndersonS <42271912+PaulAndersonS@users.noreply.github.com>
---
.../NumericEntry/SfNumericEntry.Methods.cs | 37 ++++++++++++++-----
maui/src/NumericEntry/SfNumericEntry.cs | 5 +++
2 files changed, 32 insertions(+), 10 deletions(-)
diff --git a/maui/src/NumericEntry/SfNumericEntry.Methods.cs b/maui/src/NumericEntry/SfNumericEntry.Methods.cs
index 71d19646..188c7e95 100644
--- a/maui/src/NumericEntry/SfNumericEntry.Methods.cs
+++ b/maui/src/NumericEntry/SfNumericEntry.Methods.cs
@@ -2764,17 +2764,34 @@ 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;
- androidEntry.FocusChange += AndroidEntry_FocusChange;
- // Disable EmojiCompat to prevent the IllegalArgumentException crash on Android when start typing text.
- androidEntry.EmojiCompatEnabled = false;
+ 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;
+ }
+ else if (textBox.Handler == null && _androidEntry != null)
+ {
+ // Clean up event handlers when handler is removed
+ _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
}
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
From 4f38ea111b0e0a84a71b7e96213e620d7305b96b Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 23 Nov 2025 15:45:51 +0000
Subject: [PATCH 5/8] Address code review feedback: improve cleanup logic and
thread safety
Co-authored-by: PaulAndersonS <42271912+PaulAndersonS@users.noreply.github.com>
---
.../NumericEntry/SfNumericEntry.Methods.cs | 28 +++++++++++--------
1 file changed, 16 insertions(+), 12 deletions(-)
diff --git a/maui/src/NumericEntry/SfNumericEntry.Methods.cs b/maui/src/NumericEntry/SfNumericEntry.Methods.cs
index 188c7e95..235b4956 100644
--- a/maui/src/NumericEntry/SfNumericEntry.Methods.cs
+++ b/maui/src/NumericEntry/SfNumericEntry.Methods.cs
@@ -2766,6 +2766,19 @@ void AndroidEntryHandler(object? sender)
#if ANDROID
if (sender is SfEntryView textBox)
{
+ // Clean up existing event handlers if we have a previous Android entry
+ 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;
+ }
+
+ // 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
@@ -2781,17 +2794,6 @@ void AndroidEntryHandler(object? sender)
// Disable EmojiCompat to prevent the IllegalArgumentException crash on Android when start typing text.
androidEntry.EmojiCompatEnabled = false;
}
- else if (textBox.Handler == null && _androidEntry != null)
- {
- // Clean up event handlers when handler is removed
- _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
}
@@ -2909,13 +2911,15 @@ void AndroidEntry_FocusChange(object? sender, Android.Views.View.FocusChangeEven
// 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 && _textBox != null)
+ 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(() =>
{
+ // Re-check _textBox for null inside MainThread callback
+ // as it could be set to null on another thread
if (_textBox != null && _textBox.IsFocused)
{
// MAUI still thinks the entry is focused, but the native control has lost focus
From 74789686aec94ad14237b0a9c7dc1439f61a099e Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 23 Nov 2025 15:47:27 +0000
Subject: [PATCH 6/8] Add comprehensive null checks and cleanup to UnHookEvents
Co-authored-by: PaulAndersonS <42271912+PaulAndersonS@users.noreply.github.com>
---
.../NumericEntry/SfNumericEntry.Methods.cs | 26 ++++++++++++++++---
1 file changed, 23 insertions(+), 3 deletions(-)
diff --git a/maui/src/NumericEntry/SfNumericEntry.Methods.cs b/maui/src/NumericEntry/SfNumericEntry.Methods.cs
index 235b4956..24e8decb 100644
--- a/maui/src/NumericEntry/SfNumericEntry.Methods.cs
+++ b/maui/src/NumericEntry/SfNumericEntry.Methods.cs
@@ -2908,6 +2908,12 @@ void AndroidEntry_Click(object? sender, EventArgs e)
/// The focus change event arguments.
void AndroidEntry_FocusChange(object? sender, Android.Views.View.FocusChangeEventArgs e)
{
+ // Verify sender is valid before processing
+ if (sender == null || _androidEntry == 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
@@ -2918,9 +2924,9 @@ void AndroidEntry_FocusChange(object? sender, Android.Views.View.FocusChangeEven
// If so, we need to trigger the unfocus to ensure proper event handling
MainThread.BeginInvokeOnMainThread(() =>
{
- // Re-check _textBox for null inside MainThread callback
- // as it could be set to null on another thread
- if (_textBox != null && _textBox.IsFocused)
+ // Re-check all references for null inside MainThread callback
+ // as they could be set to null on another thread during cleanup
+ if (_textBox != null && _androidEntry != null && _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)
@@ -2948,6 +2954,20 @@ void UnHookEvents()
_textBox.Unfocused -= TextBoxOnLostFocus;
}
+
+#if ANDROID
+ // Clean up Android-specific event handlers
+ 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
}
#endregion
From 21de4b5de7d3456d124984ecc7319f1486ef9c29 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 23 Nov 2025 15:49:22 +0000
Subject: [PATCH 7/8] Extract cleanup method and fix race condition with local
references
Co-authored-by: PaulAndersonS <42271912+PaulAndersonS@users.noreply.github.com>
---
.../NumericEntry/SfNumericEntry.Methods.cs | 60 ++++++++++---------
1 file changed, 32 insertions(+), 28 deletions(-)
diff --git a/maui/src/NumericEntry/SfNumericEntry.Methods.cs b/maui/src/NumericEntry/SfNumericEntry.Methods.cs
index 24e8decb..b97dde6a 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.
@@ -2767,16 +2786,7 @@ void AndroidEntryHandler(object? sender)
if (sender is SfEntryView textBox)
{
// Clean up existing event handlers if we have a previous Android entry
- 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;
- }
+ CleanupAndroidEntryEvents();
// Subscribe to new handler's platform view if available
if (textBox.Handler != null && textBox.Handler.PlatformView is AndroidX.AppCompat.Widget.AppCompatEditText androidEntry)
@@ -2908,8 +2918,13 @@ void AndroidEntry_Click(object? sender, EventArgs e)
/// The focus change event arguments.
void AndroidEntry_FocusChange(object? sender, Android.Views.View.FocusChangeEventArgs e)
{
- // Verify sender is valid before processing
- if (sender == null || _androidEntry == null)
+ // 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;
}
@@ -2924,14 +2939,14 @@ void AndroidEntry_FocusChange(object? sender, Android.Views.View.FocusChangeEven
// If so, we need to trigger the unfocus to ensure proper event handling
MainThread.BeginInvokeOnMainThread(() =>
{
- // Re-check all references for null inside MainThread callback
- // as they could be set to null on another thread during cleanup
- if (_textBox != null && _androidEntry != null && _textBox.IsFocused)
+ // 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();
+ textBox.Unfocus();
}
});
}
@@ -2955,19 +2970,8 @@ void UnHookEvents()
}
-#if ANDROID
// Clean up Android-specific event handlers
- 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
+ CleanupAndroidEntryEvents();
}
#endregion
From ccb4f5e3269659b9765a76804dc1f1018bb0b31e Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 23 Nov 2025 15:50:28 +0000
Subject: [PATCH 8/8] Add exception handling for disposed control edge case
Co-authored-by: PaulAndersonS <42271912+PaulAndersonS@users.noreply.github.com>
---
.../NumericEntry/SfNumericEntry.Methods.cs | 22 +++++++++++++------
1 file changed, 15 insertions(+), 7 deletions(-)
diff --git a/maui/src/NumericEntry/SfNumericEntry.Methods.cs b/maui/src/NumericEntry/SfNumericEntry.Methods.cs
index b97dde6a..9c4652f1 100644
--- a/maui/src/NumericEntry/SfNumericEntry.Methods.cs
+++ b/maui/src/NumericEntry/SfNumericEntry.Methods.cs
@@ -2939,14 +2939,22 @@ void AndroidEntry_FocusChange(object? sender, Android.Views.View.FocusChangeEven
// If so, we need to trigger the unfocus to ensure proper event handling
MainThread.BeginInvokeOnMainThread(() =>
{
- // Use the local captured references to avoid race conditions
- // Only proceed if the textBox is still focused
- if (textBox.IsFocused)
+ try
{
- // 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();
+ // 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
}
});
}