Handle Network Drops in Android Like a Pro: Build a Connectivity Observer with Flow (MVVM + Jetpack Compose )

 


Seamless network awareness in your app, the MVVM way.


Why This Matters

Modern mobile apps live and die by network connectivity.

Yet, Android’s ConnectivityManager.NetworkCallback often misses reconnection events or fails silently on some devices. The result? Your app gets stuck “offline” even when the internet is back.

In this guide, we’ll show how to build a reliable, MVVM-aligned connectivity observer that’s:

  • Lifecycle-safe with StateFlow

  • Jetpack Compose-compatible

  • Reactive using callbackFlow + polling

  • Completely decoupled via ViewModel


Architecture Breakdown

View (Compose UI) 
  ↕
ViewModel (StateFlow)
  ↕
ConnectivityObserver (Flow of Status)

1. Define the ConnectivityObserver Interface (Model Layer)

interface ConnectivityObserver {
    enum class Status {
        Available, Unavailable, Losing, Lost
    }
    fun observe(): Flow<Status>
}

2. Implement the NetworkConnectivityObserver (Model Layer)

class NetworkConnectivityObserver(private val context: Context) : ConnectivityObserver {

    private val connectivityManager =
        context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager

    override fun observe(): Flow<ConnectivityObserver.Status> = callbackFlow {
        val callback = object : ConnectivityManager.NetworkCallback() {
            override fun onAvailable(network: Network) {
                trySend(ConnectivityObserver.Status.Available)
            }

            override fun onLost(network: Network) {
                trySend(ConnectivityObserver.Status.Lost)
            }

            override fun onLosing(network: Network, maxMsToLive: Int) {
                trySend(ConnectivityObserver.Status.Losing)
            }

            override fun onUnavailable() {
                trySend(ConnectivityObserver.Status.Unavailable)
            }
        }

        val request = NetworkRequest.Builder().build()
        connectivityManager.registerNetworkCallback(request, callback)

        // Periodic check to recover missed state
        val scope = CoroutineScope(Dispatchers.IO)
        scope.launch {
            var lastStatus: ConnectivityObserver.Status? = null
            while (isActive) {
                val status = currentStatus()
                if (status != lastStatus) {
                    trySend(status)
                    lastStatus = status
                }
                delay(3000)
            }
        }

        awaitClose {
            connectivityManager.unregisterNetworkCallback(callback)
        }
    }.distinctUntilChanged()

    private fun currentStatus(): ConnectivityObserver.Status {
        val info = connectivityManager.activeNetworkInfo
        return if (info != null && info.isConnected) {
            ConnectivityObserver.Status.Available
        } else {
            ConnectivityObserver.Status.Unavailable
        }
    }
}

3. Create the ViewModel (ViewModel Layer)

class NetworkViewModel(
    private val connectivityObserver: ConnectivityObserver
) : ViewModel() {

    val networkStatus: StateFlow<ConnectivityObserver.Status> =
        connectivityObserver.observe()
            .stateIn(viewModelScope, SharingStarted.Eagerly, ConnectivityObserver.Status.Available)
}

4. Hook it to Compose UI (View Layer)

@Composable
fun NetworkStatusScreen(viewModel: NetworkViewModel) {
    val networkStatus by viewModel.networkStatus.collectAsState()

    LaunchedEffect(networkStatus) {
        when (networkStatus) {
            ConnectivityObserver.Status.Available -> showSnackbar("You are online")
            ConnectivityObserver.Status.Lost -> showSnackbar("You are offline")
            ConnectivityObserver.Status.Losing -> showSnackbar("Connection is weak")
            ConnectivityObserver.Status.Unavailable -> showSnackbar("No connection available")
        }
    }

    // Your UI here...
}

5. Provide the ViewModel Without Hilt (Pure MVVM)

class NetworkViewModelFactory(
    private val observer: ConnectivityObserver
) : ViewModelProvider.Factory {
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        return NetworkViewModel(observer) as T
    }
}
@Composable
fun AppScreen() {
    val context = LocalContext.current
    val observer = remember { NetworkConnectivityObserver(context) }

    val viewModel: NetworkViewModel = viewModel(
        factory = NetworkViewModelFactory(observer)
    )

    NetworkStatusScreen(viewModel)
}

Why This Approach is Better (The MVVM Way)

Traditional MVVM Connectivity Observer
Imperative checks Reactive Flow
Tight coupling to UI Decoupled via ViewModel
Missed reconnect events Periodic polling to fill the gaps
Manual lifecycle control Handled by ViewModel + Compose
Difficult to test Easy to unit test ViewModel behavior

Final Words

By building a connectivity observer with Flow + MVVM + Compose, you future-proof your app’s network awareness. Your ViewModel becomes the single source of truth, and your UI stays reactive — always in sync with the network state.

Your users will never get stuck in “offline limbo” again.


Want More?

  • Add retry queues for failed actions

  • Persist last known status

  • Show persistent offline banners

  • Observe airplane mode changes too



 

Comments

Popular posts from this blog

Jetpack Compose based Android interview and questions

Kotlin Some important unsorted interview questions and answers

Null safety based Kotlin interview questions and answers