Learn how to prevent unnecessary API calls in Android by detecting offline state centrally using Kotlin Flows and interceptors — no repetitive checks required.

image source: istockphoto.com
Introduction
As Android developers, we’ve all been there — adding if (isNetworkAvailable())
before almost every API call to avoid crashes or wasted calls when the user is offline. But what if your app has 50, 100, or more repositories? Do you really want to repeat that logic everywhere?
Spoiler: You don’t have to. In this article, we’ll walk through a scalable and clean architecture pattern that solves this problem once and for all.
🚫 The Wrong Way: Checking Network Before Every API Call
if (isNetworkAvailable()) { api.getData() } else { showToast("No internet connection") }
Imagine doing that in every single repository or use case. It violates the DRY (Don’t Repeat Yourself) principle, clutters your code, and makes unit testing harder.
🔧 The Right Way: Centralize Internet Awareness
We’ll cover three scalable approaches that prevent API calls without internet — without adding boilerplate to every repository.
📶 Build a NetworkStatusTracker
This class observes the system’s network state using ConnectivityManager
and exposes a Flow<Boolean>
:
@Singleton class NetworkStatusTracker @Inject constructor( @ApplicationContext private val context: Context ) { private val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager val networkStatus: Flow<Boolean> = callbackFlow { val callback = object : ConnectivityManager.NetworkCallback() { override fun onAvailable(network: Network) { trySend(true) } override fun onLost(network: Network) { trySend(false) } } val request = NetworkRequest.Builder().build() connectivityManager.registerNetworkCallback(request, callback) trySend(isConnected()) awaitClose { connectivityManager.unregisterNetworkCallback(callback) } }.distinctUntilChanged() private fun isConnected(): Boolean { return connectivityManager.activeNetworkInfo?.isConnected == true } }
🌐 Create an InternetAvailabilityRepository
This class will act as a reactive singleton that tracks the latest connectivity state using Kotlin Flows:
@Singleton class InternetAvailabilityRepository @Inject constructor( networkStatusTracker: NetworkStatusTracker ) { private val _isConnected = MutableStateFlow(true) val isConnected: StateFlow<Boolean> = _isConnected init { CoroutineScope(Dispatchers.Default).launch { networkStatusTracker.networkStatus.collect { _isConnected.value = it } } } }
You can inject this anywhere in your app to get the latest connectivity state.
✅ Option 1: Block API Calls at the Network Layer (OkHttp Interceptor)
You can use an OkHttp
interceptor to block all requests when there’s no internet:
class NoInternetInterceptor( private val internetRepo: InternetAvailabilityRepository ) : Interceptor { override fun intercept(chain: Interceptor.Chain): Response { if (!internetRepo.isConnected.value) { throw IOException("No internet connection") } return chain.proceed(chain.request()) } }
Register the interceptor:
val client = OkHttpClient.Builder() .addInterceptor(NoInternetInterceptor(internetRepo)) .build()
Benefits:
- Centralized logic
- Works for all Retrofit calls
- No manual checks
✅ Option 2: Use a safeApiCall()
Helper Function
If you’re wrapping responses in Result<T>
or Resource<T>
, add a helper function:
suspend fun <T> safeApiCall( internetRepo: InternetAvailabilityRepository, apiCall: suspend () -> T ): Result<T> { return if (!internetRepo.isConnected.value) { Result.failure(NoInternetException()) } else { try { Result.success(apiCall()) } catch (e: Exception) { Result.failure(e) } } }
Usage in any repository:
suspend fun getTasks(): Result<List<Task>> = safeApiCall(internetRepo) { api.getTasks() }
Benefits:
- Reusable logic
- Cleaner repositories
- Works even if you don’t use OkHttp
✅ Option 3: Control Calls in the Use Case Layer
For even more business logic control, guard calls in your Use Case:
class GetTasksUseCase @Inject constructor( private val repo: TaskRepository, private val internetRepo: InternetAvailabilityRepository ) { suspend operator fun invoke(): Result<List<Task>> { if (!internetRepo.isConnected.value) { return Result.failure(NoInternetException()) } return repo.getTasks() } }
Benefits:
- Makes the decision business-driven
- Ideal for apps with offline-first features
Job Offers
🔄 Combine for Maximum Power
Goal Best Option Block all Retrofit calls Interceptor Handle gracefully in code safeApiCall() Decide per feature Use Case Layer
You can use all three depending on your architecture.
📊 Conclusion
Stop repeating if (isConnected)
in every class. Instead, centralize and abstract your logic:
- Build a reactive
NetworkStatusTracker
- Wrap the state inside an
InternetAvailabilityRepository
- Use an OkHttp Interceptor for Retrofit
- Wrap calls in a
safeApiCall
helper - Push business logic to the Use Case layer
Your codebase will be cleaner, more testable, and easier to maintain.

Dobri Kostadinov
Android Consultant | Trainer
Email me | Follow me on LinkedIn | Follow me on Medium | Buy me a coffee
This article was previously published on proandroiddev.com.