I used this tutorial to implement internet connection observing in my fully Compose app
Here is my code:
sealed class ConnectionState {
data object Available : ConnectionState()
data object Unavailable : ConnectionState()
}
val Context.isConnected: ConnectionState
get() {
val connectivityManager = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val network = connectivityManager.activeNetwork ?: return ConnectionState.Unavailable
val activeNetwork = connectivityManager.getNetworkCapabilities(network) ?: return ConnectionState.Unavailable
return when {
activeNetwork.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> ConnectionState.Available
activeNetwork.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> ConnectionState.Available
else -> ConnectionState.Unavailable
}
}
/**
* observe availability or unavailability of Internet connection
*/
fun Context.observeConnectivityAsFlow() = callbackFlow {
Log.d("Bazinga", "callbackFlow")
val connectivityManager = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val callback = NetworkCallback { connectionState -> trySend(connectionState) }
val networkRequest = NetworkRequest.Builder()
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.build()
val registered = mutableListOf<NetworkCallback>()
Log.d("Bazinga", "register - $callback")
registered.add(callback)
Log.d("Bazinga", "registered: $registered")
connectivityManager.registerNetworkCallback(networkRequest, callback)
trySend(isConnected)
awaitClose {
Log.d("Bazinga", "unregister - $callback")
registered.remove(callback)
connectivityManager.unregisterNetworkCallback(callback)
}
}
fun NetworkCallback(callback: (ConnectionState) -> Unit): ConnectivityManager.NetworkCallback {
return object : ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: Network) {
callback(ConnectionState.Available)
}
override fun onLost(network: Network) {
callback(ConnectionState.Unavailable)
}
}
}
@ExperimentalCoroutinesApi
@Composable
fun connectivityState(): State<ConnectionState> {
val context = LocalContext.current
return produceState(initialValue = context.isConnected) {
Log.d("Bazinga", "produceState")
context.observeConnectivityAsFlow().collect { value = it }
}
}
@Composable
fun ObserveConnection(onConnectionResumed: () -> Unit) {
val connection by connectivityState()
LaunchedEffect(key1 = Unit) {
snapshotFlow { connection }
.collect {
if (it == ConnectionState.Available) onConnectionResumed()
}
}
}
On some screens where I need to do something when internet connection was restored I use this code:
ObserveConnection(viewModel::onConnectionResumed)
And the most strange part as for me, when internet connected here is no any crashes. But in other hand, when with these steps:
ObserveConnection(viewModel::onConnectionResumed)
)As you see, I was added logs to see all register and unregister events, here is the logcat output in this case:
produceState
callbackFlow
register - com.mypackagename.utils.network.NetworkUtilsKt$NetworkCallback$1@e8f1a28
registered: [com.mypackagename.utils.network.NetworkUtilsKt$NetworkCallback$1@e8f1a28]
unregister - com.mypackagename.utils.network.NetworkUtilsKt$NetworkCallback$1@e8f1a28
produceState
callbackFlow
register - com.mypackagename.utils.network.NetworkUtilsKt$NetworkCallback$1@232649d
registered: [com.mypackagename.utils.network.NetworkUtilsKt$NetworkCallback$1@232649d]
unregister - com.mypackagename.utils.network.NetworkUtilsKt$NetworkCallback$1@232649d
produceState
callbackFlow
register - com.mypackagename.utils.network.NetworkUtilsKt$NetworkCallback$1@3636fc
registered: [com.mypackagename.utils.network.NetworkUtilsKt$NetworkCallback$1@3636fc]
Here is the error stacktrace:
FATAL EXCEPTION: main
Process: com.mypackagename, PID: 12545
android.net.ConnectivityManager$TooManyRequestsException
at android.net.ConnectivityManager.convertServiceException(ConnectivityManager.java:4032)
at android.net.ConnectivityManager.sendRequestForNetwork(ConnectivityManager.java:4221)
at android.net.ConnectivityManager.sendRequestForNetwork(ConnectivityManager.java:4228)
at android.net.ConnectivityManager.registerNetworkCallback(ConnectivityManager.java:4610)
at android.net.ConnectivityManager.registerNetworkCallback(ConnectivityManager.java:4580)
at com.mypackagename.utils.network.NetworkUtilsKt$observeConnectivityAsFlow$1.invokeSuspend(NetworkUtils.kt:54)
at com.mypackagename.utils.network.NetworkUtilsKt$observeConnectivityAsFlow$1.invoke(Unknown Source:8)
at com.mypackagename.utils.network.NetworkUtilsKt$observeConnectivityAsFlow$1.invoke(Unknown Source:4)
at kotlinx.coroutines.flow.ChannelFlowBuilder.collectTo$suspendImpl(Builders.kt:320)
at kotlinx.coroutines.flow.ChannelFlowBuilder.collectTo(Unknown Source:0)
at kotlinx.coroutines.flow.CallbackFlowBuilder.collectTo(Builders.kt:334)
at kotlinx.coroutines.flow.internal.ChannelFlow$collectToFun$1.invokeSuspend(ChannelFlow.kt:60)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:108)
at androidx.compose.ui.platform.AndroidUiDispatcher.performTrampolineDispatch(AndroidUiDispatcher.android.kt:81)
at androidx.compose.ui.platform.AndroidUiDispatcher.access$performTrampolineDispatch(AndroidUiDispatcher.android.kt:41)
at androidx.compose.ui.platform.AndroidUiDispatcher$dispatchCallback$1.run(AndroidUiDispatcher.android.kt:57)
at android.os.Handler.handleCallback(Handler.java:942)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loopOnce(Looper.java:201)
at android.os.Looper.loop(Looper.java:288)
at android.app.ActivityThread.main(ActivityThread.java:7872)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:936)
Suppressed: kotlinx.coroutines.internal.DiagnosticCoroutineContextException: [androidx.compose.ui.platform.MotionDurationScaleImpl@8fdf886, androidx.compose.runtime.BroadcastFrameClock@37fd547, StandaloneCoroutine{Cancelling}@c53a174, AndroidUiDispatcher@e85469d]
So, here is my solution
Firstly I created in my AppState this property:
var connectionResumed by mutableStateOf(false)
private set
fun setInternetConnectionResumed(resumed:Boolean){
connectionResumed = resumed
}
The AppState is class to store all common things like alert state, snackbar state etc
Also I moved handling of connectivity changes handling to single place, in my case it's MyApp
Composable function that observes appState:
val connection by connectivityState()
LaunchedEffect(key1 = connection) {
if (connection == ConnectionState.Available) {
state.setInternetConnectionResumed(true)
}
}
Also I was changed my ObserveConnection
function:
@Composable
fun ObserveConnection(onConnectionResumed: () -> Unit, appState: AppState) {
LaunchedEffect(key1 = appState.connectionResumed) {
onConnectionResumed()
appState.setInternetConnectionResumed(false)
}
}
And everywhere where I need to handle connection events now I place this code:
ObserveConnection(viewModel::onConnectionResumed, appState)
So now there is just one subscription to the connectivityState() and the crash doesn't happen again