DEV Community

YADNYESH RANA
YADNYESH RANA

Posted on

Bypassing Circular Dependencies in Android: The API/Implementation Module Pattern

As Android codebases scale, modularization is inevitable. We break down monolithic apps to achieve faster build times, separate team ownership, and isolate features.

However, many teams fall into a common trap: direct feature-to-feature dependencies.

If :feature:onboarding needs to trigger :feature:auth, developers often add a direct dependency. But what happens when :feature:auth needs to redirect back to onboarding, or share a common session state? You hit the dreading Circular Dependency compiler error.

To fix this, teams often resort to merging modules or writing messy reflection wrappers.

Here is the clean, enterprise-grade solution: The API vs. Implementation Module Separation.


The Core Concept: Dependency Inversion at Module Level

Instead of creating a single feature module, we split the feature into two separate Gradle modules:

  1. :feature:auth:api (Lightweight interface module): Contains only public interfaces, DTOs, navigation entry points, and event states. It has no dependency on UI, Compose, or database layers.
  2. :feature:auth:impl (Concrete implementation module): Contains the actual UI screens, Compose layouts, view models, database helper engines, and internal Hilt modules. It implements the interfaces defined in the :api module.
:feature:onboarding (Feature UI)
       │
       ▼ (Depends on API only)
:feature:auth:api (Public Interface) ◄─── (Implemented by) ─── :feature:auth:impl (Private UI)
Enter fullscreen mode Exit fullscreen mode

Now, :feature:onboarding only needs to depend on the lightweight :feature:auth:api module. It calls the public interface, while the actual implementation (:feature:auth:impl) is injected dynamically at runtime.


Step-by-Step Implementation

1. Define the API Interface (:feature:auth:api)

The API module contains only standard Kotlin code and basic model dependencies. Here, we define the contract for other modules:

// Location: :feature:auth:api
interface AuthApi {
    fun getAuthFlow(): Flow<UserSession?>
    suspend fun logout()
}
Enter fullscreen mode Exit fullscreen mode

2. Implement the Business Logic (:feature:auth:impl)

The implementation module depends on its corresponding API module and implements the interface:

// Location: :feature:auth:impl/build.gradle.kts
dependencies {
    implementation(projects.feature.auth.api)
    implementation(libs.hilt.android)
}

// Location: :feature:auth:impl/src/.../AuthApiImpl.kt
class AuthApiImpl @Inject constructor(
    private val tokenStorage: TokenStorage,
    private val database: AppDatabase
) : AuthApi {
    override fun getAuthFlow(): Flow<UserSession?> = tokenStorage.observeSession()
    override suspend fun logout() {
        tokenStorage.clear()
        database.clearAllTables()
    }
}
Enter fullscreen mode Exit fullscreen mode

3. Bind the Interface dynamically using Hilt

Inside the implementation module, define a Hilt module to bind the implementation to the interface. Because other modules request the AuthApi interface, Hilt will resolve it to the AuthApiImpl class at runtime:

// Location: :feature:auth:impl/src/.../AuthModule.kt
@Module
@InstallIn(SingletonComponent::class)
abstract class AuthModule {
    @Binds
    @Singleton
    abstract fun bindAuthApi(impl: AuthApiImpl): AuthApi
}
Enter fullscreen mode Exit fullscreen mode

Why this improves your Gradle builds

  • Zero Circular Dependencies: Sibling feature modules can call each other's APIs without circular compiler cycles.
  • Faster Incremental Builds: If you modify a Compose layout inside :feature:auth:impl, only that module compiles. Sibling modules (like onboarding) are completely bypassed because the API module (:feature:auth:api) hasn't changed.
  • Parallel Workflows: Teams can develop features in parallel. You can write your feature code using mock API implementations before the other team even begins writing their concrete implementation code.

📘 Master Android System Design & Architecture

If you want to master clean architecture, offline-first syncing, performance profiling, and dependency injection patterns, grab The Ultimate Android System Design & Tech Lead Bundle:

➡️ Get the Ultimate Tech Lead Bundle on Gumroad

The bundle includes the flagship System Design Playbook (26 pages) along with three premium bonuses: the 2026 Interview Question Bank, the Jetpack Compose Custom UI Cookbook, and the Android Gradle Optimization Cheat Sheet.

Top comments (0)