Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Bug] [platform/android] [area-essentials] Shared Preference Android #24397

Open
AugPav opened this issue Aug 23, 2024 · 6 comments
Open

[Bug] [platform/android] [area-essentials] Shared Preference Android #24397

AugPav opened this issue Aug 23, 2024 · 6 comments
Labels
area-essentials Essentials: Device, Display, Connectivity, Secure Storage, Sensors, App Info platform/android 🤖 t/enhancement ☀️ New feature or request
Milestone

Comments

@AugPav
Copy link

AugPav commented Aug 23, 2024

Description

Error when trying to read a shared preference of type long

Steps to Reproduce

1 - The shared preference is created.

new DateTimeOffset(DateTime.Now).ToUnixTimeSeconds() is of type long

Preferences.Set("SYNC_MASTER_DATE", new DateTimeOffset(DateTime.Now).ToUnixTimeSeconds());

2 - An attempt is made to read the shared preference.

var SyncMasterLast = Preferences.Get("SYNC_MASTER_DATE", 0)

Error

ex {Java.Lang.ClassCastException: java.lang.Long cannot be cast to java.lang.Integer
at Java.Interop.JniEnvironment.InstanceMethods.CallIntMethod(JniObjectReference instance, JniMethodInfo method, JniArgumentValue* args) in /Users/runner/work/1/s/xamarin-android/external/Java.Interop/src/Java.Interop/obj/Release/net7.0/JniEnvironment.g.cs:line 20203
at Android.Runtime.JNIEnv.CallIntMethod(IntPtr jobject, IntPtr jmethod, JValue* parms) in /Users/runner/work/1/s/xamarin-android/src/Mono.Android/Android.Runtime/JNIEnv.g.cs:line 192
at Android.Content.ISharedPreferencesInvoker.GetInt(String key, Int32 defValue) in /Users/runner/work/1/s/xamarin-android/src/Mono.Android/obj/Release/net8.0/android-34/mcw/Android.Content.ISharedPreferences.cs:line 830
at Microsoft.Maui.Storage.PreferencesImplementation.Get[Int32](String key, Int32 defaultValue, String sharedName) in //src/Essentials/src/Preferences/Preferences.android.cs:line 110
at Microsoft.Maui.Storage.Preferences.Get(String key, Int32 defaultValue, String sharedName) in /
/src/Essentials/src/Preferences/Preferences.shared.cs:line 185
at Microsoft.Maui.Storage.Preferences.Get(String key, Int32 defaultValue) in /_/src/Essentials/src/Preferences/Preferences.shared.cs:line 116
at SelfInspectionMaui.Services.SyncMaster.SyncMasterLogica() in D:\DevRd\SelfInspectionMaui\SelfInspectionMaui\Services\SyncMaster.cs:line 96
--- End of managed Java.Lang.ClassCastException stack trace ---
java.lang.ClassCastException: java.lang.Long cannot be cast to java.lang.Integer
at android.app.SharedPreferencesImpl.getInt(SharedPreferencesImpl.java:321)
at mono.java.lang.RunnableImplementor.n_run(Native Method)
at mono.java.lang.RunnableImplementor.run(RunnableImplementor.java:31)
at android.os.Handler.handleCallback(Handler.java:958)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loopOnce(Looper.java:230)
at android.os.Looper.loop(Looper.java:319)
at android.app.ActivityThread.main(ActivityThread.java:8919)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:578)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1103)

--- End of managed Java.Lang.ClassCastException stack trace ---
java.lang.ClassCastException: java.lang.Long cannot be cast to java.lang.Integer
at android.app.SharedPreferencesImpl.getInt(SharedPreferencesImpl.java:321)
at mono.java.lang.RunnableImplementor.n_run(Native Method)
at mono.java.lang.RunnableImplementor.run(RunnableImplementor.java:31)
at android.os.Handler.handleCallback(Handler.java:958)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loopOnce(Looper.java:230)
at android.os.Looper.loop(Looper.java:319)
at android.app.ActivityThread.main(ActivityThread.java:8919)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:578)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1103)
} Java.Lang.ClassCastException

Link to public reproduction project repository

No response

Version with bug

8.0.80 SR8

Is this a regression from previous behavior?

Not sure, did not test other versions

Last version that worked well

Unknown/Other

Affected platforms

Android

Affected platform versions

Android 14, Android 13

Did you find any workaround?

Why does this error occur?

When trying to read the shared preference, the following code is executed for [Android]

https://github.com/dotnet/maui/blob/cf42c193957a530af1a0551284c40e72e55780f9/src/Essentials/src/Preferences/Preferences.android.cs

Line 1 	public T Get<T>(string key, T defaultValue, string sharedName)										
Line 2			{								
Line 3				lock (locker)							
Line 4				{							
Line 5					object value = null;						
Line 6					using (var sharedPreferences = GetSharedPreferences(sharedName))						
Line 7					{						
Line 8						if (defaultValue == null)					
Line 9						{					
Line 10							value = sharedPreferences.GetString(key, null);				
Line 11						}					
Line 12						else					
Line 13						{					
Line 14							switch (defaultValue)				
Line 15							{				
Line 16								case int i:			
Line 17									value = sharedPreferences.GetInt(key, i);		
Line 18									break;		
Line 19								case bool b:			
Line 20									value = sharedPreferences.GetBoolean(key, b);		
Line 21									break;		
Line 22								case long l:			
Line 23									value = sharedPreferences.GetLong(key, l);		
Line 24									break;		
Line 25								case double d:			
Line 26									var savedDouble = sharedPreferences.GetString(key, null);		
Line 27									if (string.IsNullOrWhiteSpace(savedDouble))		
Line 28									{		
Line 29										value = defaultValue;	
Line 30									}		
Line 31									else		
Line 32									{		
Line 33										if (!double.TryParse(savedDouble, NumberStyles.Number | NumberStyles.AllowExponent, CultureInfo.InvariantCulture, out var outDouble))	
Line 34										{	
Line 35											var maxString = Convert.ToString(double.MaxValue, CultureInfo.InvariantCulture);
Line 36											outDouble = savedDouble.Equals(maxString, StringComparison.Ordinal) ? double.MaxValue : double.MinValue;
Line 37										}	
Line 38											
Line 39										value = outDouble;	
Line 40									}		
Line 41									break;		
Line 42								case float f:			
Line 43									value = sharedPreferences.GetFloat(key, f);		
Line 44									break;		
Line 45								case string s:			
Line 46									// the case when the string is not null		
Line 47									value = sharedPreferences.GetString(key, s);		
Line 48									break;		
Line 49								case DateTime dt:			
Line 50									var encodedValue = sharedPreferences.GetLong(key, dt.ToBinary());		
Line 51									value = DateTime.FromBinary(encodedValue);		
Line 52									break;		
Line 53							}				
Line 54						}					
Line 55					}						
Line 56											
Line 57					return (T)value;						
Line 58				}							
Line 59			}

On line 17 - value = sharedPreferences.GetInt(key, i); - the error occurs because you are trying to read an integer when the shared preference has a long type data.

The solution is to read the shared preference with the following code:

var SyncMasterLast = Preferences.Get("SYNC_MASTER_DATE", (long)0)

So, either the documentation is wrong:

https://learn.microsoft.com/en-us/dotnet/maui/platform-integration/storage/preferences?view=net-maui-8.0&tabs=android

Get preferences
To retrieve a value from preferences, you pass the key of the preference, followed by the default value when the key doesn't exist:

Where it says that the second parameter corresponds to the default value, it should say the default value and the type of data to be obtained will be taken from that value.

Or you should fix the code for each of the platform.

Relevant log output

No response

@AugPav AugPav added the t/bug Something isn't working label Aug 23, 2024
Copy link
Contributor

Hi I'm an AI powered bot that finds similar issues based off the issue title.

Please view the issues below to see if they solve your problem, and if the issue describes your problem please consider closing this one and thumbs upping the other issue to help us prioritize it. Thank you!

Closed similar issues:

Note: You can give me feedback by thumbs upping or thumbs downing this comment.

@AugPav AugPav changed the title [Bug] Shared Preference Android [Bug] [platform/android] [area-essentials] Shared Preference Android Aug 23, 2024
@samhouts samhouts added platform/android 🤖 area-essentials Essentials: Device, Display, Connectivity, Secure Storage, Sensors, App Info labels Aug 23, 2024
@Cheesebaron
Copy link
Contributor

The interface clearly states what the return value is based on:

T Get<T>(string key, T defaultValue, string? sharedName = null);

So in your case, as you found out yourself, you need to use it with the appropriate default value as it controls the return type as well.

Or you should fix the code for each of the platform

How will you infer the type if you are not specifying it explicitly? If you want to cover your behind, then always use the API like:

var myVal = Preferences.Get<YourType>("key", defaultValue);

Is this even a bug?

@AugPav
Copy link
Author

AugPav commented Oct 14, 2024

The interface clearly states what the return value is based on:

T Get(string key, T defaultValue, string? sharedName = null);
So in your case, as you found out yourself, you need to use it with the appropriate default value as it controls the return type as well.

Or you should fix the code for each of the platform

How will you infer the type if you are not specifying it explicitly? If you want to cover your behind, then always use the API like:

var myVal = Preferences.Get("key", defaultValue);
Is this even a bug?

It would be clearer if the

Preferences.Get(Shared Name, default value)

was changed to

Preferences.Get(Shared Name, default value, Type)

But that's just my impression.

@Cheesebaron
Copy link
Contributor

It would be clearer if the

Preferences.Get(Shared Name, default value)

was changed to

Preferences.Get(Shared Name, default value, Type)

But that's just my impression.

How would that work, then the signature would be what exactly? What your impression is, is already implied by the signature of T Get<T>(...). You can be explicit and specify a specific type rather than rely on defaultValue to imply the return type and generic type. So instead of writing:

var myValue = Preferences.Get(key, defaultValue);

Then you can achieve what you want with:

var myValue = Preferences.Get<long>(key, defaultValue);

or

long myValue = Preferences.Get(key, defaultValue);

@mattleibow mattleibow added this to the Backlog milestone Dec 2, 2024
@mattleibow
Copy link
Member

We could also detect thigs like loading an int/short and then load as a long. Then check to see if it fits in the int/short and if it does we can cast to that.

Not sure if Android supports storing an int and then loading as long...

@mattleibow mattleibow added t/enhancement ☀️ New feature or request and removed t/bug Something isn't working labels Dec 2, 2024
@Cheesebaron
Copy link
Contributor

We could also detect thigs like loading an int/short and then load as a long. Then check to see if it fits in the int/short and if it does we can cast to that.

Not sure if Android supports storing an int and then loading as long...

I find that a bit problematic. If you store a long, then cast it to int, what happens if that value is overflowing the int? Runtime error? Something else?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-essentials Essentials: Device, Display, Connectivity, Secure Storage, Sensors, App Info platform/android 🤖 t/enhancement ☀️ New feature or request
Projects
None yet
Development

No branches or pull requests

4 participants