Android framework version
net10.0-android
Affected platform version
.NET 10 / Xamarin.AndroidX.Health.Connect.ConnectClient 1.1.0.2
Description
The AndroidX Health Connect binding exposes Kotlin suspend APIs as methods that require passing a raw Kotlin.Coroutines.IContinuation.
Example from the binding:
client.PermissionController.GetGrantedPermissions(IContinuation continuation)
client.ReadRecords(ReadRecordsRequest request, IContinuation continuation)
This is technically callable, but difficult to use correctly from .NET. A .NET consumer has to hand-write a coroutine bridge using TaskCompletionSource, detect IntrinsicsKt.COROUTINE_SUSPENDED, implement IContinuation, unwrap Kotlin Result.Failure, map Java exceptions into CLR exceptions, and optionally bridge cancellation.
For .NET consumers, these APIs should ideally be projected as Task / Task methods.
Current required workaround:
static Task<T> Await<T>(Func<IContinuation, object> action, CancellationToken cancelToken)
{
var continuation = new Continuation<T>(cancelToken);
var result = action(continuation);
if (result != IntrinsicsKt.COROUTINE_SUSPENDED) { continuation.SetResult(result); }
return continuation.Task;
}
sealed class Continuation<T> : Java.Lang.Object, IContinuation
{
readonly TaskCompletionSource<T> tcs = new(TaskCreationOptions.RunContinuationsAsynchronously);
readonly CancellationTokenRegistration ctr;
public Continuation(CancellationToken cancelToken) =>
ctr = cancelToken.Register(() => tcs.TrySetCanceled(cancelToken));
public Task<T> Task => tcs.Task;
public ICoroutineContext Context => EmptyCoroutineContext.Instance;
public void ResumeWith(Java.Lang.Object result) => SetResult(result);
public void SetResult(object result)
{
if (result is Java.Lang.Object { Class.Name: "kotlin.Result$Failure" } failure)
{
var field = failure.Class.GetDeclaredField("exception");
field.Accessible = true;
tcs.TrySetException(new Exception(field.Get(failure).ToString()));
}
else { tcs.TrySetResult((T)result); }
ctr.Dispose();
}
}
Expected behavior:
Kotlin suspend APIs should have generated .NET-friendly overloads such as:
Task<ICollection<string>> GetGrantedPermissionsAsync(CancellationToken cancellationToken = default);
Task<ReadRecordsResponse> ReadRecordsAsync(ReadRecordsRequest request, CancellationToken cancellationToken = default);
Actual behavior:
Only the raw continuation-based API is exposed.
Relevant official documentation:
Google’s Health Connect APIs are documented as Kotlin suspend functions. For example:
https://developer.android.com/reference/kotlin/androidx/health/connect/client/HealthConnectClient#readRecords(androidx.health.connect.client.request.ReadRecordsRequest)
Steps to Reproduce:
- Create a .NET Android project targeting net10.0-android36.0.
- Add Xamarin.AndroidX.Health.Connect.ConnectClient version 1.1.0.2.
- Try to call Health Connect suspend APIs such as GetGrantedPermissions or ReadRecords.
- Observe that the API requires IContinuation instead of returning Task.
Workaround:
Manually implement an IContinuation bridge with TaskCompletionSource, detect COROUTINE_SUSPENDED, unwrap kotlin.Result$Failure, and map the Java throwable to a CLR Exception.
Request:
Please consider projecting Kotlin suspend APIs in bound AndroidX packages as Task / Task methods, or provide an official helper/interop pattern for consuming suspend APIs from .NET.
Relevant log output
Android framework version
net10.0-android
Affected platform version
.NET 10 / Xamarin.AndroidX.Health.Connect.ConnectClient 1.1.0.2
Description
The AndroidX Health Connect binding exposes Kotlin suspend APIs as methods that require passing a raw Kotlin.Coroutines.IContinuation.
Example from the binding:
This is technically callable, but difficult to use correctly from .NET. A .NET consumer has to hand-write a coroutine bridge using TaskCompletionSource, detect IntrinsicsKt.COROUTINE_SUSPENDED, implement IContinuation, unwrap Kotlin Result.Failure, map Java exceptions into CLR exceptions, and optionally bridge cancellation.
For .NET consumers, these APIs should ideally be projected as Task / Task methods.
Current required workaround:
Expected behavior:
Kotlin suspend APIs should have generated .NET-friendly overloads such as:
Actual behavior:
Only the raw continuation-based API is exposed.
Relevant official documentation:
Google’s Health Connect APIs are documented as Kotlin suspend functions. For example:
https://developer.android.com/reference/kotlin/androidx/health/connect/client/HealthConnectClient#readRecords(androidx.health.connect.client.request.ReadRecordsRequest)
Steps to Reproduce:
Workaround:
Manually implement an IContinuation bridge with TaskCompletionSource, detect COROUTINE_SUSPENDED, unwrap kotlin.Result$Failure, and map the Java throwable to a CLR Exception.
Request:
Please consider projecting Kotlin suspend APIs in bound AndroidX packages as Task / Task methods, or provide an official helper/interop pattern for consuming suspend APIs from .NET.
Relevant log output