Free tutorials & notes in Hindi & English · Clean code examples 🧰 Free PDF & Image Tools →
🏆 Complete App & Publish  ·  Lesson 40

Project: Student Diary App (Plan & Setup)

The app you will build

Student Diary is a complete homework and notes app. It uses almost everything from this course in one real project.

9:41▮▮ 85%☰Student Diary⋮AllPendingDone☐ Maths homework ★Maths · Due 24 Nov☐ Physics revision ☆Physics☑ Buy notebook ☆General+📝Notes📚Subjects📊Progress9:41▮▮ 85%←Note⋮Title: Maths homeworkSubject: Maths ▾LowMediumHighRemind me⏰ 24 Nov 2026, 09:00 AMSAVE NOTE9:41▮▮ 85%☰Progress⋮🛍️6Total🛍️2Done33% completed🖼️Tip of the day📝Notes📚Subjects📊Progress
Notes list · editor · progress screen

Features → lessons

FeatureFrom lesson
Drawer + toolbar + bottom navigation + FABNavigation Drawer, Bottom Navigation, Menus
All / Pending / Done tabsTabLayout + ViewPager2
List with swipe-to-delete + Undo, searchRecyclerView (advanced), Snackbar, Toolbar
Form with dropdown, chips, switch, pickers, photoInputs, Pickers, Camera & Gallery
Offline storageRoom
MVVM with Flow + LiveDataMVVM, Coroutines
Reminders when the app is closedWorkManager, Notifications, Permissions
Tip of the day from the internetRetrofit, Glide
Dark mode, name, settingsMaterial Theme, SharedPreferences
Online helpWebView

Architecture

UIMainActivity + 7 fragments, View Binding, adaptersNoteViewModel (shared, activityViewModels)LiveData for every screen, viewModelScopeNoteRepository · TipsApi · SettingsStoreone place per data sourceRoom (SQLite) · codingeasily.in JSON · SharedPreferencesstorage + networkWorkManager + NotificationHelperbackground reminders
MVVM with a single Activity
notesFragmenttabseditorFragmentadd / editsubjectsFragmentgridsubjectNotesFragmentfiltered liststats · settings · help
Destinations in nav_graph.xml

Project structure

📁 StudentDiary/
├─ settings.gradle.kts · build.gradle.kts · gradle/libs.versions.toml
└─ 📁 app/src/main/
   ├─ AndroidManifest.xml
   ├─ 📁 java/com/codingeasily/studentdiary/
   │  ├─ StudentDiaryApp.kt          theme + notification channel
   │  ├─ MainActivity.kt             drawer + bottom nav + FAB
   │  ├─ 📁 data/      Note.kt  NoteDao.kt  AppDatabase.kt  NoteRepository.kt  SettingsStore.kt
   │  ├─ 📁 network/   Tip.kt  TipsApi.kt
   │  ├─ 📁 reminder/  ReminderWorker.kt  ReminderScheduler.kt  NotificationHelper.kt
   │  ├─ 📁 ui/        NoteViewModel.kt
   │  │  ├─ 📁 notes/    NotesFragment.kt  NoteListFragment.kt  NoteAdapter.kt
   │  │  ├─ 📁 editor/   AddEditNoteFragment.kt
   │  │  ├─ 📁 subjects/ SubjectsFragment.kt  SubjectAdapter.kt
   │  │  ├─ 📁 stats/  settings/  help/
   │  └─ 📁 util/      DateUtils.kt  ImageStore.kt
   └─ 📁 res/  layout (12) · menu (4) · navigation · values · values-night · drawable (20) · mipmap
Every file in the download ZIP

Step 1: Gradle setup

Create a new project (Empty Views Activity, package com.codingeasily.studentdiary, minimum SDK 26), then replace the version catalog and the app build file:

TOML – gradle/libs.versions.toml
[versions]
agp = "8.7.3"
kotlin = "2.0.21"
ksp = "2.0.21-1.0.28"
coreKtx = "1.15.0"
appcompat = "1.7.0"
material = "1.12.0"
constraintlayout = "2.2.0"
recyclerview = "1.4.0"
viewpager2 = "1.1.0"
activityKtx = "1.9.3"
fragmentKtx = "1.8.5"
lifecycle = "2.8.7"
navigation = "2.8.5"
room = "2.6.1"
work = "2.10.0"
coroutines = "1.9.0"
retrofit = "2.11.0"
glide = "4.16.0"
junit = "4.13.2"

[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
material = { group = "com.google.android.material", name = "material", version.ref = "material" }
androidx-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" }
androidx-recyclerview = { group = "androidx.recyclerview", name = "recyclerview", version.ref = "recyclerview" }
androidx-viewpager2 = { group = "androidx.viewpager2", name = "viewpager2", version.ref = "viewpager2" }
androidx-activity-ktx = { group = "androidx.activity", name = "activity-ktx", version.ref = "activityKtx" }
androidx-fragment-ktx = { group = "androidx.fragment", name = "fragment-ktx", version.ref = "fragmentKtx" }
androidx-lifecycle-viewmodel-ktx = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-ktx", version.ref = "lifecycle" }
androidx-lifecycle-livedata-ktx = { group = "androidx.lifecycle", name = "lifecycle-livedata-ktx", version.ref = "lifecycle" }
androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycle" }
androidx-navigation-fragment-ktx = { group = "androidx.navigation", name = "navigation-fragment-ktx", version.ref = "navigation" }
androidx-navigation-ui-ktx = { group = "androidx.navigation", name = "navigation-ui-ktx", version.ref = "navigation" }
androidx-room-runtime = { group = "androidx.room", name = "room-runtime", version.ref = "room" }
androidx-room-ktx = { group = "androidx.room", name = "room-ktx", version.ref = "room" }
androidx-room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "room" }
androidx-work-runtime-ktx = { group = "androidx.work", name = "work-runtime-ktx", version.ref = "work" }
kotlinx-coroutines-android = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version.ref = "coroutines" }
retrofit = { group = "com.squareup.retrofit2", name = "retrofit", version.ref = "retrofit" }
retrofit-converter-gson = { group = "com.squareup.retrofit2", name = "converter-gson", version.ref = "retrofit" }
glide = { group = "com.github.bumptech.glide", name = "glide", version.ref = "glide" }
junit = { group = "junit", name = "junit", version.ref = "junit" }

[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
Kotlin DSL – app/build.gradle.kts
plugins {
    alias(libs.plugins.android.application)
    alias(libs.plugins.kotlin.android)
    alias(libs.plugins.ksp)            // generates Room code
}

android {
    namespace = "com.codingeasily.studentdiary"
    compileSdk = 35

    defaultConfig {
        applicationId = "com.codingeasily.studentdiary"
        minSdk = 26
        targetSdk = 35
        versionCode = 1
        versionName = "1.0"
        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            isMinifyEnabled = true
            isShrinkResources = true
            proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
        }
    }
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_17
        targetCompatibility = JavaVersion.VERSION_17
    }
    kotlinOptions {
        jvmTarget = "17"
    }
    buildFeatures {
        viewBinding = true             // generates ActivityMainBinding, FragmentNotesBinding, ...
    }
}

dependencies {
    implementation(libs.androidx.core.ktx)
    implementation(libs.androidx.appcompat)
    implementation(libs.material)
    implementation(libs.androidx.constraintlayout)
    implementation(libs.androidx.recyclerview)
    implementation(libs.androidx.viewpager2)
    implementation(libs.androidx.activity.ktx)
    implementation(libs.androidx.fragment.ktx)
    implementation(libs.androidx.lifecycle.viewmodel.ktx)
    implementation(libs.androidx.lifecycle.livedata.ktx)
    implementation(libs.androidx.lifecycle.runtime.ktx)
    implementation(libs.androidx.navigation.fragment.ktx)
    implementation(libs.androidx.navigation.ui.ktx)
    implementation(libs.androidx.room.runtime)
    implementation(libs.androidx.room.ktx)
    ksp(libs.androidx.room.compiler)
    implementation(libs.androidx.work.runtime.ktx)
    implementation(libs.kotlinx.coroutines.android)
    implementation(libs.retrofit)
    implementation(libs.retrofit.converter.gson)
    implementation(libs.glide)
    testImplementation(libs.junit)
}

Step 2: Manifest and Application

XML – AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

    <!-- Needed for the Help screen (WebView) and the "Tip of the day" (Retrofit) -->
    <uses-permission android:name="android.permission.INTERNET" />
    <!-- Android 13+ asks the user before showing notifications -->
    <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />

    <application
        android:name=".StudentDiaryApp"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/Theme.StudentDiary">

        <activity
            android:name=".MainActivity"
            android:exported="true"
            android:windowSoftInputMode="adjustResize">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>
Kotlin – StudentDiaryApp.kt
package com.codingeasily.studentdiary

import android.app.Application
import com.codingeasily.studentdiary.data.SettingsStore
import com.codingeasily.studentdiary.reminder.NotificationHelper

/**
 * Runs once when the app process starts (declared in AndroidManifest.xml).
 * We apply the saved light/dark theme and create the notification channel here.
 */
class StudentDiaryApp : Application() {
    override fun onCreate() {
        super.onCreate()
        SettingsStore(this).applyTheme()
        NotificationHelper.createChannel(this)
    }
}

Step 3: Data layer

Kotlin – data/Note.kt
package com.codingeasily.studentdiary.data

import androidx.room.Entity
import androidx.room.PrimaryKey

/** One row of the "notes" table. */
@Entity(tableName = "notes")
data class Note(
    @PrimaryKey(autoGenerate = true) val id: Long = 0,
    val title: String,
    val content: String = "",
    val subject: String = "General",
    val priority: Int = PRIORITY_MEDIUM,      // 0 = low, 1 = medium, 2 = high
    val dueAt: Long? = null,                  // reminder time in millis, null = no reminder
    val isDone: Boolean = false,
    val isFavorite: Boolean = false,
    val imagePath: String? = null,            // photo copied into app storage
    val createdAt: Long = System.currentTimeMillis()
) {
    companion object {
        const val PRIORITY_LOW = 0
        const val PRIORITY_MEDIUM = 1
        const val PRIORITY_HIGH = 2
    }
}

/** Result of a GROUP BY query: how many notes each subject has. */
data class SubjectCount(val subject: String, val total: Int)
Kotlin – data/NoteDao.kt
package com.codingeasily.studentdiary.data

import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query
import androidx.room.Update
import kotlinx.coroutines.flow.Flow

/** All SQL for the notes table lives here. Room generates the implementation. */
@Dao
interface NoteDao {

    // Flow = the UI is updated automatically whenever the table changes
    @Query("SELECT * FROM notes ORDER BY isDone ASC, priority DESC, createdAt DESC")
    fun getAll(): Flow<List<Note>>

    @Query("SELECT * FROM notes WHERE isDone = :done ORDER BY priority DESC, createdAt DESC")
    fun getByDone(done: Boolean): Flow<List<Note>>

    @Query("SELECT * FROM notes WHERE isFavorite = 1 ORDER BY createdAt DESC")
    fun getFavorites(): Flow<List<Note>>

    @Query("SELECT * FROM notes WHERE subject = :subject ORDER BY createdAt DESC")
    fun getBySubject(subject: String): Flow<List<Note>>

    @Query("SELECT * FROM notes WHERE title LIKE '%' || :q || '%' OR content LIKE '%' || :q || '%' ORDER BY createdAt DESC")
    fun search(q: String): Flow<List<Note>>

    @Query("SELECT subject, COUNT(*) AS total FROM notes GROUP BY subject ORDER BY total DESC")
    fun subjectCounts(): Flow<List<SubjectCount>>

    @Query("SELECT COUNT(*) FROM notes")
    fun countAll(): Flow<Int>

    @Query("SELECT COUNT(*) FROM notes WHERE isDone = 1")
    fun countDone(): Flow<Int>

    @Query("SELECT * FROM notes WHERE id = :id")
    suspend fun getById(id: Long): Note?

    @Insert
    suspend fun insert(note: Note): Long

    @Update
    suspend fun update(note: Note)

    @Delete
    suspend fun delete(note: Note)

    @Query("DELETE FROM notes WHERE isDone = 1")
    suspend fun deleteDone(): Int
}
Kotlin – data/AppDatabase.kt
package com.codingeasily.studentdiary.data

import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase

@Database(entities = [Note::class], version = 1, exportSchema = false)
abstract class AppDatabase : RoomDatabase() {

    abstract fun noteDao(): NoteDao

    companion object {
        @Volatile private var INSTANCE: AppDatabase? = null

        /** Singleton: the whole app shares one database connection. */
        fun get(context: Context): AppDatabase =
            INSTANCE ?: synchronized(this) {
                INSTANCE ?: Room.databaseBuilder(
                    context.applicationContext,
                    AppDatabase::class.java,
                    "student_diary.db"
                ).build().also { INSTANCE = it }
            }
    }
}
Kotlin – data/NoteRepository.kt
package com.codingeasily.studentdiary.data

import kotlinx.coroutines.flow.Flow

/**
 * The single source of truth for notes. ViewModels talk to the repository,
 * never directly to the DAO. Later you could add a network source here.
 */
class NoteRepository(private val dao: NoteDao) {
    val allNotes: Flow<List<Note>> = dao.getAll()
    val pendingNotes: Flow<List<Note>> = dao.getByDone(false)
    val doneNotes: Flow<List<Note>> = dao.getByDone(true)
    val favorites: Flow<List<Note>> = dao.getFavorites()
    val subjectCounts: Flow<List<SubjectCount>> = dao.subjectCounts()
    val countAll: Flow<Int> = dao.countAll()
    val countDone: Flow<Int> = dao.countDone()

    fun bySubject(subject: String) = dao.getBySubject(subject)
    fun search(q: String) = dao.search(q)

    suspend fun get(id: Long) = dao.getById(id)
    suspend fun save(note: Note): Long =
        if (note.id == 0L) dao.insert(note) else { dao.update(note); note.id }
    suspend fun delete(note: Note) = dao.delete(note)
    suspend fun deleteDone() = dao.deleteDone()
}
Kotlin – util/DateUtils.kt
package com.codingeasily.studentdiary.util

import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale

object DateUtils {
    private val full = SimpleDateFormat("dd MMM yyyy, hh:mm a", Locale.getDefault())
    private val short = SimpleDateFormat("dd MMM", Locale.getDefault())

    fun format(millis: Long): String = full.format(Date(millis))
    fun formatShort(millis: Long): String = short.format(Date(millis))
}

Step 4: Shared ViewModel

Kotlin – ui/NoteViewModel.kt
package com.codingeasily.studentdiary.ui

import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.asLiveData
import androidx.lifecycle.viewModelScope
import com.codingeasily.studentdiary.data.AppDatabase
import com.codingeasily.studentdiary.data.Note
import com.codingeasily.studentdiary.data.NoteRepository
import com.codingeasily.studentdiary.data.SubjectCount
import com.codingeasily.studentdiary.network.Tip
import com.codingeasily.studentdiary.network.TipsApi
import com.codingeasily.studentdiary.reminder.ReminderScheduler
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch

/** Which notes a list screen shows. */
enum class NoteFilter { ALL, PENDING, DONE, FAVORITES, SUBJECT }

/** State of the "Tip of the day" card. */
sealed class TipState {
    data object Loading : TipState()
    data class Success(val tip: Tip) : TipState()
    data class Error(val message: String) : TipState()
}

/**
 * Shared by all screens (activityViewModels). Survives screen rotation.
 * The UI observes LiveData; all database work runs in viewModelScope.
 */
class NoteViewModel(application: Application) : AndroidViewModel(application) {

    private val repo = NoteRepository(AppDatabase.get(application).noteDao())

    // ---------- lists ----------
    private val searchQuery = MutableStateFlow("")

    fun setSearchQuery(q: String) { searchQuery.value = q.trim() }

    @OptIn(ExperimentalCoroutinesApi::class)
    fun notes(filter: NoteFilter, subject: String? = null): LiveData<List<Note>> {
        val source: Flow<List<Note>> = when (filter) {
            NoteFilter.ALL -> repo.allNotes
            NoteFilter.PENDING -> repo.pendingNotes
            NoteFilter.DONE -> repo.doneNotes
            NoteFilter.FAVORITES -> repo.favorites
            NoteFilter.SUBJECT -> repo.bySubject(subject ?: "")
        }
        return searchQuery.flatMapLatest { q ->
            if (q.isEmpty()) source
            else source.map { list ->
                list.filter { it.title.contains(q, ignoreCase = true) || it.content.contains(q, ignoreCase = true) }
            }
        }.asLiveData()
    }

    val subjectCounts: LiveData<List<SubjectCount>> = repo.subjectCounts.asLiveData()
    val countAll: LiveData<Int> = repo.countAll.asLiveData()
    val countDone: LiveData<Int> = repo.countDone.asLiveData()

    // ---------- single note ----------
    suspend fun getNote(id: Long): Note? = repo.get(id)

    fun save(note: Note, onSaved: (Long) -> Unit) {
        viewModelScope.launch {
            val id = repo.save(note)
            val due = note.dueAt
            if (due != null && !note.isDone) ReminderScheduler.schedule(getApplication(), id, due)
            else ReminderScheduler.cancel(getApplication(), id)
            onSaved(id)
        }
    }

    fun toggleDone(note: Note) = viewModelScope.launch {
        repo.save(note.copy(isDone = !note.isDone))
        if (!note.isDone) ReminderScheduler.cancel(getApplication(), note.id)
    }

    fun toggleFavorite(note: Note) = viewModelScope.launch {
        repo.save(note.copy(isFavorite = !note.isFavorite))
    }

    fun delete(note: Note) = viewModelScope.launch {
        repo.delete(note)
        ReminderScheduler.cancel(getApplication(), note.id)
    }

    /** Used by the "Undo" button of the Snackbar. */
    fun restore(note: Note) = viewModelScope.launch {
        repo.save(note.copy(id = 0))
    }

    fun deleteDone(onDone: (Int) -> Unit) = viewModelScope.launch {
        onDone(repo.deleteDone())
    }

    // ---------- tip of the day (Retrofit) ----------
    private val _tip = MutableLiveData<TipState>()
    val tip: LiveData<TipState> = _tip

    fun loadTip(force: Boolean = false) {
        if (!force && _tip.value is TipState.Success) return
        _tip.value = TipState.Loading
        viewModelScope.launch {
            _tip.value = try {
                val tips = TipsApi.service.getTips().tips
                if (tips.isEmpty()) TipState.Error("No tips yet")
                else TipState.Success(tips[(System.currentTimeMillis() / 86_400_000L % tips.size).toInt()])
            } catch (e: Exception) {
                TipState.Error(e.message ?: "Network error")
            }
        }
    }
}

Step 5: Notes list (tabs + RecyclerView)

XML – layout/fragment_note_list.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:clipToPadding="false"
        android:paddingTop="8dp"
        android:paddingBottom="88dp"
        tools:listitem="@layout/item_note" />

    <LinearLayout
        android:id="@+id/emptyView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:gravity="center"
        android:orientation="vertical"
        android:visibility="gone">

        <ImageView
            android:layout_width="96dp"
            android:layout_height="96dp"
            android:alpha="0.4"
            android:contentDescription="@null"
            android:src="@drawable/ic_note" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="12dp"
            android:text="@string/empty_notes"
            android:textAppearance="?attr/textAppearanceBodyLarge" />
    </LinearLayout>
</FrameLayout>
XML – layout/item_note.xml
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginHorizontal="12dp"
    android:layout_marginVertical="6dp"
    app:cardCornerRadius="14dp"
    app:cardElevation="2dp">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:minHeight="76dp">

        <!-- coloured strip shows priority -->
        <View
            android:id="@+id/priorityStrip"
            android:layout_width="6dp"
            android:layout_height="0dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <com.google.android.material.checkbox.MaterialCheckBox
            android:id="@+id/cbDone"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="8dp"
            android:contentDescription="@string/mark_done"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintStart_toEndOf="@id/priorityStrip"
            app:layout_constraintTop_toTopOf="parent" />

        <TextView
            android:id="@+id/tvTitle"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginTop="12dp"
            android:ellipsize="end"
            android:maxLines="1"
            android:textAppearance="?attr/textAppearanceTitleMedium"
            app:layout_constraintEnd_toStartOf="@id/ivThumb"
            app:layout_constraintStart_toEndOf="@id/cbDone"
            app:layout_constraintTop_toTopOf="parent" />

        <TextView
            android:id="@+id/tvContent"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:ellipsize="end"
            android:maxLines="2"
            android:textAppearance="?attr/textAppearanceBodyMedium"
            app:layout_constraintEnd_toEndOf="@id/tvTitle"
            app:layout_constraintStart_toStartOf="@id/tvTitle"
            app:layout_constraintTop_toBottomOf="@id/tvTitle" />

        <TextView
            android:id="@+id/tvSubject"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="6dp"
            android:layout_marginBottom="12dp"
            android:background="@drawable/bg_chip"
            android:paddingHorizontal="10dp"
            android:paddingVertical="2dp"
            android:textAppearance="?attr/textAppearanceLabelMedium"
            android:textColor="@color/brand"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintStart_toStartOf="@id/tvTitle"
            app:layout_constraintTop_toBottomOf="@id/tvContent" />

        <TextView
            android:id="@+id/tvDue"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="8dp"
            android:drawablePadding="4dp"
            android:gravity="center_vertical"
            android:textAppearance="?attr/textAppearanceLabelMedium"
            app:drawableStartCompat="@drawable/ic_alarm"
            app:layout_constraintBaseline_toBaselineOf="@id/tvSubject"
            app:layout_constraintStart_toEndOf="@id/tvSubject" />

        <ImageView
            android:id="@+id/ivThumb"
            android:layout_width="52dp"
            android:layout_height="52dp"
            android:layout_marginEnd="4dp"
            android:contentDescription="@string/photo"
            android:scaleType="centerCrop"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toStartOf="@id/btnFavorite"
            app:layout_constraintTop_toTopOf="parent" />

        <ImageButton
            android:id="@+id/btnFavorite"
            android:layout_width="48dp"
            android:layout_height="48dp"
            android:background="?attr/selectableItemBackgroundBorderless"
            android:contentDescription="@string/favourite"
            android:src="@drawable/ic_star_outline"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:tint="@color/star" />
    </androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
Kotlin – ui/notes/NoteListFragment.kt
package com.codingeasily.studentdiary.ui.notes

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.os.bundleOf
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.codingeasily.studentdiary.R
import com.codingeasily.studentdiary.databinding.FragmentNoteListBinding
import com.codingeasily.studentdiary.ui.NoteFilter
import com.codingeasily.studentdiary.ui.NoteViewModel
import com.google.android.material.snackbar.Snackbar

/**
 * A reusable list screen. It is used:
 *  - inside the 3 tabs of NotesFragment (ALL / PENDING / DONE)
 *  - as the Favourites screen (drawer)
 *  - as the notes of one subject (from SubjectsFragment)
 */
class NoteListFragment : Fragment() {

    private var _binding: FragmentNoteListBinding? = null
    private val binding get() = _binding!!          // valid only between onCreateView and onDestroyView

    private val viewModel: NoteViewModel by activityViewModels()

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
        _binding = FragmentNoteListBinding.inflate(inflater, container, false)
        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        val filter = NoteFilter.valueOf(requireArguments().getString(ARG_FILTER, NoteFilter.ALL.name))
        val subject = arguments?.getString(ARG_SUBJECT)

        val adapter = NoteAdapter(
            onClick = { note ->
                findNavController().navigate(R.id.action_global_editor, bundleOf("noteId" to note.id))
            },
            onToggleDone = { viewModel.toggleDone(it) },
            onToggleFavorite = { viewModel.toggleFavorite(it) }
        )
        binding.recyclerView.layoutManager = LinearLayoutManager(requireContext())
        binding.recyclerView.adapter = adapter

        viewModel.notes(filter, subject).observe(viewLifecycleOwner) { list ->
            adapter.submitList(list)
            binding.emptyView.isVisible = list.isEmpty()
        }

        // Swipe left or right to delete, with UNDO
        val swipe = object : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT) {
            override fun onMove(rv: RecyclerView, vh: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder) = false
            override fun onSwiped(vh: RecyclerView.ViewHolder, direction: Int) {
                val position = vh.bindingAdapterPosition
                if (position == RecyclerView.NO_POSITION) return
                val note = adapter.currentList[position]
                viewModel.delete(note)
                Snackbar.make(binding.root, getString(R.string.note_deleted, note.title), Snackbar.LENGTH_LONG)
                    .setAction(R.string.undo) { viewModel.restore(note) }
                    .show()
            }
        }
        ItemTouchHelper(swipe).attachToRecyclerView(binding.recyclerView)
    }

    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null       // avoid memory leaks: the view is gone, the fragment may live on
    }

    companion object {
        const val ARG_FILTER = "filter"
        const val ARG_SUBJECT = "subject"

        fun newInstance(filter: NoteFilter) = NoteListFragment().apply {
            arguments = bundleOf(ARG_FILTER to filter.name)
        }
    }
}

The adapter (NoteAdapter.kt) and the tabs host (NotesFragment.kt) are shown in full in the RecyclerView advanced and TabLayout lessons, and the main screen in the Navigation Drawer lesson.

Next part

  1. 1Part 2: Add/Edit note screen with pickers, photo and reminders
  2. 2Subjects grid, Progress screen with Retrofit tip, Settings, Help
  3. 3Resources: strings, themes, icons — then test everything
  4. 4Release lesson: sign and publish on Google Play
Continue to Part 2
✅ Checkpoint

After this part you can run the app, see three empty tabs and the drawer. Temporarily insert a note in StudentDiaryApp.onCreate with a coroutine to see it in the list.

Frequently Asked Questions

Can I download the full Student Diary source code?
Yes. Use the download card on this page. Open the folder in Android Studio, let Gradle sync, and press Run.
Was the project compiled before publishing?
The Kotlin sources were syntax-checked and every view id, resource and class reference was verified with an automated checker. Our build server cannot download Android libraries, so please run the first Gradle sync in your own Android Studio; if Studio suggests a newer AGP or library version you can accept it.
← Android Tutorial (Kotlin)
🔗

Share this topic with a friend

यह topic किसी दोस्त को भेजें

Found it useful? Send it to a classmate learning the same thing.

अच्छा लगा? जो दोस्त यही सीख रहा है, उसे भेज दीजिए।

Next topicProject: Build All Screens Your progress: 40 / 43 (93%) · Lesson 41 / 43 Continue →

How was this lesson?

Your feedback helps us make the content better.

Write a review or suggestion (optional)

After approval, your name and city may appear publicly with your review. Please do not write your email or phone number.

▶ CodingEasily on YouTube

Learn with video lessons in easy Hindi

Featured video: RDBMS Part 3 – Class XI Commerce IT

▶ Subscribe – it’s free