Monitor Internet Connectivity in Jetpack Compose! 📶 ✨

Romman SabbirRomman Sabbir
7 min read

Hello, and welcome to this article where we will delve into a Jetpack Compose function that is not only lifecycle-aware but also adept at checking internet connectivity using the most up-to-date APIs available. This function is designed to utilize the ConnectivityManager.NetworkCallback class, which is a powerful tool for monitoring changes in internet status. By implementing this callback, the function can respond to various network events, such as when the device connects to or disconnects from the internet, or when the type of network connection changes, like switching from Wi-Fi to mobile data.

The code we will discuss is thoroughly documented, providing clear and comprehensive explanations for each part of the implementation. This detailed documentation is intended to guide you through the process, making it easier to understand the underlying mechanisms of internet connectivity monitoring. Additionally, the code includes extensive logging, which serves as a valuable resource for debugging. These logs will help you trace the flow of network status changes and identify any issues that may arise during the execution of the function.

By the end of this article, you will have a solid understanding of how to create a robust and efficient Jetpack Compose function that can reliably monitor internet connectivity, ensuring your application remains responsive and aware of network changes.

NOTE:

We aim to identify situations where a device is connected to a network, yet the internet is either inaccessible or not working properly. This can occur in cases where the network indicates a connected status, but users are unable to browse the web or access online services. Such scenarios might happen due to various network problems, including issues with the network infrastructure, the presence of captive portals requiring user authentication, or other types of restrictions imposed by the network provider. These issues can be frustrating for users, as they appear to have a connection but cannot perform any online activities. By detecting these conditions, we can provide better feedback to users and potentially guide them through resolving the problem.

To address this, we need to:

  1. Check if the network has internet capability using NetworkCapabilities.NET_CAPABILITY_INTERNET.

  2. Use NetworkCapabilities.NET_CAPABILITY_VALIDATED to ensure that the internet connection is usable. This capability is a good indicator that the network is functional, as the system performs a network validation check when this capability is present.

Usage Example

Incorporate this composable function into our app to actively monitor and receive updates on internet connectivity. This approach takes into account not only the presence of internet access but also the validation of the network connection. By doing so, we ensure that our application can detect when a device is connected to a network that truly provides internet access, as opposed to merely showing a connected status without actual usability. This functionality is crucial for enhancing user experience, as it allows us to inform users about the real status of their internet connection. Furthermore, it can help guide them in troubleshooting connectivity issues, ensuring they can access online services smoothly. By integrating this composable, we can maintain a robust and reliable connection status within our app, ultimately improving user satisfaction and reducing frustration caused by misleading network indicators.

@Composable
fun MyAppScreen() {
    MonitorNetworkStatus { isNetworkFunctional ->
        if (isNetworkFunctional) {
            Log.d("MyAppScreen", "Internet is functional and validated.")
            // Handle the functional network state
        } else {
            Log.d("MyAppScreen", "Internet is not functional or validated.")
            // Handle the non-functional network state
        }
    }

    // Other UI content
}

This updated implementation enhances the process of checking for a "functional" internet connection by incorporating a validation mechanism for the network. This approach offers a more dependable method for identifying situations where a device appears to be connected to a network but is unable to access the internet. Such scenarios can occur due to issues like a captive portal, which requires additional login steps, or other network-related problems that prevent internet access despite a connected status. By validating the network connection, this implementation ensures that our application can accurately determine the usability of the internet connection, providing a more robust solution for detecting and handling connectivity issues. This improvement is crucial for delivering a seamless user experience, as it allows the app to inform users about the true status of their internet connectivity and guide them in resolving any potential issues.

Code for Monitor Internet Connectivity with Validation

import android.content.Context
import android.net.ConnectivityManager
import android.net.Network
import android.net.NetworkCapabilities
import android.net.NetworkRequest
import android.util.Log
import androidx.compose.runtime.*
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.LifecycleOwner

/**
 * Checks if the current network is connected to the internet and validated.
 * Validation indicates the network is functional (e.g., it can be used for browsing).
 *
 * @return True if the network is connected, has internet capability, and is validated; false otherwise.
 */
fun Context.isInternetFunctional(): Boolean {
    val connectivityManager = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
    val network = connectivityManager.activeNetwork
    val networkCapabilities = connectivityManager.getNetworkCapabilities(network)
    return networkCapabilities?.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) == true &&
           networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
}

/**
 * A Composable function that monitors internet connectivity status in a lifecycle-aware manner.
 * It uses ConnectivityManager.NetworkCallback to listen for network changes, ensuring the network is validated.
 *
 * @param lifecycleOwner The lifecycle owner to observe, typically the hosting activity or fragment.
 * @param onNetworkStatusChanged A callback function that provides the current network status (true if the internet is functional).
 */
@Composable
fun MonitorNetworkStatus(
    lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
    onNetworkStatusChanged: (Boolean) -> Unit
) {
    val context = LocalContext.current
    var isNetworkFunctional by remember { mutableStateOf(context.isInternetFunctional()) }
    val connectivityManager = remember {
        context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
    }

    DisposableEffect(lifecycleOwner) {
        // Lifecycle observer to handle lifecycle events
        val observer = LifecycleEventObserver { _, event ->
            when (event) {
                Lifecycle.Event.ON_RESUME -> {
                    Log.d("MonitorNetworkStatus", "ON_RESUME event triggered.")
                    try {
                        // Update the network status on resume
                        val currentNetworkStatus = context.isInternetFunctional()
                        if (isNetworkFunctional != currentNetworkStatus) {
                            isNetworkFunctional = currentNetworkStatus
                            Log.d("MonitorNetworkStatus", "Network validation state: $isNetworkFunctional")
                            onNetworkStatusChanged(isNetworkFunctional)
                        }
                    } catch (e: Exception) {
                        Log.e("MonitorNetworkStatus", "Exception checking network status: ${e.localizedMessage}", e)
                    }
                }
                else -> {
                    Log.d("MonitorNetworkStatus", "Unhandled lifecycle event: $event")
                }
            }
        }

        // NetworkCallback to listen for network changes
        val networkCallback = object : ConnectivityManager.NetworkCallback() {
            override fun onAvailable(network: Network) {
                val networkCapabilities = connectivityManager.getNetworkCapabilities(network)
                val isFunctional = networkCapabilities?.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) == true &&
                                   networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
                if (isFunctional != isNetworkFunctional) {
                    isNetworkFunctional = isFunctional
                    Log.d("MonitorNetworkStatus", "Network validation state changed: $isNetworkFunctional")
                    onNetworkStatusChanged(isNetworkFunctional)
                }
            }

            override fun onLost(network: Network) {
                if (isNetworkFunctional) {
                    isNetworkFunctional = false
                    Log.d("MonitorNetworkStatus", "Network connection lost.")
                    onNetworkStatusChanged(false)
                }
            }

            override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {
                val isFunctional = networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) &&
                                   networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
                if (isFunctional != isNetworkFunctional) {
                    isNetworkFunctional = isFunctional
                    Log.d("MonitorNetworkStatus", "Network capabilities changed: $isNetworkFunctional")
                    onNetworkStatusChanged(isNetworkFunctional)
                }
            }
        }

        // Register the network callback to listen for internet connectivity changes
        val networkRequest = NetworkRequest.Builder()
            .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
            .build()
        connectivityManager.registerNetworkCallback(networkRequest, networkCallback)

        // Add the observer to the lifecycle
        lifecycleOwner.lifecycle.addObserver(observer)
        Log.d("MonitorNetworkStatus", "Observer added to lifecycle.")

        // Cleanup on disposal
        onDispose {
            lifecycleOwner.lifecycle.removeObserver(observer)
            connectivityManager.unregisterNetworkCallback(networkCallback)
            Log.d("MonitorNetworkStatus", "Observer and network callback removed.")
        }
    }
}

Detailed Explanation

  1. isInternetFunctional Function:

    • This function plays a crucial role in determining the current state of the network connection. It checks whether the network is both connected and validated. The validation process, indicated by NET_CAPABILITY_VALIDATED, ensures that the system has verified the internet connection is indeed usable. This means that the connection is not just present but is also capable of supporting activities like web browsing or data fetching.
  2. Network Callback Enhancements:

    • onAvailable: This callback method is triggered when a network becomes available. It performs a thorough check to confirm the network's functionality by verifying the presence of both NET_CAPABILITY_INTERNET and NET_CAPABILITY_VALIDATED capabilities. If there is a change in the network's status, it updates the internal state to reflect this change, ensuring that the application is always aware of the network's current condition.

    • onCapabilitiesChanged: This method is responsible for continuously monitoring any alterations in the network's capabilities. By doing so, it can detect any modifications in the network's ability to provide internet functionality, allowing the application to respond appropriately to these changes.

  3. Lifecycle Awareness:

    • The network status is updated during the ON_RESUME lifecycle event. This ensures that every time the app or composable component becomes active, the network status is checked and updated. This approach guarantees that the application always operates with the most current network information, providing a seamless user experience.
  4. Logging:

    • Logging is implemented to offer comprehensive insights into the network's status and capability changes. These logs are invaluable for debugging purposes, as they provide a detailed record of network events and transitions. By examining these logs, developers can quickly identify and resolve issues related to network connectivity, ensuring the application remains reliable and efficient.

🔗 GIST : https://ylnk.cc/@gI3i


That’s it for today, Happy coding…

0
Subscribe to my newsletter

Read articles from Romman Sabbir directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Romman Sabbir
Romman Sabbir

Senior Android Engineer from Bangladesh. Love to contribute in Open-Source. Indie Music Producer.