Android Architecture series · Part 5 of 9
VIPER and Modules: Architecture for Big Teams
VIPER splits the logic box into smaller, single-purpose roles. Add Gradle modules on top and you get the structure that lets large teams and large apps keep moving.
- VIPER: View, Interactor, Presenter, Entity, Router.
- Interactor = use case; Router = navigation.
- Modules: app, feature and core modules, dependencies point down.
- Trade-off: more files for more clarity and scale.
1. VIPER: five roles, one job each
VIPER came from iOS and takes “single responsibility” very seriously. Instead of one logic box, it splits the middle into three small roles and adds a fifth for navigation. The name is the list:
| Letter | Role | Does | Does NOT |
|---|---|---|---|
| V | View | Draws, sends user events to the presenter. | Decide anything. |
| I | Interactor | Business rules for one use case (“load the profile and check it is active”). Talks to repositories. | Know about screens. |
| P | Presenter | Receives events, calls the interactor, formats results for the view. | Touch Android views or the network. |
| E | Entity | Plain data classes: Profile, Order. | Contain screen logic. |
| R | Router | Navigation: opens the next screen, passes arguments. | Hold business logic. |
The same Profile feature in VIPER
// Entity
data class Profile(val id: String, val name: String, val active: Boolean)
// Interactor: one use case, pure logic, easy to test
class LoadProfileInteractor(private val repo: ProfileRepository) {
suspend operator fun invoke(id: String): Result<Profile> =
runCatching { repo.getProfile(id) }
.mapCatching { if (it.active) it else error("Profile disabled") }
}
// Router: the only class that knows how to move between screens
interface ProfileRouter {
fun openSettings(profileId: String)
}
class ActivityProfileRouter(private val activity: Activity) : ProfileRouter {
override fun openSettings(profileId: String) {
activity.startActivity(Intent(activity, SettingsActivity::class.java).putExtra("id", profileId))
}
}
// Presenter: connects the three
class ProfilePresenter(
private val view: ProfileView, // the ViewProxy
private val loadProfile: LoadProfileInteractor,
private val router: ProfileRouter,
private val scope: CoroutineScope,
) {
fun onStart(id: String) {
view.showLoading()
scope.launch {
loadProfile(id)
.onSuccess { view.showProfile(it) }
.onFailure { view.showError(it.message ?: "Error") }
}
}
fun onSettingsClicked(id: String) = router.openSettings(id)
}
Compare it with MVP from Part 3: the presenter got thinner because the rules moved into the interactor, and navigation moved into the router. That is the whole difference. If you ever wrote a “use case” class in an MVP or MVVM app, you already used the I of VIPER. It is also the same idea as the use case layer in Clean Architecture.
| Good | Watch out |
|---|---|
| Very testable and very clear ownership; great for big teams. | Many files per screen. A simple screen can feel like overkill (KISS!). |
| Navigation is not scattered through Activities. | Needs discipline and templates or code generation to stay consistent. |
| Interactors are reusable across screens. | Beginners struggle to decide which class owns which line. |
2. Architecture across modules
Every variant above organizes code inside a screen. For team and feature scale you also organize between screens by splitting the app into Gradle modules.
- Scalable team size: each team owns a feature module; fewer merge conflicts.
- Scalable feature size: a new feature is a new module that follows the same template.
- Faster builds: Gradle rebuilds only the modules that changed.
- Enforced boundaries: a feature that cannot see another feature’s classes cannot misuse them. Use
implementationinstead ofapito keep internals hidden.
You can mix: MVVM inside one feature, VIPER-style interactors in the domain module. The modules matter more than the label.
Small app or prototype: MVVM with a repository. Complex flows or large team: add a domain layer (interactors/use cases) and modules. Legacy Activity-heavy app: MVP is the gentlest first step. Pick one, write it down in a one-page template, and use it everywhere.