Back to Blog

Android Architecture for Absolute Beginners

Hey! Let's learn this together. I'll explain everything like you've never coded an Android app before. No jargon without explanation. Lots of pictures. Promise.


Chapter 1: What is "Architecture"? (The House Story)

Imagine you want to build a house.

You have two choices:

flowchart LR
    subgraph Bad[Way 1: No Plan]
        B1[Throw bricks
wherever] B2[Kitchen in bedroom] B3[Toilet in living room] B4[House falls down] end subgraph Good[Way 2: With a Plan] G1[Foundation first] G2[Walls next] G3[Rooms organized] G4[Strong house] end

Architecture in Android = the plan for your app.

It tells you:

  • Where to put what code
  • How different parts talk to each other
  • How to change things later without breaking everything

That's it. No magic. Just organization.


Chapter 2: The Restaurant Story (Understanding the Layers)

Forget code for a moment. Imagine a restaurant.

flowchart TB
    Customer([Customer
You]) -->|orders food| Waiter Waiter -->|tells the order| Chef Chef -->|asks for ingredients| Storage[Storage Room
Fridge + Pantry] Storage -->|gives ingredients| Chef Chef -->|cooks and gives food| Waiter Waiter -->|serves| Customer

Who does what?

PersonJobDoes NOT do
CustomerOrders, eatsCook, manage storage
WaiterTakes order, servesCook, store food
ChefCooksGreet customer, manage storage
StorageHolds ingredientsCook, talk to customer

This is EXACTLY how Android apps are organized.

flowchart TB
    User([User
Customer]) -->|taps button| UI[UI
Waiter] UI -->|asks for data| VM[ViewModel
Chef] VM -->|gets data| Repo[Repository
Storage Room] Repo -->|returns data| VM VM -->|gives ready data| UI UI -->|shows on screen| User
RestaurantAndroid
CustomerUser
WaiterUI (Buttons, Screens)
ChefViewModel
StorageRepository (Database + Internet)

One golden rule: The waiter doesn't cook. The chef doesn't serve. Each part has ONE job.


Chapter 3: What Happens When You Tap a Button?

Let's say you open Instagram and tap the heart icon to like a photo.

sequenceDiagram
    actor You
    participant Screen as Screen
(UI) participant Brain as ViewModel
(Brain) participant Storage as Repository
(Storage) participant Internet as Internet
(Server) You->>Screen: Tap heart Screen->>Brain: User tapped like! Brain->>Storage: Save the like Storage->>Internet: Tell server Internet-->>Storage: Done! Storage-->>Brain: Saved! Brain-->>Screen: Show red heart Screen-->>You: red heart

Read it like a story:

  1. You tap the heart
  2. Screen tells the Brain: "Hey, user wants to like this!"
  3. Brain tells Storage: "Please save this like"
  4. Storage tells Internet: "Send this to server"
  5. Internet says: "Done!"
  6. Storage tells Brain: "All saved!"
  7. Brain tells Screen: "Make the heart red"
  8. You see the red heart

That's an entire architecture working. Just a chain of helpers, each doing one small job.


Chapter 4: Your First Real Example (Counter App)

Let's build the simplest app ever: a button that counts how many times you tap it.

The 3 Pieces

flowchart LR
    subgraph Screen[1. Screen - What You See]
        Btn[Button: Tap me]
        Num[Number: 0]
    end

    subgraph Brain[2. Brain - ViewModel]
        Counter[count = 0]
        Func[increment]
    end

    Btn -->|tap!| Func
    Func -->|count + 1| Counter
    Counter -->|new number| Num
        

The Code (Don't worry, I'll explain every line)

Step 1: The Brain (ViewModel)

class CounterViewModel : ViewModel() {

    // This holds the number. It starts at 0.
    private val _count = MutableStateFlow(0)

    // The screen will watch this.
    val count: StateFlow<Int> = _count

    // When user taps the button, this runs.
    fun onTapClick() {
        _count.value = _count.value + 1
    }
}

What's happening?

  • ViewModel = the brain. It thinks. It remembers.
  • _count = a box that holds the number 0.
  • MutableStateFlow = a magic box that tells anyone watching when the number changes.
  • onTapClick() = the function that runs when you tap.

Step 2: The Screen (Composable)

@Composable
fun CounterScreen(viewModel: CounterViewModel = viewModel()) {

    // Watch the count. Whenever it changes, redraw the screen.
    val count by viewModel.count.collectAsState()

    Column {
        Text("You tapped $count times")

        Button(onClick = { viewModel.onTapClick() }) {
            Text("Tap me!")
        }
    }
}

What's happening?

  • @Composable = "this is a piece of UI"
  • collectAsState() = "watch the brain's number"
  • When user taps → call brain's function → brain updates number → screen sees new number → screen redraws

The Flow Animation

flowchart TB
    A[User sees: 0] -->|tap| B[Screen says: onTapClick!]
    B --> C[Brain: count = 0 + 1 = 1]
    C --> D[Brain shouts: New value is 1!]
    D --> E[Screen hears: redraw with 1]
    E --> F[User sees: 1]
    F -->|tap again| G[count = 2]
    G --> H[User sees: 2]
        

That's MVVM. Yes, you just learned MVVM.


Chapter 5: What Do Those Scary Words Mean?

Now let's name the things you saw:

flowchart TB
    subgraph MVVM
        M[M = Model
The data
like: count = 5] V[V = View
What user sees
Buttons, Text] VM[VM = ViewModel
The brain in middle] end V -->|user actions| VM VM -->|data| V VM <--> M
WordMeansLike in restaurant
ModelThe actual dataThe food itself
ViewWhat user seesThe waiter showing the food
ViewModelThe brain connecting themThe chef
MVVMModel + View + ViewModel togetherThe whole restaurant system
ComposeNew way to draw screens in AndroidModern way to design plates
StateFlowMagic box that shouts when changedBell on the counter
KotlinProgramming language we useThe language everyone speaks

Chapter 6: Where Does Data Come From? (The Repository)

Apps need data. From where?

flowchart TB
    subgraph Sources[Where data lives]
        Internet[Internet
Server somewhere] Phone[Your Phone
Local Database] Memory[RAM
Just for now] end Sources --> Repo[Repository
The Manager] Repo --> Brain[ViewModel] Brain --> Screen[Screen]

Repository = one place that knows where to get data.

Without Repository:

  • ViewModel has to know about internet
  • ViewModel has to know about database
  • ViewModel has to know about everything
  • ViewModel becomes a monster

With Repository:

  • ViewModel just asks Repository: "Give me data"
  • Repository decides: "Should I get from internet or phone?"
  • ViewModel doesn't care. Simple.

Example

class UserRepository {

    // Get user. First try phone. If not there, get from internet.
    suspend fun getUser(id: String): User {

        val userInPhone = database.findUser(id)
        if (userInPhone != null) {
            return userInPhone   // Found in phone, easy!
        }

        // Not in phone, ask the internet
        val userFromInternet = api.fetchUser(id)
        database.save(userFromInternet)   // Save for next time
        return userFromInternet
    }
}

Now ViewModel just does:

class ProfileViewModel(private val repo: UserRepository) : ViewModel() {
    fun loadUser() {
        viewModelScope.launch {
            val user = repo.getUser("user123")
            // Show on screen
        }
    }
}

ViewModel has NO IDEA whether the data came from internet or database. And it doesn't care! That's the beauty.


Chapter 7: Clean Architecture (The Building with Floors)

Imagine a 3-floor building:

flowchart TB
    subgraph Floor3[Floor 3: Presentation - What User Sees]
        UI[Screens + ViewModels]
    end

    subgraph Floor2[Floor 2: Domain - The Rules]
        UseCases[Use Cases
Business Rules] end subgraph Floor1[Floor 1: Data - Where Data Lives] Data[Repository + API + Database] end Floor3 -->|asks| Floor2 Floor2 -->|asks| Floor1 Floor1 -->|gives data back| Floor2 Floor2 -->|gives data back| Floor3

What's a "Use Case"?

A Use Case is ONE thing your app can do.

Examples:

  • LoginUseCase → "Log the user in"
  • GetUserUseCase → "Get user info"
  • LikePhotoUseCase → "Like a photo"
  • LogoutUseCase → "Log the user out"

Each Use Case does ONE job. That's it.

class LoginUseCase(private val repo: AuthRepository) {

    // This is the only thing this class does. Login. Nothing else.
    suspend operator fun invoke(email: String, password: String): Result<User> {
        return repo.login(email, password)
    }
}

Why have Use Cases?

flowchart LR
    subgraph Without[Without Use Cases]
        VM1[ViewModel knows
about Repository
+ all business rules
+ validation
= BIG] end subgraph With[With Use Cases] VM2[ViewModel] --> UC1[LoginUseCase] VM2 --> UC2[ValidateEmailUseCase] UC1 --> Repo[Repository] end

Without: ViewModel does everything = big mess.

With: ViewModel just calls small Use Cases = neat and tidy.


Chapter 8: Hilt — The Magic Connector

Look at this code:

class LoginViewModel(
    private val loginUseCase: LoginUseCase  // where does this come from?
)

loginUseCase needs a Repository. Repository needs an API and a Database. API needs Retrofit. Retrofit needs OkHttp.

Without Hilt:

val okHttp = OkHttp()
val retrofit = Retrofit(okHttp)
val api = Api(retrofit)
val database = Database()
val repo = Repository(api, database)
val useCase = LoginUseCase(repo)
val viewModel = LoginViewModel(useCase)

You have to write ALL of this YOURSELF. For EVERY screen.

With Hilt:

@HiltViewModel
class LoginViewModel @Inject constructor(
    private val loginUseCase: LoginUseCase  // Hilt builds it!
) : ViewModel()

That's it. Hilt does all the wiring for you.

The Hilt Story

flowchart TB
    Hilt[Hilt - The Genie] -->|I need a ViewModel!| Build[Hilt builds it...]
    Build --> Step1[Build OkHttp]
    Build --> Step2[Build Retrofit]
    Build --> Step3[Build API]
    Build --> Step4[Build Database]
    Build --> Step5[Build Repository]
    Build --> Step6[Build UseCase]
    Build --> Step7[Build ViewModel]
    Step7 --> Done[Here's your ViewModel!]
        

You tell Hilt once how to build each thing. Then it builds everything automatically forever.

@Module
@InstallIn(SingletonComponent::class)
object AppModule {

    @Provides
    fun provideApi(): Api = Retrofit.Builder()
        .baseUrl("https://api.example.com")
        .build()
        .create(Api::class.java)

    @Provides
    fun provideDatabase(@ApplicationContext ctx: Context): Database {
        return Room.databaseBuilder(ctx, Database::class.java, "app.db").build()
    }
}

After this, Hilt knows how to make an Api and a Database. Anywhere you need them, just write @Inject.


Chapter 9: Modularization (LEGO Blocks)

Imagine your whole app is in ONE giant file. 100,000 lines.

Problems:

  • Change one thing → rebuild EVERYTHING
  • Two developers → both editing same file → conflicts
  • Can't reuse code in another app

Solution: Break into LEGO blocks (modules)

flowchart TB
    APP[:app
Main app] subgraph Features[Feature LEGOs] F1[:feature:login] F2[:feature:home] F3[:feature:profile] end subgraph Tools[Shared Tool LEGOs] T1[:core:network] T2[:core:database] T3[:core:ui] end APP --> F1 APP --> F2 APP --> F3 F1 --> T1 F2 --> T1 F3 --> T2 F1 --> T3 F2 --> T3 F3 --> T3

Why is this awesome?

flowchart LR
    Edit[You edit :feature:login] --> Rebuild[Only rebuild :feature:login + :app]
    Edit2[Other modules?] --> Cached[Cached! Fast!]
        

Build time goes from 5 minutes → 30 seconds.

Folder structure

MyApp/
├── app/                    ← entry point
│
├── feature/
│   ├── login/             ← LEGO: login screen
│   ├── home/              ← LEGO: home screen
│   └── profile/           ← LEGO: profile screen
│
└── core/
    ├── network/           ← LEGO: Retrofit stuff
    ├── database/          ← LEGO: Room stuff
    ├── ui/                ← LEGO: shared buttons/colors
    └── domain/            ← LEGO: Use Cases + entities

Each folder is independent. Like a LEGO block. You can take any block out and the rest still works (mostly).


Chapter 10: The Complete Picture

Now let's put it ALL together. Here's a complete app's architecture:

flowchart TB
    User([User])

    subgraph UI[1. UI Layer - What User Sees]
        Screen[Compose Screen]
        VM[ViewModel
holds UI state] end subgraph Domain[2. Domain Layer - Business Rules] UC[Use Cases
One job each] end subgraph Data[3. Data Layer - Where Data Lives] Repo[Repository] API[Remote API] DB[Local Database] end User -->|taps| Screen Screen -->|action| VM VM -->|call| UC UC -->|ask data| Repo Repo --> API Repo --> DB DB -->|data| Repo API -->|data| Repo Repo -->|data| UC UC -->|data| VM VM -->|state| Screen Screen -->|shows| User

Chapter 11: Full Animation — Login Example

You're going to log into an app. Watch what happens behind the scenes:

sequenceDiagram
    autonumber
    actor User
    participant Screen as Login Screen
    participant VM as LoginViewModel
    participant UC as LoginUseCase
    participant Repo as AuthRepository
    participant API as Server
    participant DB as Database

    User->>Screen: Types email + password
    User->>Screen: Taps "Login"
    Screen->>VM: onLoginClick()
    VM->>VM: Show loading spinner
    VM->>UC: invoke(email, password)
    UC->>Repo: login(email, password)
    Repo->>API: POST /login
    API-->>Repo: Success + token
    Repo->>DB: Save user info
    DB-->>Repo: Saved
    Repo-->>UC: Result.success(User)
    UC-->>VM: Result.success(User)
    VM->>VM: Hide spinner, success!
    VM-->>Screen: Navigate to Home
    Screen-->>User: Welcome home!
        

Read it like a story. Each arrow is one step. Each character has one job.


Chapter 12: Common Mistakes Beginners Make

flowchart TB
    subgraph Bad[Don't Do This]
        B1[Put everything in Activity]
        B2[Network call from Composable]
        B3[Database access from ViewModel directly]
        B4[Reference Activity in ViewModel]
    end

    subgraph Good[Do This]
        G1[Composable to ViewModel to Repository]
        G2[Repository handles network + DB]
        G3[Use Hilt for dependencies]
        G4[Keep ViewModel free of Android stuff]
    end
        
MistakeBetter way
Putting network code in ActivityPut it in Repository
Calling API directly from screenScreen → ViewModel → UseCase → Repository
Holding Context in ViewModel (memory leak!)Pass only data, no Context
Logic inside @ComposableLogic in ViewModel
Mixing UI + business rulesKeep them in separate layers

Chapter 13: Your Learning Roadmap

flowchart LR
    Step1[1. Learn Kotlin basics] --> Step2[2. Build a Counter app]
    Step2 --> Step3[3. Add ViewModel + StateFlow]
    Step3 --> Step4[4. Add Repository]
    Step4 --> Step5[5. Add Retrofit + Room]
    Step5 --> Step6[6. Add Hilt]
    Step6 --> Step7[7. Add Use Cases]
    Step7 --> Step8[8. Split into modules]
    Step8 --> Pro[You're a pro!]
        

Pace yourself. Don't try to learn everything in one day. Build small. Each step takes 1-2 weeks.


Chapter 14: Cheat Sheet (Print This)

QuestionAnswer
Where does UI go?Composable functions
Where does state go?ViewModel (in a StateFlow)
Where does business logic go?UseCase classes
Where does data fetching go?Repository
How do I connect everything?Hilt with @Inject
How do I update UI?Update StateFlow in ViewModel
What survives screen rotation?ViewModel
What should NOT be in ViewModel?Anything Android-specific (Context, View)

Chapter 15: Real Talk

You don't need ALL of this from day 1.

Day 1: Just Composables + simple ViewModel + StateFlow.
Day 30: Add Repository + Retrofit + Room.
Day 90: Add Hilt + Use Cases.
Day 180: Modularize.

flowchart LR
    Today[Today: You
Knows nothing] --> M1[Month 1
MVVM] M1 --> M3[Month 3
Repository] M3 --> M6[Month 6
Clean Arch] M6 --> Y1[1 Year
Modular] Y1 --> Pro[Senior Android Dev]

Every senior developer was a beginner once. Take it slow. Build things. Break things. Learn.


Final Words

Architecture is just organization. Don't be scared of fancy names like "MVVM" or "Clean Architecture." They all mean the same thing: separate your code so each part has one job.

Start small. Build a Counter. Then a Todo. Then a News app. Each project teaches you one new piece.

Ask for help. Stack Overflow, Reddit r/androiddev, the Android docs. Everyone was confused once.

You got this!

Want more like this?

Check out my interview prep with 193+ questions covering Java, Kotlin, Android, DSA & Architecture — with English & Hindi answers.

Interview Prep More Posts