该项目使用 Jetpack Compose、Room Database 和 LiveData 实现了基本的笔记应用程序。以下是关键组件和代码流程的概述:
主要部件: 数据库和数据模型:
注释:表示具有 ID、标题和描述的注释的数据实体。 NotesDao:数据访问对象(DAO),包含数据库操作(插入、删除、获取)的函数。 NotesDatabase:用于 Room 数据库配置的 Singleton 类,提供对 DAO 的访问。 存储库:
NotesRepository:抽象数据操作,将ViewModel连接到Room数据库。提供获取、添加和删除注释的方法。 查看模型:
NoteViewModel:通过公开 LiveData 来观察笔记来管理 UI 相关数据,并使用 Kotlin 协程在后台线程中处理添加/删除笔记。 NoteViewModelFactory:为ViewModel提供存储库实例。 UI 组件(Jetpack Compose):
MainActivity:初始化数据库、存储库、ViewModel,并呈现 NoteScreen 可组合项。 NoteScreen:显示注释列表,并允许通过输入字段和保存按钮添加/删除注释。使用LiveData观察数据变化并更新UI。 NoteRow:呈现列表中的各个注释。 NoteInput:注释标题和说明的输入字段。 NoteButton:保存笔记的按钮。 代码流程: MainActivity 初始化 Room 数据库、存储库和 ViewModel。 NoteScreen 从 ViewModel 观察 LiveData 并使用 LazyColumn 呈现注释列表。 用户可以通过输入字段和保存按钮添加新注释,从而触发 ViewModel 中的 addNote() 函数。 removeNote() 函数处理注释删除。 ViewModel 与存储库交互,存储库又与 Room 通信以执行数据库操作。 该应用程序采用现代 Android 架构原则设计,利用 Jetpack Compose 实现 UI、Room 实现数据持久化,并利用 LiveData/ViewModel 进行生命周期感知数据处理。
Add below libraries module app gradle file
plugins {
alias(libs.plugins.kotlin.kapt)
}
// Room DB
implementation (libs.androidx.room.ktx)
implementation (libs.androidx.room.runtime)
annotationProcessor (libs.androidx.room.compiler)
kapt (libs.androidx.room.compiler)
//LiveData
implementation(libs.androidx.lifecycle.viewmodel.compose)
implementation(libs.androidx.runtime.livedata)
[libraries]
androidx-room-compiler = { module = "androidx.room:room-compiler", version.ref = "roomKtx" }
androidx-room-ktx = { module = "androidx.room:room-ktx", version.ref = "roomKtx" }
androidx-room-runtime = { module = "androidx.room:room-runtime", version.ref = "roomKtx" }
androidx-runtime-livedata = { module = "androidx.compose.runtime:runtime-livedata", version.ref = "runtimeLivedata" }
androidx-lifecycle-viewmodel-compose = { module = "androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "lifecycleRuntimeKtx" }
[versions]
kaptVersion = "1.9.0"
runtimeLivedata = "1.7.0"
roomKtx = "2.6.1"
lifecycleRuntimeKtx = "2.8.4"
agp = "8.4.0-rc02"
kotlin = "1.9.0"
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
kotlin-kapt = { id = "org.jetbrains.kotlin.kapt", version.ref = "kaptVersion"}
//build gradle file(Project level)
plugins{
alias(libs.plugins.kotlin.kapt) apply false
}
//MainActivity
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
NoteAppTheme {
Surface(modifier = Modifier.fillMaxSize()) {
val userDao = NotesDatabase.getDatabase(this).notesDao()
val repository = NotesRepository(userDao)
val factory = NoteViewModelFactory(repository)
val noteViewModel = ViewModelProvider(this,factory)[NoteViewModel::class.java]
NoteScreen(noteViewModel)
}
}
}
}
}
@Composable
fun NoteScreen(noteViewModel: NoteViewModel? = viewModel()) {
val noteListState = noteViewModel?.readAllData?.observeAsState(initial = emptyList())
val noteList = noteListState?.value ?: emptyList()
NoteScreen(notes = noteList,
onAddNote = {
noteViewModel?.addNote(it)
},
onRemoveNote = {
noteViewModel?.removeNote(it)
})
}
// NoteScreen
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun NoteScreen(notes: List<Notes>, onAddNote: (Notes) -> Unit,
onRemoveNote: (Notes) -> Unit) {
var title by remember {
mutableStateOf("")
}
var description by remember {
mutableStateOf("")
}
Column {
TopAppBar(title = {
Text(text = stringResource(id = R.string.app_name))
}, actions = {
Icon(imageVector = Icons.Rounded.Notifications, contentDescription = "Notification icon")
}, colors = TopAppBarDefaults.topAppBarColors(
containerColor = Color.LightGray
))
val context = LocalContext.current
Column(modifier = Modifier.padding(16.dp)) {
NoteInput(modifier = Modifier.fillMaxWidth(), label = "Title", text = title, onTextChange = {
if(it.all { char ->
char.isLetter() || char.isWhitespace()
}) title = it
})
Spacer(modifier = Modifier.height(10.dp))
NoteInput(modifier = Modifier.fillMaxWidth(), label = "Add a note", text = description, onTextChange = {
if (it.all { char ->
char.isLetter() || char.isWhitespace()
}) description = it
})
Spacer(modifier = Modifier.height(10.dp))
NoteButton(text = "Save",
enabled = title.isNotEmpty() && description.isNotEmpty(),
onClick = {
if (title.isNotEmpty() && description.isNotEmpty()) {
onAddNote(Notes(title = title, description = description))
Toast.makeText(context, "Added note $title", Toast.LENGTH_SHORT).show()
title = ""
description = ""
}
}, modifier = Modifier.align(
alignment = Alignment.CenterHorizontally
))
Divider(modifier = Modifier.padding(10.dp))
LazyColumn {
items(notes) { note ->
NoteRow(onClick = {
onRemoveNote(note)
}, note = note)
}
}
}
}
}
@Composable
fun NoteRow(
modifier: Modifier = Modifier,
onClick: (Notes) -> Unit,
note: Notes,
){
Surface(modifier = modifier
.padding(10.dp)
.clip(RoundedCornerShape(topEnd = 33.dp, bottomStart = 33.dp))
.fillMaxWidth(),
color = Color.Cyan,
tonalElevation = 4.dp){
Column(modifier = modifier
.clickable { onClick(note) }
.padding(horizontal = 12.dp, vertical = 8.dp),
horizontalAlignment = Alignment.Start) {
Text(text = note.title, style = MaterialTheme.typography.titleSmall)
Text(text = note.description, style = MaterialTheme.typography.bodySmall)
}
}
}
//Note Input class
@OptIn(ExperimentalMaterial3Api::class, ExperimentalComposeUiApi::class)
@Composable
fun NoteInput(
modifier: Modifier,
label: String,
text: String,
maxLine: Int = 1,
onTextChange: (String) -> Unit,
onImeAction: () -> Unit = {}
) {
val keyboardController = LocalSoftwareKeyboardController.current
TextField(value = text, onValueChange = onTextChange,
colors = TextFieldDefaults.textFieldColors(
containerColor = Color.Transparent
),
label = { Text(text = label) },
maxLines = maxLine,
keyboardOptions = KeyboardOptions.Default.copy(
imeAction = ImeAction.Done
),
keyboardActions = KeyboardActions(onDone = {
onImeAction()
keyboardController?.hide()
}),
modifier = modifier
)
}
@Composable
fun NoteButton(
modifier: Modifier = Modifier,
text: String,
onClick: () -> Unit,
enabled: Boolean = false,
) {
Button(onClick = { onClick.invoke() },
shape = CircleShape,
enabled = enabled,
modifier = modifier,
) {
Text(text = text)
}
}
//Repository
class NotesRepository(private val notesDao: NotesDao) {
fun fetchNotes(): LiveData<List<Notes>> = notesDao.fetchNotesData()
suspend fun addNotes(notes: Notes) = notesDao.insertData(notes)
}
//Data entity model
@Entity(tableName = "notes_table")
data class Notes(
@PrimaryKey(autoGenerate = true)
val id: Int = 0,
val title: String,
val description: String
)
//Dao Class
@Dao
interface NotesDao {
@Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun insertData(note: Notes)
@Query("SELECT * FROM notes_table ORDER BY id ASC")
fun fetchNotesData(): LiveData<List<Notes>>
@Delete
suspend fun deleteNote(notes: Notes)
}
//Database
@Database(entities = [Notes::class], version = 1, exportSchema = false)
abstract class NotesDatabase : RoomDatabase() {
abstract fun notesDao(): NotesDao
companion object {
var DATABASE_INSTANCE: NotesDatabase? = null
fun getDatabase(context: Context): NotesDatabase {
val tempInstance = DATABASE_INSTANCE
if (tempInstance != null) {
return tempInstance
}
synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
NotesDatabase::class.java,
"notes_database"
).build()
DATABASE_INSTANCE = instance
return instance
}
}
}
}
//Repository
class NotesRepository(private val notesDao: NotesDao) {
fun fetchNotes(): LiveData<List<Notes>> = notesDao.fetchNotesData()
suspend fun addNotes(notes: Notes) = notesDao.insertData(notes)
suspend fun deleteNote(notes: Notes) = notesDao.deleteNote(notes)
}
//ViewModel
import androidx.compose.runtime.mutableStateListOf
import androidx.lifecycle.LiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.example.noteapp.model.Notes
import com.example.noteapp.repository.NotesRepository
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
class NoteViewModel(private val repository: NotesRepository) : ViewModel() {
var readAllData: LiveData<List<Notes>>
init {
readAllData = repository.fetchNotes()
}
fun addNote(note: Notes) {
viewModelScope.launch(Dispatchers.IO) {
repository.addNotes(note)
}
}
fun removeNote(note: Notes) {
noteList.remove(note)
}
fun getAllNotes(): LiveData<List<Notes>> {
return readAllData
}
}
class NoteViewModelFactory(private val repository: NotesRepository) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(NoteViewModel::class.java)) {
return NoteViewModel(repository) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}