Blog Infos
Author
Published
Topics
Published

How to observe Network connectivity status using Kotlin Flows and show it inside Compose UI?

Monitoring Network connectivity status is a very common use-case, either we want to display current network status on the UI or to show a retry mechanism if the device is offline.

This story will show code examples about how to monitor and convert Network connectivity callbacks into callbackFlow and observe it into the UI layer to show updates inside Compose UI and in the end it will provide helpful Takeaways .

Prerequisites

This story requires basic understanding of following components

  • Kotlin Flows and callbackFlow
  • Dependency Injection ( Dagger or Dagger Hilt)
  • Basic understanding of Jetpack Compose
What do we want to achieve?

We want to create a Network Connectivity Service to achieve followings

  • Monitor current Network Statuson device to know if device is connected to the Network or not.
  • Observe changes to the Network status in real time.
  • Use Jetpack Compose API to show offline status inside the Compose UI.

Let’s get to Network Connectivity Service.

NetworkStatus

As we want to read Network Status from the service. In order to expose Network Status from the service we will use a sealed class holding that status information. See below the NetworkStatus sealed class.

sealed class NetworkStatus {
    object Unknown: NetworkStatus()
    object Connected: NetworkStatus()
    object Disconnected: NetworkStatus()
}
Network Connectivity Service

Let’s see the code first.

interface NetworkConnectivityService {
val networkStatus: Flow<NetworkStatus>
}
class NetworkConnectivityServiceImpl @Inject constructor (
context: Context
): NetworkConnectivityService {
private val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
override val networkStatus: Flow<NetworkStatus> = callbackFlow {
val connectivityCallback = object : NetworkCallback() {
override fun onAvailable(network: Network) {
trySend(NetworkStatus.Connected)
}
override fun onUnavailable() {
trySend(NetworkStatus.Disconnected)
}
override fun onLost(network: Network) {
trySend(NetworkStatus.Disconnected)
}
}
val request = NetworkRequest.Builder()
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
.build()
connectivityManager.registerNetworkCallback(request, connectivityCallback)
awaitClose {
connectivityManager.unregisterNetworkCallback(connectivityCallback)
}
}
.distinctUntilChanged()
.flowOn(Dispatchers.IO)
}

NetworkConnectivityServiceImpl is implementing an interface which is exposing networkStatus as Flow<NetworkStatus> .

The service is using callbackFlow , callbackFlow is an ideal choice here. Whenever we want to convert any API callbacks into Kotlin Flow callbackFlow is the answer. callbackFlow ensures that lambda remains active to be able to send data later-on during callbacks. It also provides awaitClose block to unregister listeners when callbackFlow closes.

In lambda connectivityManager is registering to the network status callbacks. In each callback method it sends a NetworkStatus using trySend . As mentioned before callbackFlow provides awaitClose block to unregister for the network changes. awaitClose will be called when callbackFlow is closed so we need to unregister for listeners.

flowOn(Dispatchers.IO) is to make sure that calling networkStatus is main safe because we will be calling it from the UI layer inside ViewModel.

distinctUntilChanged() ensures that it sends new value only when Network Status changes to avoid sending unnecessary updates. In real cases that will usually be the case when you turn off the Airplane mode then you will get the callback onAvailable twice, one for wifi and the other for cellular.

What to know more about callbackFlow? I have written a detailed story on callbackFlow , taking example of Firebase RealtimeDatabase callbacks and converting them into callbackFlow. If interested you can read from the link below.

Using Network Connectivity Service in ViewModel.

In order to provide current NetworkStatus for UI, we will create a networkStatus property in ViewModel from where Compose UI will collect it.

Exposing a property in ViewModel will look like below.

val networkStatus: StateFlow<NetworkStatus> = networkConnectivityService.networkStatus.stateIn(
initialValue = NetworkStatus.Unknown,
scope = viewModelScope,
started = WhileSubscribed(5000)
)
  • Flow.StateIn operator is used to convert callbackFlow into StateFlow .
  • NetworkConnectivityService will be injected as dependency inside the ViewModel constructor, I am using Hilt, It’s up to you whatever Dependency Injector you want to use.
  • initialValue inside Flow.StateIn is set Unknown because in the beginning when UI loads we will not know NetworkStatus and we don’t want to show any notification on UI setting default value to any of Connected or Disconnected.
  • Providing viewModelScope will bind Flow to the life-cycle of viewModelScope.
  • WhileSubscribed is used to cancel the upstream automatically when there are no collectors collecting the flow. WhileSubscribed(5000) will wait for 5 more minutes after the last collector before closing the upstream, It will avoid restarting the whole upstream flow unnecessary specially during configuration changes.
Collecting Network Status inside Compose UI

On the UI we will show notification when the device is offline using the snackbar. LaunchedEffect is the best choice here, I have written a very detailed blog post about LaunchedEffect vs rememberCoroutineScope exploring and explaining When and How to use both APIs. I recommend reading it, link is below.

Job Offers

Job Offers

There are currently no vacancies.

OUR VIDEO RECOMMENDATION

,

Jetpack Compose: Drawing without pain and recomposition

This is a talk on recomposition in Jetpack Compose and the myths of too many calls it is followed by. I’ll briefly explain the reasons behind recompositions and why they are not as problematic as…
Watch Video

Jetpack Compose: Drawing without pain and recomposition

Vitalii Markus
Android Engineer
Flo Health Inc.

Jetpack Compose: Drawing without pain and recomposition

Vitalii Markus
Android Engineer
Flo Health Inc.

Jetpack Compose: Drawing without pain and recomposition

Vitalii Markus
Android Engineer
Flo Health Inc.

Jobs

Collecting networkStatus inside Compose UI looks like below.

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun NetworkConnectivityScreen(
viewModel: NetworkConnectivityViewModel = viewModel(),
snackbarHostState: SnackbarHostState = SnackbarHostState()
) {
val networkStatus = viewModel.networkStatus.collectAsStateWithLifecycle()
if (networkStatus.value == NetworkStatus.Disconnected) {
LaunchedEffect(networkStatus) {
snackbarHostState.showSnackbar("you are offline")
}
}
Scaffold(
snackbarHost = { SnackbarHost(hostState = snackbarHostState) }
) { paddingValues ->
Box(
modifier = Modifier.fillMaxSize().padding(paddingValues),
contentAlignment = Alignment.Center
) {
Text(text = "Connectivity Service")
}
}
}
  • collectAsStateWithLifecycle is used to collect for networkStatus in Composable, This is life-cycle-aware API and is a recommended way to collect Flow inside Compose.
  • LaunchedEffect is used to execute a suspend function snackbarHostState.showSnackbar("message") only when networkStatus is in Disconnected state. LaunchedEffect launches coroutine within the scope of the composable where it is being used and cancels it when it leaves the composition so we don’t need to worry about the life-cycle of coroutine being automatically managed.
Takeaways
  • callbackFlow is tailor built for converting any API callbacks into Kotlin Flow.
  • Flow.StateIn converts cold flow into hot flow, in this example it converts callbackFlow into StateFlow .
  • collect Flow in Jetpack Compose in a life-cycle-aware manner via API collectAsStateWithLifecycle
  • WhileSubscribed(5000) waits for 5 minutes before restarting upstream flow if flow does not have any observer, helpful in configuration changes or heavy upstream flows and also in UI related updates.
  • LaunchedEffect effect API is the recommended way to execute suspend functions as an effect of anything happening out of the scope of composable and changes of Network connectivity is an example of those side-effects happening out of the scope of composable.
Sources

That’s it for now! Hope it was helpful… Looking forward to any questions/suggestions in the comments.

Remember to follow and 👏 if you liked it 🙂

— — — — — — — — —

GitHub | LinkedIn | Twitter

This article was previously published on proandroiddev.com

YOU MAY BE INTERESTED IN

YOU MAY BE INTERESTED IN

blog
It’s one of the common UX across apps to provide swipe to dismiss so…
READ MORE
blog
In this part of our series on introducing Jetpack Compose into an existing project,…
READ MORE
blog
In the world of Jetpack Compose, where designing reusable and customizable UI components is…
READ MORE
blog

How to animate BottomSheet content using Jetpack Compose

Early this year I started a new pet project for listening to random radio…
READ MORE
Menu