From 3070dcfae86c4d970808e5d754bb33d3e4495bf2 Mon Sep 17 00:00:00 2001 From: redth Date: Wed, 11 Mar 2026 14:53:02 -0400 Subject: [PATCH] Fix preferences listing crash with non-string types on Android Android SharedPreferences stores typed values (int, bool, double, etc.) and throws ClassCastException when reading with the wrong type. The List handler always called Preferences.Get which crashes when int or other non-string values exist. Added ReadPreferenceValue helper that tries each supported type in order (string, int, bool, double, long, float) catching cast failures. Used by both List and Get (when no type param specified). List response now includes a 'type' field for each entry. Fixes: Storing an int preference then listing would return 'java.lang. Integer cannot be cast to java.lang.String' and hide all preferences. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../DevFlowAgentService.cs | 106 +++++++++++++++--- 1 file changed, 92 insertions(+), 14 deletions(-) diff --git a/src/MauiDevFlow.Agent.Core/DevFlowAgentService.cs b/src/MauiDevFlow.Agent.Core/DevFlowAgentService.cs index 486def3..d0dc423 100644 --- a/src/MauiDevFlow.Agent.Core/DevFlowAgentService.cs +++ b/src/MauiDevFlow.Agent.Core/DevFlowAgentService.cs @@ -3609,10 +3609,8 @@ private Task HandlePreferencesList(HttpRequest request) var entries = new List(); foreach (var key in keys.OrderBy(k => k)) { - var value = sharedName != null - ? Preferences.Get(key, (string?)null, sharedName) - : Preferences.Get(key, (string?)null); - entries.Add(new { key, value, sharedName }); + var (value, type) = ReadPreferenceValue(key, sharedName); + entries.Add(new { key, value, type, sharedName }); } return Task.FromResult(HttpResponse.Json(new { keys = entries })); } @@ -3622,6 +3620,76 @@ private Task HandlePreferencesList(HttpRequest request) } } + /// + /// Read a preference value trying all supported types. + /// Android SharedPreferences stores typed values and throws ClassCastException + /// if you read with the wrong type, so we try each in turn. + /// + private (object? Value, string Type) ReadPreferenceValue(string key, string? sharedName) + { + // Try string first (most common) + try + { + var s = sharedName != null + ? Preferences.Get(key, (string?)null, sharedName) + : Preferences.Get(key, (string?)null); + return (s, "string"); + } + catch { } + + // Try int + try + { + var i = sharedName != null + ? Preferences.Get(key, int.MinValue, sharedName) + : Preferences.Get(key, int.MinValue); + return (i, "int"); + } + catch { } + + // Try bool + try + { + var b = sharedName != null + ? Preferences.Get(key, false, sharedName) + : Preferences.Get(key, false); + return (b, "bool"); + } + catch { } + + // Try double + try + { + var d = sharedName != null + ? Preferences.Get(key, double.NaN, sharedName) + : Preferences.Get(key, double.NaN); + if (!double.IsNaN(d)) return (d, "double"); + } + catch { } + + // Try long + try + { + var l = sharedName != null + ? Preferences.Get(key, long.MinValue, sharedName) + : Preferences.Get(key, long.MinValue); + return (l, "long"); + } + catch { } + + // Try float + try + { + var f = sharedName != null + ? Preferences.Get(key, float.NaN, sharedName) + : Preferences.Get(key, float.NaN); + if (!float.IsNaN(f)) return (f, "float"); + } + catch { } + + return (null, "unknown"); + } + private Task HandlePreferencesGet(HttpRequest request) { try @@ -3630,18 +3698,28 @@ private Task HandlePreferencesGet(HttpRequest request) return Task.FromResult(HttpResponse.Error("key is required")); request.QueryParams.TryGetValue("sharedName", out var sharedName); - var type = request.QueryParams.GetValueOrDefault("type", "string"); + var requestedType = request.QueryParams.GetValueOrDefault("type", null); - object? value = type.ToLowerInvariant() switch + object? value; + string type; + if (requestedType != null) { - "int" or "integer" => sharedName != null ? Preferences.Get(key, 0, sharedName) : Preferences.Get(key, 0), - "bool" or "boolean" => sharedName != null ? Preferences.Get(key, false, sharedName) : Preferences.Get(key, false), - "double" => sharedName != null ? Preferences.Get(key, 0.0, sharedName) : Preferences.Get(key, 0.0), - "float" => sharedName != null ? Preferences.Get(key, 0f, sharedName) : Preferences.Get(key, 0f), - "long" => sharedName != null ? Preferences.Get(key, 0L, sharedName) : Preferences.Get(key, 0L), - "datetime" => sharedName != null ? Preferences.Get(key, DateTime.MinValue, sharedName) : Preferences.Get(key, DateTime.MinValue), - _ => sharedName != null ? Preferences.Get(key, (string?)null, sharedName) : Preferences.Get(key, (string?)null), - }; + type = requestedType; + value = type.ToLowerInvariant() switch + { + "int" or "integer" => sharedName != null ? Preferences.Get(key, 0, sharedName) : Preferences.Get(key, 0), + "bool" or "boolean" => sharedName != null ? Preferences.Get(key, false, sharedName) : Preferences.Get(key, false), + "double" => sharedName != null ? Preferences.Get(key, 0.0, sharedName) : Preferences.Get(key, 0.0), + "float" => sharedName != null ? Preferences.Get(key, 0f, sharedName) : Preferences.Get(key, 0f), + "long" => sharedName != null ? Preferences.Get(key, 0L, sharedName) : Preferences.Get(key, 0L), + "datetime" => sharedName != null ? Preferences.Get(key, DateTime.MinValue, sharedName) : Preferences.Get(key, DateTime.MinValue), + _ => sharedName != null ? Preferences.Get(key, (string?)null, sharedName) : Preferences.Get(key, (string?)null), + }; + } + else + { + (value, type) = ReadPreferenceValue(key, sharedName); + } var exists = sharedName != null ? Preferences.ContainsKey(key, sharedName) : Preferences.ContainsKey(key); return Task.FromResult(HttpResponse.Json(new { key, value, type, exists, sharedName }));