开启Android学习之旅-2-架构组件实现数据列表及添加(kotlin)
Android Jetpack 体验-官方codelab
1. 实现功能
- 使用 Jetpack 架构组件 Room、ViewModel 和 LiveData 设计应用;
- 从sqlite获取、保存、删除数据;
- sqlite数据预填充功能;
- 使用 RecyclerView 展示数据列表;
2. 使用架构组件
架构组件及其协作方式:

- LiveData 是一种可观察的数据存储器,每当数据发生变化时,它都会通知观察者。 LiveData会根据负责监听变化的生命周期自动停止或恢复观察。
- ViewModel 充当存储库和UI之间的通信中心,以及应用程序中其他部分的UI相关的数据的容器。activity和fragment负责将数据绘制到屏幕上,ViewModel负责保存并处理界面所需的所有数据。
- Repository 管理数据源,可以是网络或本地的。
- RoomDatabase 简化数据库工作,它使用 DAO 向 SQLite 数据库发起请求。
- DAO 数据访问对象,一般是接口或抽象类
- SQLite 设备存储空间
- 实体:使用 Room 用于描述数据库表的带注解的类。
RoomWordSample 架构概览

3. 创建应用,配置依赖
环境:
Android Studio Flamingo | 2022.2.1 Patch 1
Android Gradle Plugin Version: 8.0.1
Gradle Version: 8.0
JDK 17
compileSdk 33
minSdk 24
targetSdk 33
统一项目依赖版本实现
- 在 build.gradle(root)下定义版本号,注意 buildscript 一定要在最上面
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {ext{appCompatVersion = '1.6.1'activityVersion = '1.6.0'roomVersion = '2.5.0'lifecycleVersion = '2.5.1'coroutines = '1.6.4'constraintLayoutVersion = '2.1.4'materialVersion = '1.9.0'// testingjunitVersion = '4.13.2'androidxJunitVersion = '1.1.5'espressoVersion = '3.5.1'}
}plugins {id 'com.android.application' version '8.0.1' apply falseid 'com.android.library' version '8.0.1' apply falseid 'org.jetbrains.kotlin.android' version '1.8.20' apply false
}
- 然后在 build.gradle(app)下添加依赖
dependencies {implementation "androidx.appcompat:appcompat:${rootProject.appCompatVersion}"// activity-ktx 提供了 Kotlin 对 Android Activity API 的扩展。// 这些扩展函数和属性使得在 Kotlin 中使用 Activity API 更加简洁和方便。implementation "androidx.activity:activity-ktx:${rootProject.activityVersion}"// Room componentsimplementation "androidx.room:room-ktx:${rootProject.roomVersion}"implementation "androidx.room:room-runtime:${rootProject.roomVersion}"annotationProcessor "androidx.room:room-compiler:${rootProject.roomVersion}"kapt "androidx.room:room-compiler:${rootProject.roomVersion}"testImplementation "androidx.room:room-testing:${rootProject.roomVersion}"// Lifecycle componentsimplementation "androidx.lifecycle:lifecycle-viewmodel-ktx:${rootProject.lifecycleVersion}"implementation "androidx.lifecycle:lifecycle-livedata-ktx:${rootProject.lifecycleVersion}"
// implementation "androidx.lifecycle:lifecycle-common-java8:${rootProject.lifecycleVersion}"// kotlin components// core-ktx 提供了 Kotlin 对 Android 核心库的扩展。// 这些扩展函数和属性使得在 Kotlin 中使用 Android 核心库更加简洁和方便。implementation "androidx.core:core-ktx:1.8.0"implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:${rootProject.coroutines}"implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:${rootProject.coroutines}"// uiimplementation "com.google.android.material:material:${rootProject.materialVersion}"implementation "androidx.constraintlayout:constraintlayout:${rootProject.constraintLayoutVersion}"// TestingtestImplementation "junit:junit:${rootProject.junitVersion}"androidTestImplementation "androidx.test.ext:junit:${rootProject.androidxJunitVersion}"androidTestImplementation "androidx.test.espresso:espresso-core:${rootProject.espressoVersion}"
}
- build.gradle(app) 添加 kotlin 注解处理器插件
plugins {id 'com.android.application'id 'org.jetbrains.kotlin.android'id 'kotlin-kapt'
}
- java 版本相关设置
在build.gradle(app) 配置,解决Execution failed for task ':app:kaptGenerateStubsDebugKotlin'错误.
compileOptions {sourceCompatibility JavaVersion.VERSION_17targetCompatibility JavaVersion.VERSION_17
}
kotlinOptions {jvmTarget = '17'
}
AndroidX 版本查询:
https://developer.android.google.cn/jetpack/androidx/versions?hl=zh-cn
4. 创建实体
创建 Word 数据类,描述在数据库中存储单词的表:
package com.alex.roomwordssampleimport androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey/*** @Author : alex* @Date : on 2024/1/4 21:05.* @Description :Word数据类,用于定义数据库中的表*/
@Entity(tableName = "word_table")
data class Word(@PrimaryKey @ColumnInfo(name="word") val word: String)
- 该类描述 word_table 表只有一个列:word;
@Entity(tableName ="word_table"): 表名@PrimaryKey: 主键@ColumnInfo(name ="word"):列名
5. 创建数据访问对象 DAO
DAO 定义 SQL 查询并将其与方法调用相关联。DAO 必须是一个接口或抽象类,默认情况下,所有查询必须在单独的线程上执行。
Room 支持 kotlin 协程,可以使用 suspend 修饰符对查询进行注解,然后从协程获取其他挂起函数对其进行调用。
WordDao 接口定义:
package com.alex.roomwordssampleimport androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import kotlinx.coroutines.flow.Flow/*** @Author : alex* @Date : on 2024/1/4 21:07.* @Description :word 数据访问接口*/
@Dao
interface WordDao {// 按照字母顺序获取所有单词// 为了观察数据变化情况,返回值使用了Flow// 当数据库更新时,它会发出一个新的流,然后,您可以使用该流更新UI。// 当Room查询返回LiveData或Flow时,查询是在单独的线程上异步执行的。@Query("SELECT * from word_table ORDER BY word ASC")fun getAlphabetizedWords(): Flow<List<Word>>// 插入单词// 将忽略与列表中的现有字词完全相同的新字词。@Insert(onConflict = OnConflictStrategy.IGNORE)suspend fun insert(word:Word)// 删除所有单词@Query("DELETE FROM word_table")suspend fun deleteAll()
}
解释:
@Dao注解该接口表示为 Room 的 DAO类。- 删除使用的
@Query,可以定义复杂SQL语句。 - 使用
kotlin-coroutines中的 Flow,定义返回数据类型,是为了观察数据变化情况,当数据发生变化时,Room 会更新Flow。
5. 添加 Room 数据库
Room 数据库类必须是抽象的,必须扩展 RoomDatabase,整个应用通常只需要一个 Room 数据库实例。
WordRoomDatabase 定义:
package com.alex.roomwordssampleimport android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import androidx.sqlite.db.SupportSQLiteDatabase
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch/*** @Author : alex* @Date : on 2024/1/4 21:10.* @Description : Room数据抽象类*/
@Database(entities = [Word::class], version = 1, exportSchema = false)
abstract class WordRoomDatabase: RoomDatabase(){abstract fun wordDao(): WordDaocompanion object{//单例,防止出现同时打开多个数据库实例的情况@Volatileprivate var INSTANCE: WordRoomDatabase? = nullfun getDatabase(context: Context,scope: CoroutineScope): WordRoomDatabase{return INSTANCE ?: synchronized(this){val instance = Room.databaseBuilder(context.applicationContext,WordRoomDatabase::class.java,"word_database").fallbackToDestructiveMigration().addCallback(WordDatabaseCallback(scope)).build()INSTANCE = instanceinstance}}// 为了在数据库创建时填充它,我们需要实现 RoomDatabase.Callback(),并覆盖 onCreate()。private class WordDatabaseCallback(private val scope: CoroutineScope): RoomDatabase.Callback(){override fun onCreate(db: SupportSQLiteDatabase) {super.onCreate(db)INSTANCE?.let { database ->scope.launch(Dispatchers.IO) {populateDatabase(database.wordDao())}}}}suspend fun populateDatabase(wordDao: WordDao){wordDao.deleteAll()var word=Word("Hello")wordDao.insert(word)word=Word("World")wordDao.insert(word)}}
}
代码解释:
- Room 数据库类必须是抽象的,必须扩展
RoomDatabase。 @Database将该类注解为 Room 数据库,并使用注解参数声明数据库中的实体以及设置版本号。- 数据库通过每个
@Dao的抽象getter方法公开 DAO - 定义单例 WordRoomDatabase,防止同时打开数据库的多个实例。
- getDatabase 会返回单例,首次使用时会创建数据库,并删除旧数据,填充示例数据。
6. 创建存储库
Repository 会将多个数据源的访问权限抽象化,提供一个整洁的 API,用于获取对应用其余部分的数据访问权限。
WordRepository 定义:
package com.alex.roomwordssampleimport androidx.annotation.WorkerThread
import kotlinx.coroutines.flow.Flow/*** @Author : alex* @Date : on 2024/1/4 21:13.* @Description : word 存储库,可以用于管理多个数据源*/
class WordRepository(private val wordDao:WordDao) {// Room在单独的线程上执行所有查询// 观察数据变化情况,返回值使用了Flowval allWords:Flow<List<Word>> = wordDao.getAlphabetizedWords()// 在后台线程中执行操作@Suppress("RedundantSuspendModifier")@WorkerThreadsuspend fun insert(word: Word){wordDao.insert(word)}
}
- DAO 会被传递到存储库构造函数中,而非整个数据库中。DAO 包含数据库的所有读取/写入方法,因此它只需要访问 DAO,无需向存储库公开整个数据库。
- allWords 表具有公开属性。它通过从 Room 获取
Flow字词列表来进行初始化;您之所以能够实现该操作,是因为您在“观察数据库变化”步骤中定义getAlphabetizedWords方法以返回Flow的方式。Room 将在单独的线程上执行所有查询。 - Room 在主线程之外执行挂起查询。
7. 创建 ViewModel
ViewModel: 向界面提供数据,不受配置变化的影响。ViewModel 是 Lifecycle 库的一部分。
LiveData与ViewModel的关系
LiveData 是一种可观察的数据存储器,每当数据发生变化时,您都会收到通知。与 Flow 不同,LiveData 具有生命周期感知能力,即遵循其他应用组件(如 activity 或 fragment)的生命周期。LiveData 会根据负责监听变化的组件的生命周期自动停止或恢复观察。因此,LiveData 适用于界面使用或显示的可变数据。
ViewModel 会将存储库中的数据从 Flow 转换为 LiveData,并将字词列表作为 LiveData 传递给界面。这样可以确保每次数据库中的数据发生变化时,界面都会自动更新。
viewModelScope
AndroidX lifecycle-viewmodel-ktx 库将 viewModelScope 添加为 ViewModel 类的扩展函数。
WordViewModel 定义:
package com.alex.roomwordssampleimport androidx.lifecycle.LiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.asLiveData
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.launch/*** @Author : alex* @Date : on 2024/1/4 21:15.* @Description : ViewModel 充当存储库和UI之间的通信中心,以及应用程序中其他部分的UI相关的数据的容器。* ViewModel通过使用LiveData或Flow来保留数据,这样它就可以在配置更改后继续存在。* ViewModel可以通过调用ViewModelProvider.Factory来创建。* activity和fragment负责将数据绘制到屏幕上,ViewModel负责保存并处理界面所需的所有数据。*/
class WordViewModel(private val repository:WordRepository) :ViewModel(){// LiveData 是一种可观察的数据存储器,每当数据发生变化时,它都会通知观察者。// LiveData会根据负责监听变化的生命周期自动停止或恢复观察。// 使用 LiveData 并缓存 allWords 返回的内容有几个好处:// - 我们可以在数据上设置一个观察者(而不是轮询变化),并且只有当数据实际发生变化时才更新用户界面。// - 通过 ViewModel,仓库与用户界面完全分离。val allWords: LiveData<List<Word>> = repository.allWords.asLiveData()// 启动一个新的协程以非阻塞方式插入数据。fun insert(word: Word) = viewModelScope.launch {repository.insert(word)}
}/*** WordViewModelFactory 类的作用是创建 WordViewModel 实例,* 并确保 WordViewModel 可以接收到 WordRepository 实例,以便它可以与数据源进行交互。*/
class WordViewModelFactory(private val repository: WordRepository): ViewModelProvider.Factory{override fun <T : ViewModel> create(modelClass: Class<T>): T {// 检查modelClass是否是WordViewModel的子类if (modelClass.isAssignableFrom(WordViewModel::class.java)){@Suppress("UNCHECKED_CAST")return WordViewModel(repository) as T}throw IllegalArgumentException("Unknown ViewModel class")}
}
解析:
- 创建了一个名为
WordViewModel的类,该类可获取WordRepository作为参数并扩展ViewModel。存储库是 ViewModel 需要的唯一依赖项。如果需要其他类,系统也会在构造函数中传递相应的类。 - 添加了一个公开的
LiveData成员变量以缓存字词列表。 - 使用存储库中的
allWordsFlow 初始化了LiveData。然后,您通过调用asLiveData().将该 Flow 转换成了 LiveData。 - 创建了一个可调用存储库的
insert()方法的封装容器insert()方法。这样一来,便可从界面封装insert()的实现。我们将启动新协程并调用存储库的挂起函数 insert。如上所述,ViewModel 的协程作用域基于它的名为viewModelScope的生命周期(您将在这里使用)。 - 创建了 ViewModel,并实现了
ViewModelProvider.Factory,后者可获取创建WordViewModel所需的依赖项作为参数:WordRepository。
使用 viewModels 和 ViewModelProvider.Factory 后,框架将负责 ViewModel 的生命周期。它不受配置变化的影响,即使重建 activity,您始终能得到 WordViewModel 类的正确实例。
使用asLiveData,需要添加下面的依赖:
> 添加依赖:implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1"
> 引入:import androidx.lifecycle.asLiveData
8. 列表页面布局实现(MainActivity)
列表页面使用了 RecyclerView 组件,需要先定义列表项布局 recyclerview_item.xml :
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:orientation="vertical"android:layout_width="match_parent"android:layout_height="wrap_content"><TextViewandroid:id="@+id/textView"style="@style/word_title"android:layout_width="match_parent"android:layout_height="wrap_content"android:background="@android:color/holo_orange_light" /></LinearLayout>
在activity_main.xml 中引入 RecyclerView,并添加一个浮动按钮
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayoutxmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".MainActivity"><androidx.recyclerview.widget.RecyclerViewandroid:id="@+id/recyclerview"android:layout_width="0dp"android:layout_height="0dp"tools:listitem="@layout/recyclerview_item"android:padding="@dimen/big_padding"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintRight_toRightOf="parent"app:layout_constraintTop_toTopOf="parent"/><com.google.android.material.floatingactionbutton.FloatingActionButtonandroid:id="@+id/fab"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintEnd_toEndOf="parent"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_margin="16dp"android:contentDescription="@string/add_word"android:src="@drawable/id_add_black_24db"/></androidx.constraintlayout.widget.ConstraintLayout>
浮动按钮图标的制作,使用了 Asset Studio 工具(File->New->Vector Asset)
9. RecyclerView
MainActivity 中 使用 RecyclerView 显示数据。
添加步骤:
- 定义 WordListAdapter 类
- 定义填充列表项行为
- 在 MainActivity 中添加 RecyclerView
WordListAdapter 类定义:
package com.alex.roomwordssampleimport android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView/*** @Author : alex* @Date : on 2024/1/4 21:40.* @Description :RecyclerView的适配器*/
class WordListAdapter:ListAdapter<Word, WordListAdapter.WordViewHolder>(WordsComparator()) {override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): WordViewHolder {return WordViewHolder.create(parent)}override fun onBindViewHolder(holder: WordViewHolder, position: Int) {val current=getItem(position)holder.bind(current.word)}class WordViewHolder(itemView:View):RecyclerView.ViewHolder(itemView) {private val wordItemView:TextView= itemView.findViewById(R.id.textView)fun bind(text:String?){wordItemView.text=text}companion object{fun create(parent:ViewGroup):WordViewHolder{val view:View=LayoutInflater.from(parent.context).inflate(R.layout.recyclerview_item,parent,false)return WordViewHolder(view)}}}class WordsComparator : DiffUtil.ItemCallback<Word>() {override fun areItemsTheSame(oldItem: Word, newItem: Word): Boolean {return oldItem === newItem}override fun areContentsTheSame(oldItem: Word, newItem: Word): Boolean {return oldItem.word == newItem.word}}
}
RecyclerView 是 Android 中用于显示大量数据集的一个组件,它优化了这些数据的显示,只创建和渲染屏幕上可见的部分,从而提高了性能。
RecyclerView 通过一个适配器来管理数据的显示,适配器负责将数据与每个列表项视图(item view)进行绑定。
RecyclerView.ViewHolder 是一个静态类,用于存储对列表项视图中的界面元素的引用。
在这个例子中,WordViewHolder 是 RecyclerView.ViewHolder 的一个子类,它存储了对 TextView 的引用,并提供了一个 bind 方法来更新 TextView 的内容。
class WordViewHolder(itemView:View):RecyclerView.ViewHolder(itemView) {private val wordItemView:TextView= itemView.findViewById(R.id.textView)fun bind(text:String?){wordItemView.text=text}...
}
ListAdapter 是 RecyclerView.Adapter 的一个子类,它使用 DiffUtil 来计算数据集的最小更新。当数据发生变化时,ListAdapter 会计算出新旧数据集之间的差异,并使用这些差异来更新 RecyclerView。
在这个例子中,WordListAdapter 是 ListAdapter 的一个子类,它使用 WordsComparator 来计算数据集的差异。
WordsComparator 是 DiffUtil.ItemCallback 的一个子类,它提供了两个方法:areItemsTheSame 和 areContentsTheSame。
areItemsTheSame 用于检查两个 Word 是否表示同一个对象,areContentsTheSame 用于检查两个 Word 的内容是否相同。
class WordsComparator : DiffUtil.ItemCallback<Word>() {override fun areItemsTheSame(oldItem: Word, newItem: Word): Boolean {return oldItem === newItem}override fun areContentsTheSame(oldItem: Word, newItem: Word): Boolean {return oldItem.word == newItem.word}
}
WordViewHolder 中的 create 静态方法用于创建 WordViewHolder 的实例。这个方法接收一个 ViewGroup 类型的参数 parent,这通常是 RecyclerView。
在 create 方法中,首先通过 LayoutInflater 从 recyclerview_item.xml 布局文件中创建一个新的视图。然后,将这个新创建的视图作为参数传递给 WordViewHolder 的构造函数,创建一个 WordViewHolder 的实例。
这样做的好处是,WordViewHolder 的创建逻辑被封装在 WordViewHolder 类内部,使得 WordListAdapter 的代码更加简洁。同时,如果 WordViewHolder 的创建逻辑需要修改,只需要在 WordViewHolder 类内部修改,而不需要修改 WordListAdapter 的代码。
在MainActivity中添加 RecyclerView:
setContentView(R.layout.activity_main)val recyclerView=findViewById<RecyclerView>(R.id.recyclerview)
val adapter=WordListAdapter()
recyclerView.adapter=adapter
recyclerView.layoutManager= LinearLayoutManager(this)
10. 在应用中实例化Repository和Database
您希望应用中的数据库和存储库只有一个实例。实现该目的的一种简单的方法是,将它们作为 Application 类的成员进行创建。然后,在需要时只需从应用检索,而不是每次都进行构建。
创建 WordsApplication,继承自 Application:
package com.alex.roomwordssampleimport android.app.Application
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob/*** @Author : alex* @Date : on 2024/1/4 22:03.* @Description : 实现Application类,以便在整个应用程序中使用单个实例*/
class WordsApplication: Application(){// 为应用程序的生命周期创建一个作用域,以便在应用程序被销毁时取消所有协程// 不需要取消这个作用域,因为它会随着进程的结束而被销毁。// SupervisorJob() 创建了一个新的 Job 实例,并将其作为参数传递给 CoroutineScope 的构造函数,创建了一个新的协程作用域 applicationScope。// 这个作用域的特性是,它的子协程之间是相互独立的,一个子协程的失败不会导致其他子协程的取消。val applicationScope = CoroutineScope(SupervisorJob())val database by lazy {WordRoomDatabase.getDatabase(this,applicationScope)}val repository by lazy { WordRepository(database.wordDao()) }
}
在 AndroidManifest 文件将 WordApplication 设为 application android:name
11. 填充数据库
在 WordRoomDatabase 定义了 WordDatabaseCallback 用于在创建数据库的时候,删除旧数据,并添加示例数据
12. 添加 新增数据页面 NewWordActivity
布局 activity_new_word.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:orientation="vertical"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".NewWordActivity"><EditTextandroid:id="@+id/edit_word"android:layout_width="match_parent"android:layout_height="wrap_content"android:minHeight="@dimen/min_height"android:fontFamily="sans-serif-light"android:hint="@string/hint_word"android:inputType="textAutoComplete"android:layout_margin="@dimen/big_padding"android:textSize="18sp" /><Buttonandroid:id="@+id/button_save"android:layout_width="match_parent"android:layout_height="wrap_content"android:background="@color/purple_500"android:text="@string/button_save"android:layout_margin="@dimen/big_padding"android:textColor="@color/buttonLabel" /></LinearLayout>
NewWordActivity 代码:
package com.alex.roomwordssampleimport android.app.Activity
import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.text.TextUtils
import android.widget.Button
import android.widget.EditTextclass NewWordActivity : AppCompatActivity() {private lateinit var editWordView:EditTextoverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_new_word)editWordView = findViewById(R.id.edit_word)val button = findViewById<Button>(R.id.button_save)button.setOnClickListener {val replyIntent = Intent()if(TextUtils.isEmpty(editWordView.text)){setResult(Activity.RESULT_CANCELED,replyIntent)}else{val word = editWordView.text.toString()replyIntent.putExtra(EXTRA_REPLY,word)setResult(Activity.RESULT_OK,replyIntent)}finish()}}companion object{const val EXTRA_REPLY = "com.alex.roomwordssample.REPLY"}
}
13. 数据与页面关联
最后一步是将界面连接到数据库,方法是保存用户输入的新字词,并在 RecyclerView 中显示当前字词数据库的内容。
如需显示数据库的当前内容,请添加可观察 ViewModel 中的 LiveData 的观察者。
每当数据发生变化时,系统都会调用 onChanged() 回调,此操作会调用适配器的 setWords() 方法来更新此适配器的缓存数据并刷新显示的列表。
1. 在 MainActivity 中创建 ViewModel
// 通过 viewModels 委托属性实现ViewModel的实例化
private val wordViewModel: WordViewModel by viewModels {WordViewModelFactory((application as WordsApplication).repository)
}
这里使用了 viewModels 委托,并传入了 WordViewModelFactory 实例,该实例基于从 WordApplication 中检索的存储库构建而成。
当观察到数据发生变化且 activity 在前台显示时,将触发 onChanged() 方法:
// 通过调用 observe() 来观察 LiveData 对象,传入 LifecycleOwner 和 Observer。
wordViewModel.allWords.observe(this){ words ->words.let { adapter.submitList(it) }
}
浮动按钮点击事件,将打开 NewWordActivity ,这里使用了registerForActivityResult 方法和 ActivityResultContracts,因为 startActivityForResult 和 onActivityResult 方法在 Android 11(API 30)中已被弃用。
完整代码:
package com.alex.roomwordssampleimport android.app.Activity
import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Toast
import androidx.activity.viewModels
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.floatingactionbutton.FloatingActionButtonimport androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.result.ActivityResultLauncher
class MainActivity : AppCompatActivity() {// 请求代码,打开 NewWordActivity 时使用
// private val newWordActivityRequestCode = 1private lateinit var newWordActivityLauncher: ActivityResultLauncher<Intent>// 通过 viewModels 委托属性实现ViewModel的实例化private val wordViewModel: WordViewModel by viewModels {WordViewModelFactory((application as WordsApplication).repository)}override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)val recyclerView=findViewById<RecyclerView>(R.id.recyclerview)val adapter=WordListAdapter()recyclerView.adapter=adapterrecyclerView.layoutManager= LinearLayoutManager(this)// 通过调用 observe() 来观察 LiveData 对象,传入 LifecycleOwner 和 Observer。wordViewModel.allWords.observe(this){ words ->words.let { adapter.submitList(it) }}val fab = findViewById<FloatingActionButton>(R.id.fab)newWordActivityLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->if (result.resultCode == Activity.RESULT_OK) {val data: Intent? = result.datadata?.getStringExtra(NewWordActivity.EXTRA_REPLY)?.let { reply ->val word = Word(reply)wordViewModel.insert(word)}} else {Toast.makeText(applicationContext,R.string.empty_not_saved,Toast.LENGTH_LONG).show()}}fab.setOnClickListener {val intent = Intent(this@MainActivity, NewWordActivity::class.java)// startActivityForResult 和 onActivityResult 方法在 Android 11(API 30)中已被弃用。// 取而代之的是 registerForActivityResult 方法和 ActivityResultContracts 类。
// startActivityForResult(intent, newWordActivityRequestCode)newWordActivityLauncher.launch(intent)}}// override fun onActivityResult(requestCode: Int, resultCode: Int, intentData: Intent?) {
// super.onActivityResult(requestCode, resultCode, intentData)
//
// if (requestCode == newWordActivityRequestCode && resultCode == Activity.RESULT_OK) {
// intentData?.getStringExtra(NewWordActivity.EXTRA_REPLY)?.let { reply ->
// val word = Word(reply)
// wordViewModel.insert(word)
// }
// } else {
// Toast.makeText(
// applicationContext,
// R.string.empty_not_saved,
// Toast.LENGTH_LONG
// ).show()
// }
// }}


相关文章:
开启Android学习之旅-2-架构组件实现数据列表及添加(kotlin)
Android Jetpack 体验-官方codelab 1. 实现功能 使用 Jetpack 架构组件 Room、ViewModel 和 LiveData 设计应用;从sqlite获取、保存、删除数据;sqlite数据预填充功能;使用 RecyclerView 展示数据列表; 2. 使用架构组件 架构组…...
leetcode 动态规划(最后一块石头的重量II、目标和、一和零)
1049.最后一块石头的重量II 力扣题目链接(opens new window) 题目难度:中等 有一堆石头,每块石头的重量都是正整数。 每一回合,从中选出任意两块石头,然后将它们一起粉碎。假设石头的重量分别为 x 和 y,且 x < …...
JavaWeb-HTTP
一、概念 HTTP:HyperText Transfer Protocol,超文本传输协议。读者应该不是第一次接触这个名词,但可能仍然不是很理解,笔者将逐一解释。 HyperText(超文本):根据维斯百科,Hypertex…...
算法训练营第四十二天|动态规划:01背包理论基础 416. 分割等和子集
目录 动态规划:01背包理论基础416. 分割等和子集 动态规划:01背包理论基础 文章链接:代码随想录 题目链接:卡码网:46. 携带研究材料 01背包问题 二维数组解法: #include <bits/stdc.h> using namesp…...
前端 JS篇快问快答
问题:常见的特殊字符(不包括空格\s) 正则表达式为: 回答:/[!#$%^&*()\-_{};:",.<>/?[\]~|]/ (加粗的紫色字符都是特殊字符) 问题:常见的特殊字符(包括…...
vue/vue3/js来动态修改我们的界面浏览器上面的文字和图标
前言: 整理vue/vue3项目中修改界面浏览器上面的文字和图标的方法。 效果: vue2/vue3: 默认修改 public/index.html index.html <!DOCTYPE html> <html lang"en"><head><link rel"icon" type"image/sv…...
MobaXterm SSH 免密登录配置
文章目录 1.简介2.SSH 免密登录配置第一步:点击 Session第二步:选择 SSH第三步:输入服务器地址与用户名第四步:设置会话名称第五步:点击 OK 并输入密码 3.密码管理4.小结参考文献 1.简介 MobaXterm 是一个功能强大的终…...
霍兰德职业兴趣测试:找到与你性格匹配的职业
霍兰德职业兴趣理论 约翰霍兰德(John Holland)是美国约翰霍普金斯大学心理学教授,美国著名的职业指导专家。他于1959年提出了具有广泛社会影响的职业兴趣理论。认为人的人格类型、兴趣与职业密切相关,兴趣是人们活动的巨大动力&a…...
LVGL学习笔记 显示和隐藏 对象的属性标志位 配置
在显示GUI的过程中需要对某些对象进行临时隐藏或临时显示,因此需要对该对象的FLAG进行配置就可以实现对象的显示和隐藏了. 调用如下接口可以实现: lv_obj_add_flag(user_obj, LV_OBJ_FLAG_HIDDEN);//隐藏对象lv_obj_clear_flag(user_obj, LV_OBJ_FLAG_HIDDEN);//取消隐藏实现的…...
cuda上使用remap函数
在使用opencv中的remap函数时,发现运行时间太长了,如果使用视频流进行重映射时根本不能实时,因此只能加速 1.使用opencv里的cv::cuda::remap函数 cv::cuda::remap函数头文件是#include <opencv2/cudawarping.hpp>,编译ope…...
【JaveWeb教程】(18) MySQL数据库开发之 MySQL数据库设计-DDL 如何查询、创建、使用、删除数据库数据表 详细代码示例讲解
目录 2. 数据库设计-DDL2.1 项目开发流程2.2 数据库操作2.2.1 查询数据库2.2.2 创建数据库2.2.3 使用数据库2.2.4 删除数据库 2.3 图形化工具2.3.1 介绍2.3.2 安装2.3.3 使用2.2.3.1 连接数据库2.2.3.2 操作数据库 2.3 表操作2.3.1 创建2.3.1.1 语法2.3.1.2 约束2.3.1.3 数据类…...
ElasticSearch学习笔记-SpringBoot整合Elasticsearch7
项目最近需要接入Elasticsearch7,顺带记录下笔记。 Elasticsearch依赖包版本 <properties><elasticsearch.version>7.9.3</elasticsearch.version><elasticsearch.rest.version>7.9.3</elasticsearch.rest.version> </propertie…...
[足式机器人]Part2 Dr. CAN学习笔记 - Ch02动态系统建模与分析
本文仅供学习使用 本文参考: B站:DR_CAN Dr. CAN学习笔记 - Ch02动态系统建模与分析 1. 课程介绍2. 电路系统建模、基尔霍夫定律3. 流体系统建模4. 拉普拉斯变换(Laplace)传递函数、微分方程4.1 Laplace Transform 拉式变换4.2 收…...
【一周年创作总结】人生是远方的无尽旷野呀
那一眼瞥见的伟大的灵魂,却似模糊的你和我 文章目录 📒各个阶段的experience🔎大一寒假🔎大一下学期🔎大一暑假🔎大二上学期(现在) 🍔相遇CSDN🛸自媒体&#…...
金融帝国实验室(Capitalism Lab)V10版本游戏平衡性优化与改进
即将推出的V10版本中的各种游戏平衡性优化与改进: ————————————— 一、当玩家被提议收购一家即将破产的公司时,显示商业秘密。 当一家公司濒临破产,玩家被提议收购该公司时,如果玩家有兴趣评估该公司,则无…...
[SpringBoot]接口的多实现:选择性注入SpringBoot接口的实现类
最近在项目中遇到两种情况,准备写个博客记录一下。 情况说明:Service层一个接口是否可以存在多个具体实现,此时应该如何调用Service(的具体实现)? 其实之前的项目中也遇到过这种情况,只不过我采…...
北京大学 wlw机器学习2022春季期末试题分析
北京大学 wlw机器学习2022春季期末试题分析 前言新的开始第一题第二题第三题 前言 你好! 这是你第一次使用 Markdown编辑器 所展示的欢迎页。如果你想学习如何使用Markdown编辑器, 可以仔细阅读这篇文章,了解一下Markdown的基本语法知识。 新的开始 第…...
前端文件下载方法(包含get和post)
export const downloadFileWithIframe (url, name) > {const iframe document.createElement(iframe);iframe.style.display none; // 防止影响页面iframe.style.height 0; // 防止影响页面iframe.name name;iframe.src url;document.body.appendChild(iframe); // 这…...
高性能、可扩展、支持二次开发的企业电子招标采购系统源码
在数字化时代,企业需要借助先进的数字化技术来提高工程管理效率和质量。招投标管理系统作为企业内部业务项目管理的重要应用平台,涵盖了门户管理、立项管理、采购项目管理、采购公告管理、考核管理、报表管理、评审管理、企业管理、采购管理和系统管理等…...
2645. 构造有效字符串的最少插入数
Problem: 2645. 构造有效字符串的最少插入数 文章目录 解题思路解决方法复杂度分析代码实现 解题思路 解决此问题需要确定如何以最小的插入次数构造一个有效的字符串。首先,我们需要确定开头的差距,然后决定中间的补足,最后决定末尾的差距。…...
循环冗余码校验CRC码 算法步骤+详细实例计算
通信过程:(白话解释) 我们将原始待发送的消息称为 M M M,依据发送接收消息双方约定的生成多项式 G ( x ) G(x) G(x)(意思就是 G ( x ) G(x) G(x) 是已知的)࿰…...
使用分级同态加密防御梯度泄漏
抽象 联邦学习 (FL) 支持跨分布式客户端进行协作模型训练,而无需共享原始数据,这使其成为在互联和自动驾驶汽车 (CAV) 等领域保护隐私的机器学习的一种很有前途的方法。然而,最近的研究表明&…...
linux 错误码总结
1,错误码的概念与作用 在Linux系统中,错误码是系统调用或库函数在执行失败时返回的特定数值,用于指示具体的错误类型。这些错误码通过全局变量errno来存储和传递,errno由操作系统维护,保存最近一次发生的错误信息。值得注意的是,errno的值在每次系统调用或函数调用失败时…...
springboot整合VUE之在线教育管理系统简介
可以学习到的技能 学会常用技术栈的使用 独立开发项目 学会前端的开发流程 学会后端的开发流程 学会数据库的设计 学会前后端接口调用方式 学会多模块之间的关联 学会数据的处理 适用人群 在校学生,小白用户,想学习知识的 有点基础,想要通过项…...
使用Spring AI和MCP协议构建图片搜索服务
目录 使用Spring AI和MCP协议构建图片搜索服务 引言 技术栈概览 项目架构设计 架构图 服务端开发 1. 创建Spring Boot项目 2. 实现图片搜索工具 3. 配置传输模式 Stdio模式(本地调用) SSE模式(远程调用) 4. 注册工具提…...
掌握 HTTP 请求:理解 cURL GET 语法
cURL 是一个强大的命令行工具,用于发送 HTTP 请求和与 Web 服务器交互。在 Web 开发和测试中,cURL 经常用于发送 GET 请求来获取服务器资源。本文将详细介绍 cURL GET 请求的语法和使用方法。 一、cURL 基本概念 cURL 是 "Client URL" 的缩写…...
wpf在image控件上快速显示内存图像
wpf在image控件上快速显示内存图像https://www.cnblogs.com/haodafeng/p/10431387.html 如果你在寻找能够快速在image控件刷新大图像(比如分辨率3000*3000的图像)的办法,尤其是想把内存中的裸数据(只有图像的数据,不包…...
破解路内监管盲区:免布线低位视频桩重塑停车管理新标准
城市路内停车管理常因行道树遮挡、高位设备盲区等问题,导致车牌识别率低、逃费率高,传统模式在复杂路段束手无策。免布线低位视频桩凭借超低视角部署与智能算法,正成为破局关键。该设备安装于车位侧方0.5-0.7米高度,直接规避树枝遮…...
mac:大模型系列测试
0 MAC 前几天经过学生优惠以及国补17K入手了mac studio,然后这两天亲自测试其模型行运用能力如何,是否支持微调、推理速度等能力。下面进入正文。 1 mac 与 unsloth 按照下面的进行安装以及测试,是可以跑通文章里面的代码。训练速度也是很快的。 注意…...
FFmpeg avformat_open_input函数分析
函数内部的总体流程如下: avformat_open_input 精简后的代码如下: int avformat_open_input(AVFormatContext **ps, const char *filename,ff_const59 AVInputFormat *fmt, AVDictionary **options) {AVFormatContext *s *ps;int i, ret 0;AVDictio…...
