Last active
July 22, 2024 11:37
-
-
Save DjuroRad/c48c02f70027eba6ee5a01f83e8708f2 to your computer and use it in GitHub Desktop.
Returns Online and Offline network status as Flow<ConnectionStatus>
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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