Module 10 of 20

Android Architecture (MVVM, Repository, Clean Architecture & SOLID)

Learn professional Android architecture patterns including MVVM, Repository, Clean Architecture, and SOLID principles.

Module 10: Android Architecture (MVVM, Repository, Clean Architecture & SOLID)

Learning Objectives

By the end of this module, you’ll understand:

  • Why architecture exists
  • Why “Massive Activity” is a problem
  • Separation of Concerns (SoC)
  • SOLID principles
  • MVC, MVP, MVVM, MVI comparison
  • Why Google recommends MVVM
  • ViewModel internals
  • LiveData vs StateFlow vs SharedFlow
  • Repository Pattern
  • Use Cases (Domain Layer)
  • Clean Architecture
  • Dependency Injection (conceptual)
  • Modern Android project structure
  • Common architectural mistakes

Part 1 — Why Do We Need Architecture?

Let’s begin with a simple login screen.

Many beginners write:

class LoginActivity : AppCompatActivity() {

    override fun onCreate(...) {

        loginButton.setOnClickListener {

            val email = emailEditText.text.toString()
            val password = passwordEditText.text.toString()

            if(email.isEmpty()){
                ...
            }

            val response = api.login(email,password)

            if(response.success){

                database.saveUser(response.user)

                startActivity(...)

            }

        }
    }
}

Looks fine.

Until the app grows.


Suppose six months later you add:

  • Analytics
  • Logging
  • Offline support
  • Retry mechanism
  • Cache
  • Encryption
  • Unit tests
  • Biometrics
  • Multiple APIs

Now the Activity becomes:

LoginActivity



1500 Lines



2000 Lines



3000 Lines

This is called a Massive Activity (or Massive Fragment).


2. Why Massive Activities Are Bad

Imagine a restaurant.

One chef:

  • Takes orders
  • Cooks
  • Cleans tables
  • Washes dishes
  • Collects payments
  • Buys groceries

Possible?

Technically.

Efficient?

No.

Instead:

Cashier



Chef



Waiter



Cleaner

Everyone has one responsibility.

Software should work the same way.


3. Separation of Concerns (SoC)

One of the most fundamental software engineering principles.

Definition:

Every class should have one well-defined responsibility.

Example:

UI



Business Logic



Data

Three completely different concerns.

Mixing them leads to chaos.


Suppose user taps Login.

What actually happens?

User



Activity



Validate Input



API Call



Parse Response



Save Database



Navigate



Analytics



Logs

Should one class perform all of these?

No.

Architecture distributes responsibilities.


Part 2 — SOLID Principles

Before Android architecture, we need SOLID.

These principles influence almost every modern architecture.


S — Single Responsibility Principle (SRP)

Definition:

A class should have only one reason to change.

Bad:

LoginActivity



UI



API



Database



Analytics

Good:

LoginActivity



LoginViewModel



LoginRepository



ApiService

Each class has one responsibility.


Example:

UserRepository



Only Handles User Data

Not UI.

Not Navigation.

Not Analytics.


O — Open/Closed Principle

Definition:

Open for extension, closed for modification.

Imagine payment methods.

Bad:

if(card)

else if(paypal)

else if(upi)

else if(wallet)

Every new payment method modifies old code.

Better:

PaymentProcessor



CardProcessor



PaypalProcessor



UpiProcessor

Add new classes.

Don’t modify existing ones.


L — Liskov Substitution Principle

Definition:

Subclasses should be replaceable with their parent without breaking behavior.

Bad example:

Bird



Penguin

If Bird.fly() exists,

Penguin breaks the contract.

Poor inheritance design.

Android example:

A custom RecyclerView.Adapter should still behave like a normal adapter.


I — Interface Segregation Principle

Don’t force clients to implement methods they don’t need.

Bad:

Animal



Eat()

Fly()

Swim()

Dog implements Fly()?

Makes no sense.

Create smaller interfaces.


D — Dependency Inversion Principle

One of the most important principles.

Instead of:

ViewModel



Retrofit

Use:

ViewModel



Repository Interface



Retrofit Implementation

High-level modules depend on abstractions, not concrete implementations.

This enables testing and flexibility.


Part 3 — Evolution of Android Architectures


MVC

Original approach.

View



Controller



Model

Android Activity became the Controller.

Problem:

Activity also handled View responsibilities.

Eventually:

Activity



3000 Lines

MVC on Android often degenerated into “Massive Controller.”


MVP

Google’s early solution.

View



Presenter



Model

The View became passive.

Presenter contained business logic.

Problems:

  • Lots of interfaces
  • Boilerplate
  • Harder lifecycle management

MVVM

Google’s recommended architecture.

View



ViewModel



Repository



Data Source

Notice something.

The View doesn’t know where data comes from.

The ViewModel hides that complexity.


MVI

Modern unidirectional architecture.

Intent



Reducer



State



UI

Popular in Compose and functional programming circles.

Excellent for predictable state.

Steeper learning curve.


Comparison

ArchitectureBiggest Problem
MVCMassive Activities
MVPBoilerplate
MVVMExcellent balance
MVIMore complexity but strong state management

Part 4 — MVVM Deep Dive


Why MVVM?

Imagine:

Button Click



Load Users



Display Users

Without MVVM:

Activity



API



Database



UI

Everything is coupled.


With MVVM:

Activity



ViewModel



Repository



Network



Database

Each layer has one job.


The View

Responsibilities:

  • Display UI
  • Observe state
  • Send user actions

Nothing else.

Example:

User Click



ViewModel.login()

The View doesn’t know:

  • Retrofit
  • Room
  • Firebase

The ViewModel

This is the brain of the screen.

Responsibilities:

  • Business logic
  • State management
  • Coordinate repositories
  • Survive configuration changes

It should not know about Android Views like Button or TextView.


Example flow:

User Click



ViewModel



Repository



API



Repository



ViewModel



UI State Updated

Why Does ViewModel Survive Rotation?

Remember Module 5.

Rotation:

Activity Destroyed



Activity Created

ViewModel is stored by a ViewModelStore owned by the Activity (or Fragment).

Activity



ViewModelStore



ViewModel

When a new Activity instance is created after a configuration change, it retrieves the existing ViewModel from the store.

This avoids unnecessary API calls and preserves screen state.


Part 5 — State Management

The ViewModel exposes state.

Historically:

LiveData

Today, Google generally recommends:

StateFlow

For one-time events:

SharedFlow

Let’s understand why.


LiveData

Lifecycle-aware observable.

ViewModel



LiveData



Activity

Pros:

  • Lifecycle-aware
  • Simple
  • XML-friendly

Cons:

  • Android-specific
  • Less flexible than Flow

StateFlow

Think of it as:

A stream that always has a current value.

Example:

Loading



Success



Error



Retry

The UI always knows the latest state.

Ideal for screen state.


SharedFlow

Some events should not replay.

Example:

Show Toast



Navigate



Snackbar

You don’t want a rotation to show the same toast again.

SharedFlow is designed for one-time events.


Rule of Thumb

Use CaseRecommended
Screen UI StateStateFlow
One-time eventsSharedFlow
Legacy XML appsLiveData (or StateFlow with collectors)

Part 6 — Repository Pattern

Suppose your ViewModel needs users.

Without Repository:

ViewModel



Retrofit



Room



Firebase

Too many dependencies.

Instead:

ViewModel



Repository



Network



Database



Cache

The Repository becomes the single source of truth for data access.


Imagine the network fails.

Repository decides:

Network?



Yes → Return API



No



Return Cache

The ViewModel doesn’t care where the data came from.


Part 7 — Clean Architecture

Robert C. Martin (Uncle Bob) proposed Clean Architecture.

Core idea:

Dependencies should point inward.

Typical layers:

Presentation



Domain



Data

Presentation Layer

Contains:

  • Activity
  • Fragment
  • Compose UI
  • ViewModel

Responsible for interacting with users.


Domain Layer

Pure business logic.

Contains:

Use Cases

Entities

Interfaces

Example:

LoginUseCase



Authenticate User

No Android imports.

No Retrofit.

No Room.

This layer can often be reused across platforms.


Data Layer

Contains:

Repository



Remote



Local



Cache

This layer knows how to fetch and store data.


Complete Flow

Imagine user taps Login.

UI



ViewModel



LoginUseCase



UserRepository



Retrofit



API

Response:

API



Repository



UseCase



ViewModel



StateFlow



UI

Notice the clear direction of dependencies.


Part 8 — Dependency Injection (Conceptual)

Suppose:

val repository = UserRepository()

Problem:

The ViewModel decides which repository to use.

Instead:

ViewModel



Needs Repository



DI Provides It

The ViewModel simply declares its dependency.


Benefits:

  • Easier testing
  • Replace implementations
  • Looser coupling

In Android, Hilt is Google’s recommended DI framework.

We’ll dedicate an entire module to DI later.


Part 9 — Recommended Project Structure

A common feature-first organization:

app/

├── data/
│   ├── remote/
│   ├── local/
│   ├── repository/

├── domain/
│   ├── model/
│   ├── repository/
│   └── usecase/

├── ui/
│   ├── login/
│   ├── home/
│   └── profile/

├── di/

└── common/

Many teams organize by feature rather than by technical layer alone, especially in larger apps.


Common Architecture Mistakes

❌ Putting business logic in Activities

Activities should coordinate UI, not perform business rules.


❌ Calling Retrofit directly from the UI

Always go through the Repository (and often a Use Case).


❌ Repositories updating UI

Repositories return data.

The ViewModel decides how that data affects the UI.


❌ ViewModel holding View references

Never keep references to:

  • Activity
  • Fragment
  • TextView
  • Button

This causes memory leaks and violates separation of concerns.


❌ Everything as a Singleton

Not every object should live for the entire app lifetime.

Choose scopes appropriate to the object’s responsibility.


Real-World Example

Let’s trace a product search in an e-commerce app.

User types "Laptop"


SearchScreen (Compose)


SearchViewModel


SearchProductsUseCase


ProductRepository
      ┌─┴─────────────┐
      ▼               ▼
Remote API        Local Cache
      │               │
      └───────┬───────┘

      Repository Result

SearchViewModel updates StateFlow

Compose recomposes with search results

Each layer has a single responsibility:

  • UI displays state.
  • ViewModel manages state.
  • Use Case applies business rules.
  • Repository coordinates data.
  • Data sources fetch and store data.

Mental Model

Think of a hospital.

Patient

Reception

Doctor

Laboratory

Pharmacy

The receptionist doesn’t perform surgery.

The doctor doesn’t manufacture medicine.

Each role has a clear responsibility.

A well-architected Android app follows the same philosophy.


Best Practices

  • Keep UI “dumb” and state-driven.
  • Put business logic in the ViewModel or Use Cases, not Activities.
  • Expose immutable UI state (StateFlow) from ViewModels.
  • Use Repositories as the single entry point for data.
  • Keep the Domain layer independent of Android APIs when using Clean Architecture.
  • Depend on interfaces instead of implementations.
  • Prefer constructor injection with Hilt for dependencies.

Interview Questions

  1. Why do Android apps need an architecture?
  2. What problems do Massive Activities cause?
  3. Explain the SOLID principles with Android examples.
  4. Compare MVC, MVP, MVVM, and MVI.
  5. Why is MVVM Google’s recommended architecture?
  6. Why does a ViewModel survive configuration changes?
  7. When would you use StateFlow vs SharedFlow?
  8. What is the Repository pattern, and why is it useful?
  9. Explain the layers of Clean Architecture.
  10. What problem does Dependency Injection solve?

Module 10 Summary

You now understand the architectural principles behind modern Android development:

  • Separation of Concerns keeps responsibilities clear.
  • SOLID principles make code maintainable and extensible.
  • MVVM separates UI from business logic.
  • ViewModel manages screen state and survives configuration changes.
  • Repositories abstract data access.
  • Clean Architecture organizes applications into Presentation, Domain, and Data layers.
  • Dependency Injection reduces coupling and improves testability.

At this point, you have the conceptual foundation used in most professional Android codebases.


Next Module: Concurrency, Coroutines & Flow

This is where you’ll learn how Android performs work without freezing the UI.

We’ll cover:

  • Why Android has a Main Thread
  • What causes ANRs (Application Not Responding)
  • Threads and their limitations
  • Kotlin Coroutines in depth
  • launch, async, withContext
  • Structured Concurrency
  • Coroutine scopes (viewModelScope, lifecycleScope)
  • Exception handling and cancellation
  • Cold vs Hot Flows
  • Flow, StateFlow, and SharedFlow internals
  • Real-world patterns for networking, databases, and UI updates

This is one of the most technically rich topics in Android, and mastering it is essential for building responsive, modern applications.