Modularization Quiz
BUILD & TOOLING › Gradle
Which dependency relationship violates recommended Android modularization rules?
- A feature module depending on a shared data module for its models
- A feature module depending straight on another feature module
- The app module depending on a feature module for navigation
- A data module depending on a core module for common utilities
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?
- It exposes the dependency transitively to every downstream module.
- The api configuration is deprecated in the current Android plugin.
- It keeps the dependency internal, shrinking the API and recompiles
- Kotlin multiplatform modules must use implementation for inter-module deps.
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?
- Pass a primitive ID via navigation, then load it from a data module
- Serialize the entire domain object directly into the navigation arguments
- Add a direct Gradle dependency from one feature module to the other
- Store the object inside a global mutable singleton in the app module
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?
- Hosting the checkout UI and its ViewModel for that screen
- Defining the user-data repository interface for the domain
- Supplying concrete DI implementations and root navigation
- Encapsulating data sources for a single feature's domain
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?
- Merge both source implementations into a single coarse data module
- Dependency inversion: features depend only on an abstraction module
- Make each feature depend via api on both implementation modules
- Use a static factory inside each feature module to select the source
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?
- More modules are always better since overall builds get faster and linearly
- A single giant data module stays ideal no matter how large the project
- Too-fine modules add build and boilerplate overhead beyond the benefits
- Module granularity has no measurable effect at all on build times
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?
- Android library modules only work inside the app module itself
- Kotlin/Java modules support Jetpack Compose, but Android ones do not
- They avoid Android build overhead like manifests and resources
- Only Kotlin/Java modules can use the implementation configuration
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?
- Whenever the module is an Android library, not a plain Kotlin module
- Only when the dependency is needed solely by unit test source sets
- When that dependency appears in the module’s public API signatures
- Never, because newer Android Gradle Plugin versions removed api
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?
- To centralize repeated build setup and keep modules consistent
- To let feature modules safely depend on one another at build time
- To remove the need for a Hilt-based dependency graph in the app
- To merge all modules back into a single compilation unit again
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?
- The base app module declares an implementation dependency on the dynamic feature module
- They depend on each other bidirectionally using the api configuration
- Neither references the other; they communicate only through reflection
- The dynamic feature module declares a dependency on the base app module, not the reverse
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?
- Marking the class private at the file level, limiting it to one file
- Marking the class internal so only the module can use it
- Exposing the class through the api configuration for other modules
- Wrapping the class in a sealed interface to block outside references
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?
- The settings screen's ViewModel and Compose UI, plus its state handling
- The root navigation graph and concrete DI setup that wires everything up
- Reusable building blocks like networking, design system, and analytics
- A repository serving only the checkout feature and its private data flow
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?
- Gradle silently picks one direction and continues building
- The build succeeds but the app crashes at runtime with a ClassNotFoundException
- Only the app module is affected while the libraries build fine
- Gradle fails the build with a circular dependency error
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?
- It centralizes dependency coordinates and versions in one shared place
- It automatically removes accidental feature-to-feature dependencies
- It enables parallel compilation of modules that could not build together
- It replaces Hilt for providing dependencies across module boundaries
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.