Simple Notes App with Jetpack Compose, Room & Hilt

Software Developer with over 5 years of experience building high-quality web, mobile, and Android applications. Proficient in JavaScript, TypeScript, Flutter, React, Node.js, and Android development using Kotlin, with a strong background in designing scalable backend services using microservices and event-driven architectures. Experienced in building resilient APIs, asynchronous workflows, and distributed systems that power dynamic user interfaces across platforms. Skilled in solving complex problems, optimizing application performance, and writing clean, maintainable code. Hands-on experience with Docker, Kubernetes, and AWS for containerization, orchestration, and cloud-native deployments. Passionate about turning technical challenges into smart, scalable, and production-ready solutions. As an active competitive programmer, I’ve solved 1,000+ LeetCode problems and rank in the Top 2.22% globally (Knight badge), demonstrating strong expertise in algorithms, data structures, and dynamic programming. I thrive on applying algorithmic thinking to build scalable, real-world systems that create lasting impact.
This small Android app demonstrates a clean, modern stack:Jetpack Compose for UI, Room for local persistence, and Hilt for dependency injection.The code is compact and organized into Application, data(Room), repository, ViewModel, and Compose UI layers- perfect as a learning example or starting point for a producition - readt notes app.
GitRepo : https://github.com/oganaa2472/notes_app/
What you’ll learn
How the app is structured (files & MVVM architechture)
How Room, DAO and database are defined and used
How Hilt provides dependencies
How the VIewModel and repository connect the UI to the data layer
How a simple compose UI collects user input and displays notes
Project Overview
This repository contains a single app module (app) implementing a basic notes application. Major files to inspect:
MyApp.kt — Hilt-enabled Application class
MainActivity.kt — Compose host and entry point
Note.kt — Room entity for a note
NoteDao.kt — DAO with database operations
NoteDatabase.kt — Room database singleton
AppModule.kt — Hilt module to provide Room and DAO
NoteRepository.kt — Data abstraction over DAO
NoteViewModel.kt — Exposes flows and actions to UI
NoteScreen.kt & NoteItem.kt — Compose UI for creating and listing notes
This layering keeps UI, business logic, and persistence concerns separated and testable.
Files & key snippets explained
1) Application entry: MyApp.kt
The application is annotated with Hilt to enable dependency injection across the app.
@HiltAndroidApp
class MyApp: Application()
This wire-up allows Hilt to inject dependencies into Activities, ViewModels, and other components.
2) Main activity: MainActivity.kt
A minimal Compose host that applies an app theme and loads NoteScreen.
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent { NoteAppTheme { NoteScreen() } }
}
}
@AndroidEntryPoint allows Hilt to inject dependencies (for example ViewModels using Hilt).
3) Data model: Note.kt
A straightforward Room entity representing a note.
@Entity(tableName = "Notes")
data class Note(
@PrimaryKey(autoGenerate = true) val id: Int = 0,
val title: String,
val content: String,
val timestamp: Long
)
The timestamp is used for ordering notes by recency.
4) DAO: NoteDao.kt
A DAO using Kotlin Flow to observe note lists:
@Dao
interface NoteDao {
@Insert suspend fun insert(note: Note)
@Query("Select * from notes order by timestamp desc")
fun getAllNotes(): Flow<List<Note>>
}
Using Flow<List<Note>> makes UI reactive: updates are observed automatically.
Note: the SQL uses notes in lowercase while the entity tableName is Notes; Room is usually case-insensitive for table name mapping but it's good practice to keep the names consistent (e.g., both notes).
5) Database singleton: NoteDatabase.kt
A classic double-checked, synchronized singleton to create the Room database:
@Database(entities = [Note::class], version = 1, exportSchema = false)
abstract class NoteDatabase: RoomDatabase() {
abstract fun noteDao(): NoteDao
companion object {
@Volatile private var INSTANCE: NoteDatabase? = null
fun getDatabase(context: Context): NoteDatabase {
return INSTANCE ?: synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
NoteDatabase::class.java,
"notes_database"
).build()
INSTANCE = instance
instance
}
}
}
}
This provides a thread-safe single database instance across the app.
6) Hilt module: AppModule.kt
Provides the database and DAO as singletons:
@Module
@InstallIn(SingletonComponent::class)
object AppModule {
@Provides @Singleton fun provideNotDatabase(@ApplicationContext context: Context): NoteDatabase =
NoteDatabase.getDatabase(context)
@Provides @Singleton fun provideNoteDao(noteDatabase: NoteDatabase) = noteDatabase.noteDao()
}
Because these are singletons, they are shared across the app lifetime and can be injected into repositories and ViewModels.
7) Repository: NoteRepository.kt
A thin repository that wraps DAO calls. This isolates the rest of the app from direct DAO usage:
class NoteRepository @Inject constructor(private val noteDoa: NoteDao) {
suspend fun insertNote(note: Note) { noteDoa.insert(note) }
val allNotesInDb: Flow<List<Note>> = noteDoa.getAllNotes()
}
Note: a small typo noteDoa — consider renaming to noteDao for clarity.
8) ViewModel: NoteViewModel.kt
Exposes the Flow of notes to the UI and a method to insert notes:
@HiltViewModel
class NoteViewModel @Inject constructor(private val repo: NoteRepository): ViewModel() {
val allNotes: Flow<List<Note>> = repo.allNotesInDb
fun insert(note: Note) = viewModelScope.launch { repo.insertNote(note) }
}
viewModelScope ensures DB operations run off the main thread.
9) Compose UI: NoteScreen.kt & NoteItem.kt
The NoteScreen collects title/content via TextFields, inserts a new Note on Button click, and lists notes with LazyColumn:
val notes by viewModel.allNotes.collectAsState(emptyList())
var title by remember { mutableStateOf("") }
var content by remember { mutableStateOf("") }
Button(onClick = {
viewModel.insert(Note(0, title, content, System.currentTimeMillis()))
title = ""
content = ""
}) { Text("Add note") }
LazyColumn { items(notes) { note -> NoteItem(note) } }
And NoteItem renders a card with title and content:
@Composable
fun NoteItem(note: Note) {
Card(modifier = Modifier.fillMaxWidth().padding(8.dp)) {
Column(modifier = Modifier.padding(16.dp)) {
Text(note.title, style = MaterialTheme.typography.titleMedium)
Text(note.content, style = MaterialTheme.typography.bodyMedium)
}
}
}
This Compose UI is compact and idiomatic: state hoisted into local remember variables, collectAsState to subscribe to the Flow, and a LazyColumn for performance.
Design choices & rationale
Room + Flow: keeps UI reactive and avoids manual refresh logic.
Hilt: simple DI for singletons (DB/DAO) and ViewModel injection.
Repository layer: separates data-source details from ViewModel (helps testing and future extension to remote sources).
Compose UI: minimal, clean, and modern Android UI toolkit.
Small issues and recommended improvements
Table name consistency:
@Entity(tableName = "Notes")vs DAO query"Select * from notes ...". Use consistent casing (e.g.,notes) to avoid confusion.DAO query ordering: good use of
timestamp desc, but consider storingupdatedAtvscreatedAtto manage edits.Deletions/updates: currently only insertion and listing are implemented. Add
@Updateand@Deletemethods inNoteDaoand corresponding UI actions.Error handling & validation: validate empty title/content, and show user feedback (snackbar/toast) on success/failure.
Migration strategy: the database uses version 1 with no migration. For production, either add migrations or use
fallbackToDestructiveMigration()carefully in dev only.Tests: add unit tests for repository and ViewModel, and instrumented tests for DAO using an in-memory Room database.
How to run locally
From project root (macOS / zsh):
./gradlew assembleDebug
# install on a connected device/emulator:
./gradlew installDebug
Or open the project in Android Studio and run the app configuration.
Conclusion
This NoteApp is a compact, practical demonstration of modern Android development: Jetpack Compose for a declarative UI, Room for reliable local persistence, and Hilt for simple, testable dependency injection. The project shows how a small, well-layered architecture keeps concerns separated—UI, ViewModel, repository, and data—making the app easy to reason about, extend, and test.



