4

I’m trying to get a step count from Samsung Health using Xamarin Forms (for an Android App).

I tried using SamsungHealthForXamarin to recreate the SimpleHealth sample project from the Samsung Health SDK.

The Java sample project uses anonymous inline classes within the same scope that we can't do in C#. So it appears you have to build out an IConnectionListener interface instead.

Does anyone have any experience getting "step data" from Samsung Health using Xamarin Forms (C#) ? I’d love to see a super simple example of just getting today's step count in C#. It doesn't seem like it should be so difficult.

Brad Bamford
  • 3,783
  • 3
  • 22
  • 30

1 Answers1

3

If I have understood you correctly, your issue is mostly with how to have the connection listener know what exactly its data store is. Starting from the fact that all listeners on Java are simply a class in C#, that implements the needed interface, we can create our listener like so:

public class ConnectionListener : HealthDataStore.IConnectionListener
{
    internal HealthDataStore Store { get; set; }

    public void OnConnected()
    {
        var stepCountReporter = new StepCountReporter(Store);
        // NOTE: Check for permissions here
        stepCountReporter.Start();
    }
    public void OnConnectionFailed(HealthConnectionErrorResult p0)
    {
        // Health data service is not available.
    }
    public void OnDisconnected()
    {
        Store.DisconnectService();
    }
}

The important line is the third one - the internal property Store. Here we will keep a reference to our HealthDataStore, which will depend on our listener.

Our service will look like this:

private void InitStepService()
{
    var connectionListener = new ConnectionListener();
    store = new HealthDataStore(this, connectionListener);
    connectionListener.Store = store; // This is the important line
    store.ConnectService();
}

Again, the important line is the third line from the method - we are assigning the store to our listener's property, so that we can have its reference there.

The same approach will go for the StepCountReporter class:

public class StepCountReporter
{
    private readonly HealthDataStore store;
    private const long OneDayInMillis = 24 * 60 * 60 * 1000L;

    public StepCountReporter(HealthDataStore store)
    {
        this.store = store;
    }

    public void Start()
    {
        HealthDataObserver.AddObserver(store, HealthConstants.StepCount.HealthDataType,
            new StepObserver(ReadTodayStepCount));
        ReadTodayStepCount();
    }

    private void ReadTodayStepCount()
    {
        var resolver = new HealthDataResolver(store, null);

        // Set time range from start time of today to the current time
        var startTime = DateTime.Now.Date.Ticks;
        var endTime = startTime + OneDayInMillis;

        ReadRequestBuilder requestBuilder = new ReadRequestBuilder()
            .SetDataType(HealthConstants.StepCount.HealthDataType)
            .SetProperties(new[] { HealthConstants.StepCount.Count })
            .SetLocalTimeRange(HealthConstants.StepCount.StartTime, HealthConstants.StepCount.TimeOffset,
                startTime, endTime);

        IReadRequest request = requestBuilder.Build();

        try
        {
            resolver.Read(request).SetResultListener(new StepResultHolderResultListener());
        }
        catch (Exception)
        {
            // Getting step count fails.
        }
    }
}

You will need 2 additional classes here - StepResultHolderResultListener & StepObserver

StepResultHolderResultListener

public class StepResultHolderResultListener : IHealthResultHolderResultListener
{
    public void OnResult(Java.Lang.Object resultObject)
    {
        if (resultObject is ReadResult result)
        {
            int count = 0;

            try
            {
                var iterator = result.Iterator();
                while (iterator.HasNext)
                {
                    var data = (HealthData) iterator.Next();
                    count += data.GetInt(HealthConstants.StepCount.Count);
                }
            }
            finally
            {
                result.Close();
            }

            // Update your UI here with the count variable
        }
    }

    // Rest of the methods from the interface
}

StepObserver

public class StepObserver : HealthDataObserver
{
    private readonly Action readTodayStepCountAction;

    private StepObserver(Handler p0)
        : base(p0)
    {
    }

    public StepObserver(Action readTodayStepCountAction)
        : this((Handler) null)
    {
        this.readTodayStepCountAction = readTodayStepCountAction;
    }

    public override void OnChange(string dataTypeName)
    {
        readTodayStepCountAction();
    }
}

After that, you can notify the UI in whatever way you wish - using Xamarin's MessagingCenter, using events, using some other kind of observer logic - depending on your project's architecture.

A few side notes on the topic:

  1. Please note that the project has been moved to Bitbucket as it says in the README.md
  2. As always, there are some restrictions, that you should take into consideration. Samsung Health docs - Restrictions

Samsung Health Android SDK runs on devices with Android 6.0 Marshmallow (API level 23) or above.

It requires Samsung Health installation. The latest SDK works with Samsung Health 6.2 or above. See the SDK and Samsung Health’s compatible versions here.

An app’s targetSdkVersion that uses Samsung Health Android SDK should be 26 or above.

Samsung Health is available on all Samsung smartphones and also non-Samsung Android smartphones with Marshmallow or above.

Mihail Duchev
  • 4,691
  • 10
  • 25
  • 32
  • This is helpful, but the app crashes every time on **result.ToArray()** and result.Count is always 0 in StepResultHolderResultListener. – Brad Bamford May 27 '20 at 19:07
  • That's strange. Is there any useful information in the `result` object? – Mihail Duchev May 27 '20 at 19:22
  • Not seeing much, I was able to get Count to contain data by setting starttime = 0; and endTime = long.MaxValue; However, the app still crashes at ToArray. JNI DETECTED ERROR IN APPLICATION: jarray argument has non-array type: com.samsung.android.sdk.healthdata.HealthDataResolver$ReadResult in call to GetArrayLength from void StepResultHolderResultListener.n_onResult – Brad Bamford May 27 '20 at 19:39
  • 1
    This Worked: `var iterator = result.Iterator(); while (iterator.HasNext) { var data = (HealthData)iterator.Next(); count += data.GetInt(HealthConstants.StepCount.Count); }` – Brad Bamford May 27 '20 at 20:00
  • I was just trying a similar approach. It should be working now, at least according to the demo app. – Mihail Duchev May 27 '20 at 20:10