Android 笔记 App2: 初始化项目

159 阅读6分钟

以下是基于推荐技术栈的 Android 笔记 App 初始化项目结构。我们将使用 Kotlin、Jetpack Compose、Room、Hilt 等技术栈。


1. 项目初始化

  1. 创建项目

    • 打开 Android Studio,选择 New Project
    • 选择 Empty Compose Activity 模板。
    • 设置项目名称(如 NotesApp),包名(如 com.example.notes),语言选择 Kotlin
    • 最低 API 级别选择 21(Android 5.0)。
  2. 配置 Gradle

    • build.gradle.kts 中添加必要的依赖项。

2. 项目结构

NotesApp/
├── app/
│   ├── src/
│   │   ├── main/
│   │   │   ├── java/com/example/notes/
│   │   │   │   ├── ui/                # UI 层(Compose)
│   │   │   │   ├── viewmodel/         # ViewModel
│   │   │   │   ├── repository/        # 数据仓库
│   │   │   │   ├── database/          # Room 数据库
│   │   │   │   ├── model/             # 数据模型
│   │   │   │   └── di/                # 依赖注入
│   │   │   └── res/                   # 资源文件
│   │   └── test/                      # 单元测试
│   └── build.gradle.kts               # 模块构建脚本
├── build.gradle.kts                   # 项目构建脚本
└── settings.gradle.kts                # 项目设置

3. 配置 Gradle 文件

build.gradle.kts(项目级)
plugins {
    alias(libs.plugins.gradle.versions)
    alias(libs.plugins.version.catalog.update)
    alias(libs.plugins.android.application) apply false
    alias(libs.plugins.kotlin.android) apply false
    alias(libs.plugins.kotlin.parcelize) apply false
    alias(libs.plugins.compose) apply false
}
build.gradle.kts(模块级)
plugins {
    alias(libs.plugins.android.application)
    alias(libs.plugins.kotlin.android)
    alias(libs.plugins.compose)
    alias(libs.plugins.ksp)
    alias(libs.plugins.hilt)
}

android {
    compileSdk = libs.versions.compileSdk.get().toInt()
    namespace = "com.example.jetnews"

    defaultConfig {
        applicationId = "com.nemo.notes"
        minSdk = libs.versions.minSdk.get().toInt()
        targetSdk = libs.versions.targetSdk.get().toInt()
        versionCode = 1
        versionName = "1.0"
        vectorDrawables.useSupportLibrary = true
        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
    }

    signingConfigs {
        // Important: change the keystore for a production deployment
        val userKeystore = File(System.getProperty("user.home"), ".android/debug.keystore")
        val localKeystore = rootProject.file("debug_2.keystore")
        val hasKeyInfo = userKeystore.exists()
        create("release") {
            storeFile = if (hasKeyInfo) userKeystore else localKeystore
            storePassword = if (hasKeyInfo) "android" else System.getenv("compose_store_password")
            keyAlias = if (hasKeyInfo) "androiddebugkey" else System.getenv("compose_key_alias")
            keyPassword = if (hasKeyInfo) "android" else System.getenv("compose_key_password")
        }
    }

    buildTypes {
        getByName("debug") {

        }

        getByName("release") {
            isMinifyEnabled = true
            signingConfig = signingConfigs.getByName("release")
            proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"),
                "proguard-rules.pro")
        }
    }

    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_17
        targetCompatibility = JavaVersion.VERSION_17
    }

    buildFeatures {
        compose = true
    }

    packaging.resources {
        // Multiple dependency bring these files in. Exclude them to enable
        // our test APK to build (has no effect on our AARs)
        excludes += "/META-INF/AL2.0"
        excludes += "/META-INF/LGPL2.1"
    }

    hilt {
        enableAggregatingTask = false
    }
}

kotlin {
    jvmToolchain(17)
}

dependencies {
    val composeBom = platform(libs.androidx.compose.bom)
    implementation(composeBom)
    androidTestImplementation(composeBom)

    implementation(libs.kotlin.stdlib)
    implementation(libs.kotlinx.coroutines.android)

    // Compose
    implementation(libs.androidx.compose.ui)
    implementation(libs.androidx.compose.animation)
    implementation(libs.androidx.compose.foundation.layout)
    implementation("com.google.android.material:material:1.10.0")
    implementation(libs.androidx.compose.material.iconsExtended)
    implementation(libs.androidx.compose.material3)
    implementation(libs.androidx.compose.materialWindow)
    implementation(libs.androidx.compose.runtime.livedata)
    implementation(libs.androidx.compose.ui.tooling.preview)
    implementation(libs.androidx.lifecycle.viewModelCompose)
    implementation(libs.androidx.lifecycle.runtime.compose)
    implementation(libs.androidx.navigation.compose)
    implementation(libs.androidx.activity.compose)

    // Core
    implementation(libs.androidx.appcompat)
    implementation(libs.androidx.activity.ktx)
    implementation(libs.androidx.core.ktx)
    implementation(libs.androidx.lifecycle.runtime)

    implementation(libs.androidx.glance)
    implementation(libs.androidx.glance.appwidget)
    implementation(libs.androidx.glance.material3)

    implementation(libs.androidx.lifecycle.viewmodel.ktx)
    implementation(libs.androidx.lifecycle.viewmodel.savedstate)
    implementation(libs.androidx.lifecycle.livedata.ktx)
    implementation(libs.androidx.window)

    // Room
    implementation(libs.androidx.room.runtime)
    implementation(libs.androidx.room.ktx)
    ksp(libs.androidx.room.compiler)

    // Hilt
    implementation(libs.hilt.android)
    implementation(libs.androidx.hilt.navigation.compose)
    ksp(libs.hilt.compiler)

    implementation("com.squareup:javapoet:1.13.0")

    // Testing
    androidTestImplementation(libs.junit)
    androidTestImplementation(libs.androidx.test.core)
    androidTestImplementation(libs.androidx.test.runner)
    androidTestImplementation(libs.androidx.test.espresso.core)
    androidTestImplementation(libs.androidx.test.rules)
    androidTestImplementation(libs.androidx.test.ext.junit)
    androidTestImplementation(libs.kotlinx.coroutines.test)
    androidTestImplementation(libs.androidx.compose.ui.test)
    androidTestImplementation(libs.androidx.compose.ui.test.junit4)

    // Debug
    debugImplementation(libs.androidx.compose.ui.test.manifest)
    debugImplementation(libs.androidx.compose.ui.tooling)
}
libs.versions.toml(模块级)
#####
# This file is duplicated to individual samples from the global scripts/libs.versions.toml
# Do not add a dependency to an individual sample, edit the global version instead.
#####
[versions]
accompanist = "0.37.0"
androidGradlePlugin = "8.7.3"
androidx-activity-compose = "1.9.3"
androidx-appcompat = "1.7.0"
androidx-compose-bom = "2024.12.01"
androidx-constraintlayout = "1.1.0"
androidx-core-splashscreen = "1.0.1"
androidx-corektx = "1.15.0"
androidx-glance = "1.1.1"
androidx-lifecycle = "2.8.2"
androidx-lifecycle-compose = "2.8.7"
androidx-lifecycle-runtime-compose = "2.8.7"
androidx-navigation = "2.8.5"
androidx-palette = "1.0.0"
androidx-test = "1.6.1"
androidx-test-espresso = "3.6.1"
androidx-test-ext-junit = "1.2.1"
androidx-test-ext-truth = "1.6.0"
androidx-tv-foundation = "1.0.0-alpha11"
androidx-tv-material = "1.0.0"
androidx-wear-compose = "1.4.0"
androidx-window = "1.3.0"
androidxHiltNavigationCompose = "1.2.0"
androix-test-uiautomator = "2.3.0"
coil = "2.7.0"
# @keep
compileSdk = "35"
coroutines = "1.9.0"
google-maps = "18.2.0"
gradle-versions = "0.51.0"
hilt = "2.53.1"
hiltExt = "1.2.0"
horologist = "0.6.22"
# @pin When updating to AGP 7.4.0-alpha10 and up we can update this https://developer.android.com/studio/write/java8-support#library-desugaring-versions
jdkDesugar = "1.2.2"
junit = "4.13.2"
kotlin = "2.1.0"
kotlinx-serialization-json = "1.7.3"
kotlinx_immutable = "0.3.8"
ksp = "2.1.0-1.0.29"
maps-compose = "3.1.1"
# @keep
minSdk = "21"
okhttp = "4.12.0"
play-services-wearable = "18.1.0"
robolectric = "4.14.1"
roborazzi = "1.37.0"
rome = "1.18.0"
room = "2.6.1"
secrets = "2.0.1"
# @keep
targetSdk = "33"
version-catalog-update = "0.8.5"

[libraries]
accompanist-adaptive = { module = "com.google.accompanist:accompanist-adaptive", version.ref = "accompanist" }
accompanist-permissions = { module = "com.google.accompanist:accompanist-permissions", version.ref = "accompanist" }
androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activity-compose" }
androidx-activity-ktx = { module = "androidx.activity:activity-ktx", version.ref = "androidx-activity-compose" }
androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "androidx-appcompat" }
androidx-compose-animation = { module = "androidx.compose.animation:animation" }
androidx-compose-bom = { module = "androidx.compose:compose-bom", version.ref = "androidx-compose-bom" }
androidx-compose-foundation = { module = "androidx.compose.foundation:foundation" }
androidx-compose-foundation-layout = { module = "androidx.compose.foundation:foundation-layout" }
androidx-compose-material-iconsExtended = { module = "androidx.compose.material:material-icons-extended" }
androidx-compose-material3 = { module = "androidx.compose.material3:material3" }
androidx-compose-material3-adaptive = { module = "androidx.compose.material3.adaptive:adaptive" }
androidx-compose-material3-adaptive-layout = { module = "androidx.compose.material3.adaptive:adaptive-layout" }
androidx-compose-material3-adaptive-navigation = { module = "androidx.compose.material3.adaptive:adaptive-navigation" }
androidx-compose-material3-adaptive-navigationSuite = { module = "androidx.compose.material3:material3-adaptive-navigation-suite" }
androidx-compose-materialWindow = { module = "androidx.compose.material3:material3-window-size-class" }
androidx-compose-runtime = { module = "androidx.compose.runtime:runtime" }
androidx-compose-runtime-livedata = { module = "androidx.compose.runtime:runtime-livedata" }
androidx-compose-ui = { module = "androidx.compose.ui:ui" }
androidx-compose-ui-googlefonts = { module = "androidx.compose.ui:ui-text-google-fonts" }
androidx-compose-ui-graphics = { module = "androidx.compose.ui:ui-graphics" }
androidx-compose-ui-test = { module = "androidx.compose.ui:ui-test" }
androidx-compose-ui-test-junit4 = { module = "androidx.compose.ui:ui-test-junit4" }
androidx-compose-ui-test-manifest = { module = "androidx.compose.ui:ui-test-manifest" }
androidx-compose-ui-text = { module = "androidx.compose.ui:ui-text" }
androidx-compose-ui-tooling = { module = "androidx.compose.ui:ui-tooling" }
androidx-compose-ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview" }
androidx-compose-ui-util = { module = "androidx.compose.ui:ui-util" }
androidx-compose-ui-viewbinding = { module = "androidx.compose.ui:ui-viewbinding" }
androidx-constraintlayout-compose = { module = "androidx.constraintlayout:constraintlayout-compose", version.ref = "androidx-constraintlayout" }
androidx-core-ktx = { module = "androidx.core:core-ktx", version.ref = "androidx-corektx" }
androidx-core-splashscreen = { module = "androidx.core:core-splashscreen", version.ref = "androidx-core-splashscreen" }
androidx-glance = { module = "androidx.glance:glance", version.ref = "androidx-glance" }
androidx-glance-appwidget = { module = "androidx.glance:glance-appwidget", version.ref = "androidx-glance" }
androidx-glance-material3 = { module = "androidx.glance:glance-material3", version.ref = "androidx-glance" }
androidx-hilt-navigation-compose = { module = "androidx.hilt:hilt-navigation-compose", version.ref = "androidxHiltNavigationCompose" }
androidx-lifecycle-livedata-ktx = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "androidx-lifecycle-compose" }
androidx-lifecycle-runtime = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "androidx-lifecycle-compose" }
androidx-lifecycle-runtime-compose = { module = "androidx.lifecycle:lifecycle-runtime-compose", version.ref = "androidx-lifecycle-runtime-compose" }
androidx-lifecycle-viewModelCompose = { module = "androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "androidx-lifecycle-compose" }
androidx-lifecycle-viewmodel-ktx = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "androidx-lifecycle-compose" }
androidx-lifecycle-viewmodel-savedstate = { module = "androidx.lifecycle:lifecycle-viewmodel-savedstate", version.ref = "androidx-lifecycle-compose" }
androidx-material-icons-core = { module = "androidx.compose.material:material-icons-core" }
androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "androidx-navigation" }
androidx-navigation-fragment = { module = "androidx.navigation:navigation-fragment-ktx", version.ref = "androidx-navigation" }
androidx-navigation-ui-ktx = { module = "androidx.navigation:navigation-ui-ktx", version.ref = "androidx-navigation" }
androidx-palette = { module = "androidx.palette:palette", version.ref = "androidx-palette" }
androidx-room-compiler = { module = "androidx.room:room-compiler", version.ref = "room" }
androidx-room-ktx = { module = "androidx.room:room-ktx", version.ref = "room" }
androidx-room-runtime = { module = "androidx.room:room-runtime", version.ref = "room" }
androidx-test-core = { module = "androidx.test:core", version.ref = "androidx-test" }
androidx-test-espresso-core = { module = "androidx.test.espresso:espresso-core", version.ref = "androidx-test-espresso" }
androidx-test-ext-junit = { module = "androidx.test.ext:junit", version.ref = "androidx-test-ext-junit" }
androidx-test-ext-truth = { module = "androidx.test.ext:truth", version.ref = "androidx-test-ext-truth" }
androidx-test-rules = { module = "androidx.test:rules", version.ref = "androidx-test" }
androidx-test-runner = "androidx.test:runner:1.6.2"
androidx-test-uiautomator = { module = "androidx.test.uiautomator:uiautomator", version.ref = "androix-test-uiautomator" }
androidx-tv-foundation = { module = "androidx.tv:tv-foundation", version.ref = "androidx-tv-foundation" }
androidx-tv-material = { module = "androidx.tv:tv-material", version.ref = "androidx-tv-material" }
androidx-wear-compose-foundation = { module = "androidx.wear.compose:compose-foundation", version.ref = "androidx-wear-compose" }
androidx-wear-compose-material = { module = "androidx.wear.compose:compose-material", version.ref = "androidx-wear-compose" }
androidx-wear-compose-navigation = { module = "androidx.wear.compose:compose-navigation", version.ref = "androidx-wear-compose" }
androidx-wear-compose-ui-tooling = { module = "androidx.wear.compose:compose-ui-tooling", version.ref = "androidx-wear-compose" }
androidx-window = { module = "androidx.window:window", version.ref = "androidx-window" }
androidx-window-core = { module = "androidx.window:window-core", version.ref = "androidx-window" }
coil-kt-compose = { module = "io.coil-kt:coil-compose", version.ref = "coil" }
core-jdk-desugaring = { module = "com.android.tools:desugar_jdk_libs", version.ref = "jdkDesugar" }
dagger-hiltandroidplugin = { module = "com.google.dagger:hilt-android-gradle-plugin", version.ref = "hilt" }
googlemaps-compose = { module = "com.google.maps.android:maps-compose", version.ref = "maps-compose" }
googlemaps-maps = { module = "com.google.android.gms:play-services-maps", version.ref = "google-maps" }
hilt-android = { module = "com.google.dagger:hilt-android", version.ref = "hilt" }
hilt-android-testing = { module = "com.google.dagger:hilt-android-testing", version.ref = "hilt" }
hilt-compiler = { module = "com.google.dagger:hilt-compiler", version.ref = "hilt" }
hilt-ext-compiler = { module = "androidx.hilt:hilt-compiler", version.ref = "hiltExt" }
horologist-audio-ui = { module = "com.google.android.horologist:horologist-audio-ui", version.ref = "horologist" }
horologist-composables = { module = "com.google.android.horologist:horologist-composables", version.ref = "horologist" }
horologist-compose-layout = { module = "com.google.android.horologist:horologist-compose-layout", version.ref = "horologist" }
horologist-compose-material = { module = "com.google.android.horologist:horologist-compose-material", version.ref = "horologist" }
horologist-compose-tools = { module = "com.google.android.horologist:horologist-compose-tools", version.ref = "horologist" }
horologist-images-coil = { module = "com.google.android.horologist:horologist-images-coil", version.ref = "horologist" }
horologist-media-data = { module = "com.google.android.horologist:horologist-media-data", version.ref = "horologist" }
horologist-media-ui = { module = "com.google.android.horologist:horologist-media-ui", version.ref = "horologist" }
horologist-roboscreenshots = { module = "com.google.android.horologist:horologist-roboscreenshots", version.ref = "horologist" }
junit = { module = "junit:junit", version.ref = "junit" }
kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib-jdk8", version.ref = "kotlin" }
kotlinx-collections-immutable = { module = "org.jetbrains.kotlinx:kotlinx-collections-immutable", version.ref = "kotlinx_immutable" }
kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "coroutines" }
kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "coroutines" }
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization-json" }
okhttp-logging = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "okhttp" }
okhttp3 = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" }
play-services-wearable = { module = "com.google.android.gms:play-services-wearable", version.ref = "play-services-wearable" }
robolectric = { module = "org.robolectric:robolectric", version.ref = "robolectric" }
roborazzi = { module = "io.github.takahirom.roborazzi:roborazzi", version.ref = "roborazzi" }
roborazzi-compose = { module = "io.github.takahirom.roborazzi:roborazzi-compose", version.ref = "roborazzi" }
roborazzi-rule = { module = "io.github.takahirom.roborazzi:roborazzi-junit-rule", version.ref = "roborazzi" }
rometools-modules = { module = "com.rometools:rome-modules", version.ref = "rome" }
rometools-rome = { module = "com.rometools:rome", version.ref = "rome" }

[plugins]
android-application = { id = "com.android.application", version.ref = "androidGradlePlugin" }
android-library = { id = "com.android.library", version.ref = "androidGradlePlugin" }
android-test = { id = "com.android.test", version.ref = "androidGradlePlugin" }
compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
gradle-versions = { id = "com.github.ben-manes.versions", version.ref = "gradle-versions" }
hilt = { id = "com.google.dagger.hilt.android", version.ref = "hilt" }
kapt = { id = "org.jetbrains.kotlin.kapt", version.ref = "kotlin" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
kotlin-parcelize = { id = "org.jetbrains.kotlin.plugin.parcelize", version.ref = "kotlin" }
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
roborazzi = { id = "io.github.takahirom.roborazzi", version.ref = "roborazzi" }
secrets = { id = "com.google.android.libraries.mapsplatform.secrets-gradle-plugin", version.ref = "secrets" }
version-catalog-update = { id = "nl.littlerobots.version-catalog-update", version.ref = "version-catalog-update" }


4. 创建数据模型

Note.kt
// Note.kt
package com.nemo.notes.model

import androidx.room.Entity
import androidx.room.PrimaryKey
import java.util.Date

@Entity(tableName = "notes")
data class Note(
    @PrimaryKey(autoGenerate = true) val id: Long = 0,
    val title: String,
    val content: String,
    val createdAt: Date = Date(),
    val updatedAt: Date = Date()
)

5. 创建 Room 数据库

NoteDatabase.kt
package com.nemo.notes.database

import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import androidx.room.TypeConverters
import com.nemo.notes.model.Note

@Database(entities = [Note::class], version = 1, exportSchema = false)
@TypeConverters(DateConverter::class)
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,
                    "note_database"
                ).build()
                INSTANCE = instance
                instance
            }
        }
    }
}
NoteDao.kt
package com.nemo.notes.database

import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query
import com.nemo.notes.model.Note
import kotlinx.coroutines.flow.Flow

@Dao
interface NoteDao {
    @Query("SELECT * FROM notes ORDER BY updatedAt DESC")
    fun getAllNotes(): Flow<List<Note>>

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

    @Query("DELETE FROM notes WHERE id = :noteId")
    suspend fun delete(noteId: Long): Int

    @Delete
    suspend fun delete(note: Note): Int
}
DateConverter.kt
// DateConverter.kt
package com.nemo.notes.database

import androidx.room.TypeConverter
import java.util.Date

class DateConverter {
    @TypeConverter
    fun fromTimestamp(value: Long?): Date? {
        return value?.let { Date(it) }
    }

    @TypeConverter
    fun dateToTimestamp(date: Date?): Long? {
        return date?.time
    }
}

6. 创建 Repository

NoteRepository.kt
package com.nemo.notes.repository

import com.nemo.notes.database.NoteDao
import com.nemo.notes.model.Note
import kotlinx.coroutines.flow.Flow
import javax.inject.Inject

class NoteRepository @Inject constructor(
    private val noteDao: NoteDao
) {
    fun getAllNotes(): Flow<List<Note>> = noteDao.getAllNotes()

    suspend fun insert(note: Note) = noteDao.insert(note)

    suspend fun delete(noteId: Long) = noteDao.delete(noteId)
}

7. 配置 Hilt

AppModule.kt
package com.nemo.notes.di

import android.content.Context
import com.nemo.notes.database.NoteDatabase
import com.nemo.notes.repository.NoteRepository
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton

@Module
@InstallIn(SingletonComponent::class)
object AppModule {
    @Provides
    @Singleton
    fun provideNoteDatabase(@ApplicationContext context: Context): NoteDatabase {
        return NoteDatabase.getDatabase(context)
    }

    @Provides
    @Singleton
    fun provideNoteRepository(database: NoteDatabase): NoteRepository {
        return NoteRepository(database.noteDao())
    }
}
NotesApp.kt
package com.nemo.notes

import android.app.Application
import dagger.hilt.android.HiltAndroidApp

@HiltAndroidApp
class NotesApp : Application() {
    companion object {
        const val NOTES_APP_URI = "https://developer.android.com/notes"
    }

}

8. 创建 ViewModel

NoteViewModel.kt
package com.nemo.notes.viewmodel

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.nemo.notes.model.Note
import com.nemo.notes.repository.NoteRepository
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.launch
import javax.inject.Inject

@HiltViewModel
class NoteViewModel @Inject constructor(
    private val repository: NoteRepository
) : ViewModel() {

    val allNotes: Flow<List<Note>> = repository.getAllNotes()

    fun insert(note: Note) = viewModelScope.launch {
        repository.insert(note)
    }

    fun addNote(note: Note) {
        viewModelScope.launch {
            repository.insert(note)
        }
    }

    fun delete(noteId: Long) = viewModelScope.launch {
        repository.delete(noteId)
    }
}

9. 创建 UI

MainActivity.kt
package com.nemo.notes.ui

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.ui.Modifier
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import com.nemo.notes.ui.theme.NotesAppTheme
import dagger.hilt.android.AndroidEntryPoint

@AndroidEntryPoint
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            NotesAppTheme {
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colorScheme.background
                ) {
                    val navController = rememberNavController()
                    NavHost(navController = navController, startDestination = "noteList") {
                        composable("noteList") {
                            NoteListScreen(navController)
                        }
                        composable("noteEdit/{noteId}") { backStackEntry ->
                            val noteId = backStackEntry.arguments?.getString("noteId")?.toLongOrNull()
                            NoteEditScreen(navController, noteId)
                        }
                    }
                }
            }
        }
    }
}
NoteListScreen.kt
package com.nemo.notes.ui

import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.navigation.NavHostController
import com.nemo.notes.viewmodel.NoteViewModel

@Composable
fun NoteListScreen(navController: NavHostController) {
    val viewModel: NoteViewModel = hiltViewModel()
    val notes by viewModel.allNotes.collectAsState(initial = emptyList())

    Column(modifier = Modifier.padding(16.dp)) {
        Text(text = "My Notes", style = MaterialTheme.typography.headlineMedium)
        LazyColumn {
            items(notes) { note ->
                Text(text = note.title, modifier = Modifier.padding(8.dp))
            }
        }
    }
}

@Composable
fun NoteEditScreen(navController: NavHostController, noteId: Long?) {
    // ...
}

以上为主要部分

项目代码参考地址:github.com/wxxzy/Notes…

10. 运行项目

  1. 连接 Android 设备或启动模拟器。
  2. 点击 Android Studio 中的 Run 按钮。
  3. 应用将启动并显示一个简单的笔记列表界面。

11. 下一步

  • 添加笔记创建和编辑功能。
  • 实现搜索和标签管理功能。
  • 集成云同步和备份功能。
  • 优化 UI 和用户体验。

通过以上步骤,您已经成功初始化了一个基于 Kotlin、Jetpack Compose、Room 和 Hilt 的 Android 笔记应用项目。如果您有更多需求或问题,欢迎进一步讨论!