Essentials: Android Preferences.Get consecutive calls causes Handle must be valid exception

Created on 9 May 2019  路  10Comments  路  Source: xamarin/Essentials

Bug report best practices: Submitting Issues

Description

Preferences.Get or Preferences.Set randomly throws "Handle must be valid" exception if you access several preferences one after the other.

The reason seems to be the "using" clause inside the Xamarin.Essentials Preference class. The using is disposing the SharedPreferences object but the next call "grabs" the handle of it while it is being disposed.

Steps to Reproduce

  1. Access 3 or 4 preferences using "Preferences.Get" method in OnResume lifecycle method.
  2. Go back and forth between activities for this code to be constantly executing.
  3. The crash can happen as soon as the first call or 20th call.

Expected Behavior

You can get preferences without the app crashing.

Actual Behavior

Unhandled exception causing app to crash

Basic Information

  • Version with issue: 1.1.0
  • IDE: VSForMac
  • Android: Pie

Screenshots

Reproduction Link

bug in-progress need-more-information unable-to-reproduce

All 10 comments

Can you give me an example of how you are calling it?

Hi @jamesmontemagno

I was using it on OnResume of an activity. This activity has fragments but the code was set in the OnResume of the main activity.

Basically have the following:

protected override void OnResume()
{
    var userIsValid = Preferences.Get("IsValidUser", false);
    var lastTimeChecked = Preferences.Get("LastTimeChecked", DateTime.MinValue);

//other unrelated logic
}

Sometimes it fails in the first Get and sometimes in the second one. It doesn't seem to fail with the Set method though which is weird. Further research I was able to see how on some calls, the object returned by GetDefaultSharedPreferences in native android became disposed randomly.

I honestly now think that this might go deeper and might not be related to this library but the GetDefaultSharedPreferences method in Android.

As a workaround I implemented a preference dependency similar to this one but I have a while loop with a try / catch that will make a call to GetDefaultSharedPreferences if the object has been disposed. It actually works just fine but it is a very bad/not professional solution.

Other notes:
When navigating back and forth I'm using the default navigation service injected in my viewmodels using MVVMCross.

Please let me know if I can be of help with anything.

I have the same issue as @JPSiller, and I've noticed another particular.
In my case it happens when I use "Preferences.Get" in the common project of my app, and after some time I use ISharedPreferences.GetString(..) to get a preference (not the same one) in the Android project.

I am unable to reproduce here. I did this:

using Android.App;
using Android.OS;
using Android.Support.V7.App;
using Android.Runtime;
using Android.Widget;
using Xamarin.Essentials;
using System;

namespace App14
{
    [Activity(Label = "@string/app_name", Theme = "@style/AppTheme", MainLauncher = true)]
    public class MainActivity : AppCompatActivity
    {
        protected override void OnCreate(Bundle savedInstanceState)
        {
            base.OnCreate(savedInstanceState);
            Xamarin.Essentials.Platform.Init(this, savedInstanceState);
            // Set our view from the "main" layout resource
            SetContentView(Resource.Layout.activity_main);

            Preferences.Set("IsValidUser", true);
            Preferences.Set("LastTimeChecked", DateTime.Now);
        }

        protected override void OnResume()
        {
            base.OnResume();
            var userIsValid = Preferences.Get("IsValidUser", false);
            var lastTimeChecked = Preferences.Get("LastTimeChecked", DateTime.MinValue);
        }
        public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Android.Content.PM.Permission[] grantResults)
        {
            Xamarin.Essentials.Platform.OnRequestPermissionsResult(requestCode, permissions, grantResults);

            base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
        }
    }
}

I put the app back and forth to the background 50 times which triggers an on resume. I will need a reproduction project to test.

Thanks for taking the time to review it.

Please give me until the end of the week to try to create a simple demo, if I can't replicate it I will close the issue.

I feel it might be running async causing unexpected behavior. Should the preferences object work when running it in async methods?

Yeah, that shouldn't make a difference at all I am pretty sure.

@jamesmontemagno, I managed to reproduce it with the following (crash on sp.Edit()):

```c#
using System;
using Android.App;
using Android.OS;
using Android.Preferences;
using Android.Runtime;
using Android.Support.Design.Widget;
using Android.Support.V7.App;
using Xamarin.Essentials;

namespace TestPref
{
[Activity(Label = "@string/app_name", Theme = "@style/AppTheme.NoActionBar", MainLauncher = true)]
public class MainActivity : AppCompatActivity
{
const string key1 = "key_1";
const string key2 = "key_2";

    protected override void OnCreate(Bundle savedInstanceState)
    {
        base.OnCreate(savedInstanceState);
        Platform.Init(this, savedInstanceState);
        SetContentView(Resource.Layout.activity_main);

        Android.Support.V7.Widget.Toolbar toolbar = FindViewById<Android.Support.V7.Widget.Toolbar>(Resource.Id.toolbar);
        SetSupportActionBar(toolbar);

        FloatingActionButton fab = FindViewById<FloatingActionButton>(Resource.Id.fab);
        fab.Click += FabOnClick;

        var sp = PreferenceManager.GetDefaultSharedPreferences(this);

        Preferences.Set(key1, "k1");

        var e = sp.Edit();
        e.PutString(key2, "k2");
        e.Commit();
    }

    private void FabOnClick(object sender, EventArgs eventArgs)
    {
    }

    public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Android.Content.PM.Permission[] grantResults)
    {
        Platform.OnRequestPermissionsResult(requestCode, permissions, grantResults);

        base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
    }
}

}

```

So this code makes sense why it crashes... you request the shared preferences and we also do as well but we dispose of it and then you try to use it.

If you move the line down under the Set it would work just fine. Kind of in a catch 22 situation here as I remember why we dispose is because if you don't it can also cause issues: https://github.com/jamesmontemagno/Xamarin.Plugins/pull/57

@jamesmontemagno I can't duplicate it in a simple project so I believe this might be an issue in our code where somehow we can trigger that condition. We might have something similar to what @markusian is doing in his test. Since we already workaround it, I'm good closing this one. Thanks for looking into it.

I just tried Xamarin.Essentials Preferences now in our Android app and got the same "Handle must be valid" exception on calls to the native preferences fetched with PreferenceManager.GetDefaultSharedPreferences(context);

So it seems that there is a problem using both kinds at the same time...

Was this page helpful?
0 / 5 - 0 ratings