Android Architecture series · Part 5 of 9

VIPER and Modules: Architecture for Big Teams

Khairul Alam·October 2026·~10 min read
VIPERClean ArchitectureModularizationGradleKotlin

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.

In 60 seconds
  • 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.
Contents
  1. VIPER: five roles, one job each
  2. Architecture across modules

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:

LetterRoleDoesDoes NOT
VViewDraws, sends user events to the presenter.Decide anything.
IInteractorBusiness rules for one use case (“load the profile and check it is active”). Talks to repositories.Know about screens.
PPresenterReceives events, calls the interactor, formats results for the view.Touch Android views or the network.
EEntityPlain data classes: Profile, Order.Contain screen logic.
RRouterNavigation: opens the next screen, passes arguments.Hold business logic.
ViewdrawsPresenterformats, coordinatesRouternavigationInteractoruse case rulesEntityplain dataRepository
Each arrow is a small interface. The presenter is the traffic controller; the interactor holds the rules.

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.

GoodWatch 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.

:appwires everything:feature:profile:feature:feed:feature:search:core:domainentities, use cases:core:datarepositories:core:uitheme, widgets
Dependencies point down only. Features never depend on each other; they share things through core modules.

You can mix: MVVM inside one feature, VIPER-style interactors in the domain module. The modules matter more than the label.

When should I use what?

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.

← Back to all writing