agentleFS
javiercamarenatriguerojaviercamarenatriguero/android-skills/android-modularization/SKILL.md

android-modularization

Skill8 starsChanged 4 months agoApache-2.0View on GitHub

Designs and enforces a clean multi-module Android architecture with strict layer isolation. Use when user asks "where should this code go", "create a new module", "fix a circular dependency", "what layer does this belong in", or "set up module architecture".

1 other project uses a version of this.

Adopted by

Most recently taken up by

The file

We split this skill into the rules it gives an agent

2 rules. About 0 are common, 0 are distinctive, and 2 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈0

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 2

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    All Dependency Injection configuration is centralized in the app module.

    line 226
  2. Very specific:

    All navigation graphs live in app/navigation/.

    line 243

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Android Modularization

Overview

This skill defines a four-layer module architecture for Android projects that scales well, enforces separation of concerns, and avoids circular dependencies. It is entirely project-agnostic and can be applied to any Android codebase.


Module Layers

app/
feature/
  └── feature-a/
  └── feature-b/
component/
  └── component-x/
  └── component-y/
integration/          ← optional, bridges components
  └── integration-ab/
common/
  └── common-utils/
  └── common-ui/

Layer Diagram

graph TD
    subgraph APP["app (entry point)"]
        A[":app"]
    end

    subgraph FEATURE["feature/* (presentation)"]
        F1[":feature:feature-a"]
        F2[":feature:feature-b"]
    end

    subgraph INTEGRATION["integration/* (optional bridge)"]
        I1[":integration:a-b"]
    end

    subgraph COMPONENT["component/* (domain + data)"]
        C1[":component:component-x"]
        C2[":component:component-y"]
    end

    subgraph COMMON["common/* (shared utilities)"]
        CM1[":common:utils"]
        CM2[":common:ui"]
    end

    A --> F1
    A --> F2
    A --> C1
    A --> C2
    A --> I1

    F1 --> C1
    F1 --> CM1
    F1 --> CM2
    F2 --> C2
    F2 --> I1
    F2 --> CM2

    I1 --> C1
    I1 --> C2
    I1 --> CM1

    C1 --> CM1
    C2 --> CM1

    style APP fill:#4A90D9,color:#fff,stroke:#2c6fad
    style FEATURE fill:#7B68EE,color:#fff,stroke:#5a4db0
    style INTEGRATION fill:#E8A838,color:#fff,stroke:#b87d1a
    style COMPONENT fill:#5BA85A,color:#fff,stroke:#3d7a3d
    style COMMON fill:#888,color:#fff,stroke:#555

Read the arrows as "depends on". Arrows always flow downward — never upward, never sideways within the same layer.

Layer Responsibilities

LayerScopeContains
appEntry point & wiringDI initialization, Navigation graphs, Application class
feature/*UI presentationScreens, ViewModels, MVI Contracts
component/*Business logicDomain Models, Use Cases, Repository interfaces + implementations, Data Sources
integration/*Cross-component orchestrationUse Cases that coordinate ≥2 components
common/*Shared utilitiesExtensions, formatters, shared UI primitives, no business logic

Dependency Rules (Critical)

app         → feature/*, component/*, integration/*, common/*
feature/*   → component/*, integration/*, common/*
integration/* → component/*, common/*
component/* → common/* only
common/*    → (no dependencies)

Allowed vs Forbidden Dependencies

flowchart LR
    subgraph ALLOWED["✅ Allowed"]
        direction TB
        a1["app → feature"] 
        a2["app → component"]
        a3["feature → component"]
        a4["feature → integration"]
        a5["integration → component"]
        a6["any → common"]
    end

    subgraph FORBIDDEN["❌ Forbidden"]
        direction TB
        f1["feature/A → feature/B"]
        f2["component/A → component/B"]
        f3["any → app"]
        f4["common → feature/component"]
    end

    style ALLOWED fill:#d4edda,stroke:#28a745,color:#155724
    style FORBIDDEN fill:#f8d7da,stroke:#dc3545,color:#721c24

Zero-Tolerance Rules

  • ❌ feature/A → feature/B — Features must never depend on each other
  • ❌ component/A → component/B — Components must never depend on each other
  • ❌ Any module → app — app is a leaf consumer
  • ✅ When component/A needs component/B, create integration/A-B

Why These Rules?

  • Prevents circular dependencies: A → B → A impossible
  • Enables independent testing: Each module testable in isolation
  • Parallel development: Teams can work on separate features simultaneously
  • Easy replacement: Swap implementations without breaking consumers

Module Structure by Type

Feature Module

feature/my-feature/
├── build.gradle.kts
└── src/
    └── main/java/.../feature/myfeature/
        ├── MyFeatureContract.kt       # ViewState / ViewEvent / SideEffect
        ├── MyFeatureViewModel.kt
        ├── MyFeatureScreen.kt
        └── components/               # Internal composables

build.gradle.kts (minimal):

plugins {
    id("android-library-convention")   // your convention plugin
    id("compose-convention")
    id("unit-test-convention")
}

dependencies {
    implementation(project(":component:my-component"))
    implementation(project(":common:ui"))
}

Component Module

component/my-component/
├── build.gradle.kts
└── src/
    └── main/java/.../component/mycomponent/
        ├── domain/
        │   ├── model/          # Domain data classes
        │   ├── repository/     # Repository interfaces
        │   └── usecase/        # Use Cases
        └── data/
            ├── repository/     # Repository implementations
            ├── datasource/     # Local / Remote data sources
            └── mapper/         # Entity ↔ Domain mappers

build.gradle.kts (minimal):

plugins {
    id("android-library-convention")
    id("unit-test-convention")
}

dependencies {
    implementation(project(":common:utils"))
}

Common Module

common/utils/
├── build.gradle.kts
└── src/
    └── main/java/.../common/utils/
        ├── extensions/
        └── formatters/

Common modules have zero dependencies on other project modules.


DI Wiring — Always in app

All Dependency Injection configuration is centralized in the app module.

app/src/main/java/.../di/
├── AppModule.kt           # Platform & framework providers
├── RepositoryModule.kt    # Data layer bindings
├── DomainModule.kt        # Use Cases
└── ViewModelModule.kt     # ViewModels
  • Feature and component modules use constructor injection only
  • They never contain DI setup code

Navigation — Always in app

All navigation graphs live in app/navigation/.

app/src/main/java/.../navigation/
├── RootNavGraph.kt
└── myfeature/
    └── MyFeatureNavigation.kt
  • Feature modules expose screen composables only
  • Navigation side effects are handled at the app level

When to Create an Integration Module

Create integration/A-B when:

  1. A Use Case from component/A needs data from component/B
  2. An orchestration flow spans multiple components
  3. You find yourself wanting to add component/B as a dependency of component/A
// integration/order-payment/domain/usecase/PlaceOrderUseCase.kt
class PlaceOrderUseCase(
    private val orderRepository: OrderRepository,      // from component/order
    private val paymentRepository: PaymentRepository,  // from component/payment
)

Common Anti-Patterns

❌ Business logic in feature modules

// BAD — logic inside ViewModel
class OrderViewModel : ViewModel() {
    fun calculateTotal(items: List<Item>): Double {
        return items.sumOf { it.price * it.quantity } * 1.21  // VAT hardcoded
    }
}
// GOOD — logic in Use Case inside component
class CalculateOrderTotalUseCase(private val taxRate: Double) {
    operator fun invoke(items: List<Item>): Double =
        items.sumOf { it.price * it.quantity } * (1 + taxRate)
}

❌ Shared state between features via direct dependency

// BAD
// feature/checkout depends on feature/cart
implementation(project(":feature:cart"))
// GOOD — both features depend on component/cart
// feature/checkout → component/cart
// feature/cart     → component/cart

❌ Repository implementation in domain layer

// BAD — RepositoryImpl inside domain/
component/orders/domain/repository/OrderRepositoryImpl.kt
// GOOD — interface in domain, impl in data
component/orders/domain/repository/OrderRepository.kt      // interface
component/orders/data/repository/OrderRepositoryImpl.kt    // implementation

Checklist: New Module

  • Module placed in correct layer (feature/, component/, common/, integration/)
  • Only depends on layers below it (see dependency table)
  • No dependency on app module
  • Business logic NOT placed in feature module
  • UI NOT placed in component module
  • DI registration added in app/di/
  • Navigation (if UI module) added in app/navigation/
  • Convention plugin applied (no manual SDK/test config)

References

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.