Search code examples
iosxamarin.ioshealthkithksamplequery

Why is my HKHealthStore.DeleteObjectAsync method not executing and returning an object in Xamarin?


I am trying to delete an HKObject added by my Xamarin.iOS app. I am able to successfully save and query the data from the Apple HealthKit, but the HKHealthStore.DeleteObjectAsync (and HKHealthStore.DeleteObject) method vanishes after it is run. There is no error returned and the next line of code does not get hit. Below is sample code that can be used to recreate the issue. Since there is no response or exception, I am not sure where to start trying to troubleshoot this issue. Does anyone know how I can get the DeleteObjectAsync (or DeleteObject) working in Xamarin.iOS?

The code for the AsyncHelpers class that I am using can be found here.

Zipped Project

public partial class ViewController : UIViewController
{
    string Id => "54b50df8-35ab-4f53-ac19-0ad0c3d3551a";

    protected ViewController(IntPtr handle) : base(handle)
    {
    }

    public override void ViewDidLoad()
    {
        base.ViewDidLoad();
    }

    partial void AddUsage(UIButton sender) //<- Button TouchUpInside
    {
        Tuple<HKUnit, double> entryItems = new Tuple<HKUnit, double>(HKUnit.CreateLiterUnit(HKMetricPrefix.Milli), 10);

        HKQuantity quantity = HKQuantity.FromQuantity(entryItems.Item1, entryItems.Item2);
        HKQuantityType qType = HKQuantityType.Create(HKQuantityTypeIdentifier.DietaryWater);
        HKMetadata metadata = new HKMetadata { ExternalUuid = Id };
        HKQuantitySample sample = HKQuantitySample.FromType(qType, quantity, new NSDate(), new NSDate(), metadata);
        using (HKHealthStore store = new HKHealthStore())
        {
            AsyncHelpers.RunSync(async () =>
            {
                Tuple<bool, NSError> saveResult = await store.SaveObjectAsync(sample);
            });

        }
    }

    partial void DeleteUsage(UIButton sender) //<- Button TouchUpInside
    {
        using (HKHealthStore store = new HKHealthStore())
        {
            NSCalendar calendar = NSCalendar.CurrentCalendar;
            NSDate now = new NSDate();
            NSDateComponents components = calendar.Components(NSCalendarUnit.Year | NSCalendarUnit.Month | NSCalendarUnit.Day, now);
            NSDate startDate = calendar.DateByAddingUnit(NSCalendarUnit.Day, -2, now, NSCalendarOptions.None);
            NSDate endDate = calendar.DateByAddingUnit(NSCalendarUnit.Day, 1, now, NSCalendarOptions.None);
            HKQuantityType qType = HKQuantityType.Create(HKQuantityTypeIdentifier.DietaryWater);
            NSPredicate pred = HKQuery.GetPredicateForSamples(startDate, endDate, HKQueryOptions.None);
            HKSampleQuery query = new HKSampleQuery(qType, pred, 0, null, (sampleQuery, results, error) =>
            {
                HKObject[] arr = results.Where(r => r.Metadata.ExternalUuid == Id).ToArray();
                if (arr.Length > 0)
                {
                    AsyncHelpers.RunSync(async () =>
                        {
                            Tuple<bool, NSError> deleteResult = await store.DeleteObjectAsync(arr.First()); // <- The breakpoint on this line is hit
                            string s = "s"; // <- The breakpoint on this line isn't ever hit
                        });
                }
            });
            store.ExecuteQuery(query);
        }
    }
}

Solution

  • Couple things:

    1) Per the HealthKit docs

    You need only a single HealthKit store per app. These are long-lived objects. Create the store once, and keep a reference for later use.

    So you shouldn't be wrapping your HKHealthStore in a using statement.

    2) Your AsyncHelpers are eating the exception or doing something else funky likely because you because you're in the context of a callback on a background thread when trying to delete.

    This updated code works:

    public partial class ViewController : UIViewController
    {
        HKHealthStore Store = new HKHealthStore();
    
        string Id => "54b50df8-35ab-4f53-ac19-0ad0c3d3551a";
    
        protected ViewController(IntPtr handle) : base(handle)
        {
        }
    
        public override void ViewDidLoad()
        {
            base.ViewDidLoad();
        }
    
        partial void AddUsage(UIButton sender) //<- Button TouchUpInside
        {
            Tuple<HKUnit, double> entryItems = new Tuple<HKUnit, double>(HKUnit.CreateLiterUnit(HKMetricPrefix.Milli), 10);
    
            HKQuantity quantity = HKQuantity.FromQuantity(entryItems.Item1, entryItems.Item2);
            HKQuantityType qType = HKQuantityType.Create(HKQuantityTypeIdentifier.DietaryWater);
            HKMetadata metadata = new HKMetadata { ExternalUuid = Id };
            HKQuantitySample sample = HKQuantitySample.FromType(qType, quantity, new NSDate(), new NSDate(), metadata);
    
            Task.Run(async () =>
            {
                try
                {
                    var save = await Store.SaveObjectAsync(sample);
                    System.Diagnostics.Debug.WriteLine($"{save}");
                }
                catch (Exception ex)
                {
                    System.Diagnostics.Debug.WriteLine($"{ex}");
                }
            });
        }
    
        partial void DeleteUsage(UIButton sender) //<- Button TouchUpInside
        {
            NSCalendar calendar = NSCalendar.CurrentCalendar;
            NSDate now = new NSDate();
            NSDateComponents components = calendar.Components(NSCalendarUnit.Year | NSCalendarUnit.Month | NSCalendarUnit.Day, now);
            NSDate startDate = calendar.DateByAddingUnit(NSCalendarUnit.Day, -2, now, NSCalendarOptions.None);
            NSDate endDate = calendar.DateByAddingUnit(NSCalendarUnit.Day, 1, now, NSCalendarOptions.None);
            HKQuantityType qType = HKQuantityType.Create(HKQuantityTypeIdentifier.DietaryWater);
            NSPredicate pred = HKQuery.GetPredicateForSamples(startDate, endDate, HKQueryOptions.None);
            HKSampleQuery query = new HKSampleQuery(qType, pred, 0, null, (sampleQuery, results, error) =>
            {
                HKObject[] arr = results.Where(r => r.Metadata.ExternalUuid == Id).ToArray();
                if (arr.Length > 0)
                {
                    Task.Run(async () =>
                    {
                        try
                        {
                            var delete = await Store.DeleteObjectAsync(arr.First());
                            System.Diagnostics.Debug.WriteLine($"{delete}");
                        }
                        catch (Exception ex)
                        {
                            System.Diagnostics.Debug.WriteLine($"{ex}");
                        }
                    });
                }
            });
            Store.ExecuteQuery(query);
        }
    }