Modularization Quiz

BUILD & TOOLING › Gradle

Which dependency relationship violates recommended Android modularization rules?

Answer: A feature module depending straight on another feature module

Feature modules must not depend on each other, as it creates tight coupling and risks cyclic dependencies; they should communicate via shared data modules or a mediator.

Why is Gradle's implementation configuration preferred over api for inter-module dependencies?

Answer: It keeps the dependency internal, shrinking the API and recompiles

implementation keeps a dependency internal to the module so consumers neither see it nor recompile when it changes; api leaks it transitively and should be reserved for when consumers need it directly.

What is the recommended way to pass data between two feature screens that live in different modules?

Answer: Pass a primitive ID via navigation, then load it from a data module

Passing only a primitive ID and re-fetching from a shared data module preserves a single source of truth and keeps the features decoupled, avoiding object serialization and direct feature-to-feature dependencies.

Which responsibility belongs to the :app module rather than a :feature module?

Answer: Supplying concrete DI implementations and root navigation

The app module is the entry point that wires up root navigation and supplies concrete DI implementations (often per build variant); features hold their own UI and ViewModels.

A team wants to swap a Room-backed data source for a Firestore one without recompiling feature modules. Which pattern best supports this?

Answer: Dependency inversion: features depend only on an abstraction module

With dependency inversion, features depend only on an abstraction (interfaces/models) and the app module injects the chosen implementation, so swapping implementations does not force recompilation of consumers.

Which statement about choosing module granularity is correct?

Answer: Too-fine modules add build and boilerplate overhead beyond the benefits

Excessively fine-grained modules introduce configuration and boilerplate overhead, while overly coarse ones recreate a monolith; the right granularity balances these trade-offs against codebase size.

Why does the Android guidance recommend pure Kotlin/Java modules over Android library modules where possible?

Answer: They avoid Android build overhead like manifests and resources

Android library modules carry manifest and resource processing overhead; when a module holds no Android-specific code, a pure Kotlin/Java module is lighter and builds faster.

When is Gradle's api configuration actually the correct choice for a module dependency instead of implementation?

Answer: When that dependency appears in the module’s public API signatures

api is appropriate only when a dependency's types appear in the module's public surface (return types, parameters) so consumers genuinely need them transitively; otherwise implementation keeps it encapsulated.

What is the main purpose of moving shared Gradle build logic into a convention plugin inside a build-logic included build?

Answer: To centralize repeated build setup and keep modules consistent

Convention plugins centralize repeated setup (plugins, compile options, common dependencies) so each module applies one plugin instead of copying boilerplate, reducing drift as the module count grows.

In a Play Feature Delivery setup, what is the dependency direction between the base :app module and a dynamic feature module?

Answer: The dynamic feature module declares a dependency on the base app module, not the reverse

Dynamic feature modules invert the usual direction: the on-demand feature depends on the base app module, while the base app does not depend on the feature so it can be installed and delivered separately.

Within a single Gradle module, which Kotlin mechanism best enforces that a class cannot be referenced from other modules?

Answer: Marking the class internal so only the module can use it

The internal visibility modifier scopes a declaration to its own Gradle module, so other modules cannot reference it even if it is in a public package; file-level private is narrower (one file) and sealing does not restrict cross-module use.

Which code most appropriately belongs in a :core (or :core-common) module?

Answer: Reusable building blocks like networking, design system, and analytics

Core modules hold cross-cutting, reusable building blocks (network clients, design system, analytics) that multiple features and data modules depend on, with no feature-specific logic of their own.

What happens if module A depends on module B while module B also depends on module A in Gradle?

Answer: Gradle fails the build with a circular dependency error

Gradle requires an acyclic dependency graph; a cycle between modules is detected at configuration/resolution time and fails the build, which is why dependency direction must stay strictly one-way.

What problem does a Gradle version catalog (libs.versions.toml) primarily solve in a multi-module project?

Answer: It centralizes dependency coordinates and versions in one shared place

A version catalog gives all modules a single typesafe source of dependency aliases and versions, preventing version drift and duplicated coordinate strings across many build files.

Back to Modularization