Blog Infos
Author
Published
Topics
, , , ,
Published

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

Job Offers

There are currently no vacancies.

OUR VIDEO RECOMMENDATION

Jobs

🔄 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.

Menu