I changed the question title since Kiskae provided a solution to the broader problem which makes the question about wrapping a callback API needless for the particular problem. The former question title was:
How to wrap asynchronous callback into suspend function using Coroutines?
I am trying to wrap the ConnectivityManager.NetworkCallback
API (>= SDK level 21) of the Android framework into a suspend function to facilitate a synchronous API:
private suspend fun ConnectivityManager.isNetworkAvailable(
vararg transportType: /* android.net.NetworkCapabilities.Transport */ Int)
: Boolean {
val isAvailable = suspendCancellableCoroutine<Boolean> { continuation ->
val builder = NetworkRequest.Builder()
transportType.forEach {
builder.addCapability(it)
}
val networkRequest = builder.build()
val networkCallback = object : ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: Network) {
super.onAvailable(network)
Log.d(javaClass.simpleName, "[${Thread.currentThread().name}] onAvailable")
continuation.resume(true)
unregisterNetworkCallback(this)
}
override fun onLost(network: Network) {
super.onLost(network)
Log.d(javaClass.simpleName, "[${Thread.currentThread().name}] onLost")
continuation.resume(false)
unregisterNetworkCallback(this)
}
}
Log.d(javaClass.simpleName, "[${Thread.currentThread().name}] registerNetworkCallback BEFORE")
registerNetworkCallback(networkRequest, networkCallback)
Log.d(javaClass.simpleName, "[${Thread.currentThread().name}] registerNetworkCallback AFTER")
}
Log.d(javaClass.simpleName, "[${Thread.currentThread().name}] return isAvailable BEFORE")
return isAvailable
}
When I call the isNetworkAvailable(NetworkCapabilities.TRANSPORT_WIFI)
then the following is output:
[DefaultDispatcher-worker-1] registerNetworkCallback BEFORE
[DefaultDispatcher-worker-1] registerNetworkCallback AFTER
The onAvailable
and onLost
are never called, though.
References
- This is inspired by the codelabs/building-kotlin-extensions-library/#4. See
FusedLocationProviderClient.awaitLastLocation()
for a sample application.
Related
Turns out the relevant API for what you want to do isn't async, so coroutines should not be required:
private fun ConnectivityManager.isNetworkAvailable(
vararg transportType: /* android.net.NetworkCapabilities.Transport */ Int)
: Boolean {
val network = getActiveNetwork()
val caps = getNetworkCapabilities(network)
return caps != null && transportType.all(caps::hasTransport)
}
This only requires Manifest.permission.ACCESS_NETWORK_STATE
.
If you want to actively search for a network that has the capabilities required, you will need to use the requestNetwork
API. (and permission Manifest.permission.CHANGE_NETWORK_STATE
)