Room + LiveData + RecyclerView 案例
案例环境说明
- Android Studio: Android Studio Ladybug | 2024.2.1 Canary 7
- gradle version: gradle-8.13
- AGP version: 8.7.0-alpha07
- SDK version: API 34 ("UpsideDownCake"; Android 14.0)
- JDK version: java 21(Android Studio内置)
Dependency
// build.gradle.kts
dependencies {
implementation ("androidx.core:core-ktx:1.13.1")
implementation ("androidx.appcompat:appcompat:1.7.0")
implementation ("com.google.android.material:material:1.12.0")
// Lifecycle - ViewModel & LiveData
implementation ("androidx.lifecycle:lifecycle-viewmodel:2.8.7")
implementation ("androidx.lifecycle:lifecycle-livedata:2.8.7")
// Room Database
implementation ("androidx.room:room-runtime:2.6.1")
annotationProcessor ("androidx.room:room-compiler:2.6.1")
// RecyclerView
implementation ("androidx.recyclerview:recyclerview:1.3.2")
// 其他你可能需要的
implementation ("androidx.constraintlayout:constraintlayout:2.1.4")
}
Code Implementation
1. layout(activity_main.xml, item_user.xml)
<-- activity_main.xml !-->
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:orientation="vertical"
android:padding="16dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:spacing="8dp">
<EditText
android:id="@+id/et_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:autofillHints=""
android:hint="@string/input_name"
android:inputType="textPersonName"
android:minHeight="48dp" />
<EditText
android:id="@+id/et_password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:autofillHints=""
android:hint="@string/input_password"
android:inputType="textPassword"
android:minHeight="48dp" />
<Button
android:id="@+id/btn_add_user"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/add" />
</LinearLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/user_rv"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="16dp" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
<-- item_user.xml !-->
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:background="#F5F5F5"
android:baselineAligned="false"
android:gravity="center_vertical"
android:orientation="horizontal"
android:padding="12dp">
<LinearLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="3"
android:orientation="vertical">
<TextView
android:id="@+id/tv_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#888888"
android:textSize="28sp" />
<TextView
android:id="@+id/tv_password"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="18dp"
android:textColor="#888888"
android:textSize="28sp" />
</LinearLayout>
</LinearLayout>
字符串资源根据含义生成即可
2. 数据层(User, UserDao, AppDatabase, UserRepository)
// 创建四个不同的文件
// User Entity
@Entity(tableName = "users1")
data class User(
val name: String,
val password: String,
@PrimaryKey(autoGenerate = true)
val id: Int = 0
)
// DAO
@Dao
interface UserDao {
@Query("SELECT * FROM users1 ORDER BY name ASC")
fun getAllUsers(): LiveData<List<User?>?>? // 返回 LiveData
@Insert
fun insert(user: User) : Long
@Delete
fun delete(user: User?)
}
// AppDatabase
@Database(entities = [User::class], version = 1, exportSchema = false)
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
companion object {
@Volatile
private var INSTANCE: AppDatabase? = null
fun getInstance(context: Context): AppDatabase {
return INSTANCE ?: synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
AppDatabase::class.java,
"app_database_1"
).build()
INSTANCE = instance
instance
}
}
}
}
// Repository
class UserRepository(application: Application) {
private val db: AppDatabase = AppDatabase.getInstance(application)
private val userDao: UserDao = db.userDao()
fun getUsers(): LiveData<List<User?>?>? {
val data = userDao.getAllUsers()
return data
}
fun insert(user: User) {
Thread {
val id = userDao.insert(user)
}.start()
}
}
3. ViewModel 层
class UserViewModel(application: Application) : AndroidViewModel(application) {
var repository: UserRepository = UserRepository(application)
private val allUsers: LiveData<List<User?>?>? = repository.getUsers()
fun getUserList(): LiveData<List<User?>?>? {
return allUsers
}
fun insert(user : User) {
repository.insert(user)
}
}
4. Activity 层 - Observer
class MainActivity : AppCompatActivity() {
private var userViewModel: UserViewModel? = null
private var adapter: UserAdapter? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val recyclerView = findViewById<RecyclerView>(R.id.user_rv)
adapter = UserAdapter()
recyclerView.adapter = adapter
recyclerView.layoutManager = LinearLayoutManager(this)
ViewModelProvider(this)[UserViewModel::class.java].also {
userViewModel = it
}
val addBtn = findViewById<Button>(R.id.btn_add_user)
addBtn.setOnClickListener {
val name: EditText = findViewById(R.id.et_name)
val password: EditText = findViewById<EditText?>(R.id.et_password)
Log.d(
"MainActivity",
"setOnClickListener: name: $name, password: ${password}"
)
val user = User(
name.text.toString(), password.text.toString()
)
userViewModel!!.insert(user)
name.text.clear()
password.text.clear()
}
// 👇 关键:观察 LiveData
userViewModel!!.getUserList()!!.observe(
this, { users: List<User?>? ->
// 当数据库变化时,这个回调会被自动触发
adapter!!.setUsers(users) // 调用 adapter.setUsersView()
})
}
}
5. RecyclerView Adapter
private var users: List<User?>? = ArrayList()
// setUsersView() 的实现
@SuppressLint("NotifyDataSetChanged")
fun setUsers(users: List<User?>?) {
this.users = users // 更新内部数据
notifyDataSetChanged() // 通知 RecyclerView 刷新
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserViewHolder {
val itemView: View = LayoutInflater.from(parent.context)
.inflate(R.layout.item_user, parent, false)
return UserViewHolder(itemView)
}
override fun onBindViewHolder(holder: UserViewHolder, position: Int) {
val currentUser = users?.get(position)
if (currentUser != null) {
holder.etName.text = currentUser.name
holder.etPassword.text = currentUser.password
}
if (currentUser != null) {
holder.etPassword.text = currentUser.password
}
}
override fun getItemCount(): Int {
return users!!.size
}
class UserViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var etName: TextView = itemView.findViewById<EditText>(R.id.tv_name)
var etPassword: TextView = itemView.findViewById<EditText>(R.id.tv_password)
}
}
数据流分析
┌─────────────────────────────────────────────────────────────────────┐
│ UI Layer │
│ │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ MainActivity │ │
│ │ │ │
│ │ ┌──────────────┐ click ┌──────────────────────────────┐ │ │
│ │ │ EditText x2 │────────>│ btn_add_user (Button) │ │ │
│ │ │ + Button │ │ 构造 User 对象 │ │ │
│ │ └──────────────┘ └──────────┬───────────────────┘ │ │
│ │ │ │ │
│ │ ① insert(user) │ │
│ │ │ │ │
│ │ ┌───────────────────────┐ ▼ │ │
│ │ │ RecyclerView │ ┌─────────────────┐ │ │
│ │ │ └─ UserAdapter │ │ UserViewModel │ │ │
│ │ │ setUsers(list) │ └────┬──────▲─────┘ │ │
│ │ └──────────▲────────────┘ │ │ │ │
│ │ │ │ │ │ │
│ │ ⑥ adapter.setUsers() │ ⑤ LiveData.observe() │ │
│ │ │ │ │ │ │
│ │ └──── Observer ◄──────┘──────┘ │ │
│ └──────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
│ ① ▲ ⑤
▼ │
┌─────────────────────────────────────────────────────────────────────┐
│ Data Layer │
│ │
│ ┌─────────────────────┐ │
│ │ UserRepository │ │
│ │ │ ② insert() → new Thread │
│ │ getUsers() ──────┼────────────────────────────┐ │
│ │ insert(user) ─────┼─────┐ │ │
│ └─────────────────────┘ │ │ │
│ │ │ │
│ ③ ▼ ④ │ │
│ ┌──────────────────────────────────────────────────┼───────────┐ │
│ │ UserDao (Interface) │ │ │
│ │ │ │ │
│ │ insert(user): Long getAllUsers(): LiveData<List> │ │
│ └──────────┬───────────────────────────────────────┬───────────┘ │
│ │ │ │
│ ▼ ▲ │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ AppDatabase (Room / SQLite) │ │
│ │ 表: users1 [id, name, password] │ │
│ │ │ │
│ │ 写入后 Room 自动触发 LiveData 的 invalidation │ │
│ └──────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
两条数据流详解
- 写入流(用户添加数据)
用户输入 ──> Button click ──> ViewModel.insert() ──> Repository.insert() ──> new Thread { UserDao.insert() } ──> SQLite users1 表 - 读取流(数据自动刷新到 UI)
SQLite 数据变更
│
▼ Room InvalidationTracker 感知到 users1 表变更
LiveData 自动重新查询
│
▼
Observer 回调触发 (MainActivity)
│
▼
adapter.setUsers(newList) → notifyDataSetChanged()
│
▼
RecyclerView 重新绑定、渲染列表项
核心机制:Room + LiveData 的自动刷新
关键在于 UserDao.getAllUsers() 的返回类型是 LiveData。Room 在编译期生成的 Dao 实现中会:
- 注册一个 InvalidationTracker 监听 users1 表
- 当任何写操作(insert/update/delete)发生后,Tracker 标记该表为"脏"
- 如果有活跃的 Observer(Activity 处于 STARTED 以上生命周期),LiveData 自动重新执行 SELECT * FROM users1 ORDER BY name ASC 查询
- 将新结果分发给所有 Observer
posted on 2026-04-14 21:39 _pepsicola 阅读(4) 评论(0) 收藏 举报
浙公网安备 33010602011771号