-
Notifications
You must be signed in to change notification settings - Fork 4.1k
Description
Is there an existing issue for this?
- I have searched the existing issues
Describe the problem
1. Add NET_CAPABILITY_VALIDATED
Add a check for NET_CAPABILITY_VALIDATED for a more reliable internet connection check on newer Android versions. A network can be "connected" but not actually have internet access (e.g., captive portals).
Reasoning:
NET_CAPABILITY_INTERNETindicates that the network is intended to provide internet access.NET_CAPABILITY_VALIDATEDconfirms that the device has successfully connected to the internet through this network. Using both gives a more accurate signal of a usable internet connection.
2. Add distinctUntilChanged() to the Flow
The flow might emit the same value consecutively (e.g., multiple networks becoming available one after another, all causing true to be sent). While conflate() helps by dropping intermediate values, distinctUntilChanged() will prevent collectors from being notified if the connectivity status hasn't actually changed.
Reasoning:
This is a small optimization that ensures downstream collectors only react when the online status actually flips from true to false or vice-versa, reducing unnecessary recompositions or data processing.
3. Add Transport Type Check
Not all validated networks are the ones we want for general internet access. A device might have a "validated" peer-to-peer Wi-Fi network or another special-purpose network that doesn't provide the broad internet access your app expects. To improve this, we can refine our isCurrentlyConnected function to also check for common internet-providing transport types.
Reasoning:
Explicitly checking for TRANSPORT_WIFI, TRANSPORT_CELLULAR, or TRANSPORT_ETHERNET ensures we are connected to a standard internet uplink, filtering out misleading signals from other specialized networks. This makes our connectivity check more accurate
Describe the solution
1. Add NET_CAPABILITY_VALIDATED
Suggestion:
Update the isCurrentlyConnected function to also check for NET_CAPABILITY_VALIDATED.
@SuppressLint("ObsoleteSdkInt")
@Suppress("DEPRECATION")
private fun ConnectivityManager.isCurrentlyConnected(): Boolean {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
val network = activeNetwork ?: return false
val capabilities = getNetworkCapabilities(network) ?: return false
return capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
&& capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED) // Add this line
} else {
// For older versions
return activeNetworkInfo?.isConnected ?: false
}
}And similarly, update the NetworkRequest.Builder:
// Inside the callbackFlow block
val request = Builder()
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED) // Add this line
.build()
connectivityManager.registerNetworkCallback(request, callback)2. Add distinctUntilChanged() to the Flow
Suggestion:
Chain distinctUntilChanged() to the flow.
// In ConnectivityManagerNetworkMonitor.kt
import kotlinx.coroutines.flow.distinctUntilChanged
// ... other imports
override val isOnline: Flow<Boolean> = callbackFlow {
// ... same implementation
}
.flowOn(ioDispatcher)
.conflate()
.distinctUntilChanged() // Add this line3. Add Transport Type Check
Suggestion:
@SuppressLint("ObsoleteSdkInt")
@Suppress("DEPRECATION")
private fun ConnectivityManager.isCurrentlyConnected(): Boolean {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
val network = activeNetwork ?: return false
val capabilities = getNetworkCapabilities(network) ?: return false
return capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
&& capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
// Add this check for transport types
&& (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) ||
capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) ||
capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET))
} else {
// For older versions
return activeNetworkInfo?.isConnected ?: false
}
}Final Code after suggestions:
// File: nowinandroid/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/util/ConnectivityManagerNetworkMonitor.kt
// Github file link: https://github.com/android/nowinandroid/blob/705fd9068bb2225cb351a0c10b2154e0e56f40de/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/util/ConnectivityManagerNetworkMonitor.kt
internal class ConnectivityManagerNetworkMonitor @Inject constructor(
@ApplicationContext private val context: Context,
@Dispatcher(AppDispatchers.IO) private val ioDispatcher: CoroutineDispatcher,
) : NetworkMonitor {
override val isOnline: Flow<Boolean> = callbackFlow {
trace("NetworkMonitor.callbackFlow") {
val connectivityManager = context.getSystemService<ConnectivityManager>()
if (connectivityManager == null) {
channel.trySend(false)
channel.close()
return@callbackFlow
}
/**
* The callback's methods are invoked on changes to *any* network matching the [NetworkRequest],
* not just the active network. So we can simply track the presence (or absence) of such [Network].
*/
val callback = object : NetworkCallback() {
private val networks = mutableSetOf<Network>()
override fun onAvailable(network: Network) {
networks += network
channel.trySend(true)
}
override fun onLost(network: Network) {
networks -= network
channel.trySend(networks.isNotEmpty())
}
}
trace("NetworkMonitor.registerNetworkCallback") {
val request = Builder()
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
.build()
connectivityManager.registerNetworkCallback(request, callback)
}
/**
* Sends the latest connectivity status to the underlying channel.
*/
channel.trySend(connectivityManager.isCurrentlyConnected())
awaitClose {
connectivityManager.unregisterNetworkCallback(callback)
}
}
}
.flowOn(ioDispatcher)
.conflate()
.distinctUntilChanged()
@SuppressLint("ObsoleteSdkInt")
@Suppress("DEPRECATION")
private fun ConnectivityManager.isCurrentlyConnected(): Boolean {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
val network = activeNetwork ?: return false
val capabilities = getNetworkCapabilities(network) ?: return false
return capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
&& capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
&& (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) ||
capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) ||
capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET))
} else {
// For older versions
return activeNetworkInfo?.isConnected ?: false
}
}
}Additional context
Additional context
- For Add
NET_CAPABILITY_VALIDATED:
-
https://developer.android.com/develop/connectivity/network-ops/reading-network-state
-
https://developer.android.com/reference/android/net/NetworkCapabilities#NET_CAPABILITY_VALIDATED
- For Add Transport Type Check:
Code of Conduct
- I agree to follow this project's Code of Conduct