使用 LiveData 和 JetPack Compose Android Kotlin 插入和读取 RoomDb 数据

问题描述 投票:0回答:1

该项目使用 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 进行生命周期感知数据处理。

kotlin android-jetpack-compose android-jetpack android-livedata roomdb
1个回答
0
投票
    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")
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.