Android Architecture MVVM Room DB & Hilt DI

Zaheer Ahmad 4 min read min read
Python
Android Architecture MVVM Room DB & Hilt DI

Introduction

Modern Android apps are no longer built with just Activities and Fragments. Instead, they follow structured architectural patterns that make apps scalable, testable, and maintainable. One of the most popular and industry-recommended approaches is MVVM (Model-View-ViewModel) combined with Room Database for local storage and Hilt Dependency Injection for managing dependencies.

In this tutorial, you’ll learn Android Architecture: MVVM, Room DB & Hilt DI in a practical, real-world way tailored for Pakistani students. Whether you’re building a student management app in Lahore or an expense tracker in Karachi using PKR, these concepts will help you write clean and professional Android code.

Learning this stack is important because:

  • It’s widely used in real-world Android jobs
  • It improves app performance and maintainability
  • It prepares you for modern Android development practices

Prerequisites

Before starting this tutorial, you should have:

  • Basic knowledge of Kotlin programming
  • Understanding of Android Studio
  • Familiarity with Activities & Fragments
  • Basic idea of RecyclerView
  • Some exposure to Coroutines (helpful but not mandatory)

Core Concepts & Explanation

MVVM Architecture in Android

MVVM stands for:

  • Model → Data layer (Room, APIs)
  • View → UI layer (Activity/Fragment)
  • ViewModel → Connects View & Model

Example Flow:

  • Ahmad opens an expense app
  • View (Activity) asks ViewModel for data
  • ViewModel fetches from Repository
  • Repository gets data from Room DB or API

Benefits:

  • Separation of concerns
  • Lifecycle-aware UI updates
  • Easy testing

Room Database in Android

Room is an abstraction over SQLite that makes database handling easier.

Key Components:

  • Entity → Table
  • DAO → Queries
  • Database → Main database holder

Example:

If Fatima wants to store student data:

  • Entity = Student table
  • DAO = Insert, Delete, Fetch queries

Hilt Dependency Injection

Hilt simplifies dependency injection in Android apps.

Why Use Hilt?

  • Reduces boilerplate code
  • Automatically provides dependencies
  • Improves scalability

Example:

Instead of manually creating Repository objects, Hilt injects them automatically.


Repository Pattern (Glue Between Layers)

Repository acts as a mediator between:

  • ViewModel
  • Data sources (Room + API)

Why Important?

  • Centralizes data logic
  • Allows switching between local DB and API easily


Practical Code Examples

Example 1: Basic MVVM + Room Setup

@Entity(tableName = "students")
data class Student(
    @PrimaryKey(autoGenerate = true) val id: Int = 0,
    val name: String,
    val city: String
)

Explanation:

  • @Entity → Creates a table named "students"
  • id → Primary key, auto-generated
  • name, city → Columns

@Dao
interface StudentDao {

    @Insert
    suspend fun insertStudent(student: Student)

    @Query("SELECT * FROM students")
    fun getAllStudents(): LiveData<List<Student>>
}

Explanation:

  • @Dao → Data Access Object
  • insertStudent() → Inserts data into DB
  • getAllStudents() → Fetches all records
  • LiveData → Updates UI automatically

@Database(entities = [Student::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
    abstract fun studentDao(): StudentDao
}

Explanation:

  • @Database → Defines DB
  • entities → Tables included
  • studentDao() → Provides DAO

class StudentRepository(private val dao: StudentDao) {

    val students = dao.getAllStudents()

    suspend fun insert(student: Student) {
        dao.insertStudent(student)
    }
}

Explanation:

  • Repository wraps DAO
  • Exposes data to ViewModel
  • Handles logic centrally

class StudentViewModel(private val repo: StudentRepository) : ViewModel() {

    val students = repo.students

    fun addStudent(student: Student) {
        viewModelScope.launch {
            repo.insert(student)
        }
    }
}

Explanation:

  • ViewModel holds UI data
  • viewModelScope.launch → Runs coroutine
  • Prevents UI blocking

Example 2: Real-World Application (Expense Tracker in PKR)

@Entity(tableName = "expenses")
data class Expense(
    @PrimaryKey(autoGenerate = true) val id: Int = 0,
    val title: String,
    val amount: Double,
    val city: String
)

Explanation:

  • Stores expense data
  • Example: "Food", 500 PKR, Islamabad

@Dao
interface ExpenseDao {

    @Insert
    suspend fun addExpense(expense: Expense)

    @Query("SELECT * FROM expenses ORDER BY id DESC")
    fun getExpenses(): Flow<List<Expense>>
}

Explanation:

  • Uses Flow instead of LiveData
  • Flow supports coroutines better
  • Sorted results

@HiltViewModel
class ExpenseViewModel @Inject constructor(
    private val repository: ExpenseRepository
) : ViewModel() {

    val expenses = repository.expenses

    fun addExpense(expense: Expense) {
        viewModelScope.launch {
            repository.insert(expense)
        }
    }
}

Explanation:

  • @HiltViewModel → Enables DI
  • @Inject constructor → Hilt provides repository
  • Clean and scalable


Common Mistakes & How to Avoid Them

Mistake 1: Doing Database Work on Main Thread

❌ Wrong:

dao.insertStudent(student)

✔️ Correct:

viewModelScope.launch {
    dao.insertStudent(student)
}

Why?

  • Main thread blocking causes app crashes (ANR)
  • Always use coroutines

Mistake 2: Skipping Repository Layer

❌ Direct DAO usage in ViewModel

✔️ Use Repository:

class Repo(private val dao: Dao)

Why?

  • Keeps code clean
  • Easier to scale (add API later)

Mistake 3: Not Using Hilt Properly

❌ Manually creating dependencies

✔️ Use:

@Inject constructor()

Why?

  • Reduces boilerplate
  • Better lifecycle handling


Practice Exercises

Exercise 1: Student Manager App

Problem:
Create an app where Ali can add students from Karachi and display them.

Solution:

  • Create Entity (Student)
  • DAO with insert + fetch
  • ViewModel to handle UI
  • Display in RecyclerView

Exercise 2: Expense Calculator

Problem:
Fatima wants to track daily expenses in PKR.

Solution:

  • Create Expense Entity
  • Use Flow for updates
  • Display total expense dynamically

Frequently Asked Questions

What is MVVM in Android?

MVVM is an architecture pattern that separates UI, data, and business logic. It improves maintainability and makes apps easier to test and scale.

How do I use Room database in Android?

You define Entities, DAOs, and a Database class. Room then handles SQLite operations automatically with less boilerplate.

What is Hilt dependency injection?

Hilt is a library that automatically provides dependencies like repositories and databases, reducing manual object creation.

Should I use LiveData or Flow?

Use Flow for modern apps with coroutines. LiveData is still useful but Flow is more powerful and flexible.

Is MVVM required for small apps?

Not required, but recommended. Even small apps benefit from clean architecture and easier future scaling.


Summary & Key Takeaways

  • MVVM separates UI, logic, and data effectively
  • Room simplifies local database operations
  • Hilt automates dependency injection
  • Repository pattern improves code structure
  • Coroutines + Flow provide modern async handling
  • Clean architecture is essential for real-world apps

To continue your Android learning journey on theiqra.edu.pk, explore:

  • Learn UI development with Jetpack Compose Tutorial
  • Strengthen your programming basics with Kotlin Tutorial
  • Build APIs with Retrofit Android Tutorial
  • Explore advanced architecture in Clean Architecture Android Guide

These tutorials will help you become a professional Android developer ready for jobs in Pakistan and globally 🚀

Practice the code examples from this tutorial
Open Compiler
Share this tutorial:

Test Your Python Knowledge!

Finished reading? Take a quick quiz to see how much you've learned from this tutorial.

Start Python Quiz

About Zaheer Ahmad