Skip to content

Instantly share code, notes, and snippets.

@DjuroRad
Last active July 22, 2024 11:37
Show Gist options
  • Save DjuroRad/c48c02f70027eba6ee5a01f83e8708f2 to your computer and use it in GitHub Desktop.
Save DjuroRad/c48c02f70027eba6ee5a01f83e8708f2 to your computer and use it in GitHub Desktop.
Returns Online and Offline network status as Flow<ConnectionStatus>
package com.example.gistconnectivity
import android.content.Context
import android.net.ConnectivityManager
import android.net.Network
import android.net.NetworkCapabilities
import android.net.NetworkRequest
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.channels.Channel.Factory.UNLIMITED
import kotlinx.coroutines.channels.ProducerScope
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.channels.trySendBlocking
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.buffer
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.IOException
import java.net.InetAddress
enum class ConnectionStatus { Online, Offline }
// use your base url and ping it to make sure server is up
private const val HOST_NAME = "github.com"
private const val HOST_PING_TIMEOUT = 5_000
private const val INITIAL_CHECK_DELAY = 3_000L
class ConnectionStatusObserver(
context: Context
) {
private val connectivityManager =
context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
private var currentActiveNetworks = mutableSetOf<Network>()
fun getConnectionStatusFlow(): Flow<ConnectionStatus> = callbackFlow {
val networkCallback = object : ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: Network) {
super.onAvailable(network)
launch {
if (network.isOnline()) {
currentActiveNetworks.add(network)
trySendBlocking(ConnectionStatus.Online)
} else sendCurrentConnectionStatus()
}
}
override fun onCapabilitiesChanged(
network: Network, networkCapabilities: NetworkCapabilities
) {
super.onCapabilitiesChanged(network, networkCapabilities)
launch {
if (currentActiveNetworks.contains(network).not() && network.isOnline()) {
currentActiveNetworks.add(network)
trySendBlocking(ConnectionStatus.Online)
} else sendCurrentConnectionStatus()
}
}
override fun onLost(network: Network) {
super.onLost(network)
currentActiveNetworks.remove(network)
launch { sendCurrentConnectionStatus() }
}
}
connectivityManager.registerNetworkCallback(
NetworkRequest.Builder().build(),
networkCallback,
)
launch { sendIfInitiallyDisconnected() }
awaitClose {
connectivityManager.unregisterNetworkCallback(networkCallback)
}
}.distinctUntilChanged().buffer(capacity = UNLIMITED)
private suspend fun Network.isOnline(): Boolean =
isNetworkCapabilitiesValid() && isHostReachable()
private suspend fun ProducerScope<ConnectionStatus>.sendCurrentConnectionStatus() =
if (currentActiveNetworks.any { it.isOnline() }) trySendBlocking(ConnectionStatus.Online)
else trySendBlocking(ConnectionStatus.Offline)
private suspend fun isHostReachable(): Boolean = withContext(Dispatchers.IO) {
InetAddress.getByName(HOST_NAME).run {
isReachable(HOST_PING_TIMEOUT)
}
}
private fun Network.isNetworkCapabilitiesValid(): Boolean =
connectivityManager.getNetworkCapabilities(this)?.let {
with(it) {
hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) && hasCapability(
NetworkCapabilities.NET_CAPABILITY_VALIDATED
)
}
} ?: false
private suspend fun ProducerScope<ConnectionStatus>.sendIfInitiallyDisconnected() {
delay(INITIAL_CHECK_DELAY)
try {
if (currentActiveNetworks.any { it.isOnline() }.not() || isHostReachable().not())
trySendBlocking(ConnectionStatus.Offline)
} catch (ioException: IOException) {
trySendBlocking(ConnectionStatus.Offline)
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment