Room + LiveData + RecyclerView 案例
<h1 id="案例环境说明">案例环境说明</h1><ul>
<li><strong>Android Studio</strong>: Android Studio Ladybug | 2024.2.1 Canary 7</li>
<li><strong>gradle version</strong>: gradle-8.13</li>
<li><strong>AGP version</strong>: 8.7.0-alpha07</li>
<li><strong>SDK version</strong>: API 34 ("UpsideDownCake"; Android 14.0)</li>
<li><strong>JDK version</strong>: java 21(Android Studio内置)</li>
</ul>
<h1 id="dependency">Dependency</h1>
<pre><code class="language-gradle">// 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></pre>
<hr>
<h1 id="code-implementation">Code Implementation</h1>
<h2 id="1-layoutactivity_mainxml-item_userxml">1. layout(<code>activity_main.xml</code>, <code>item_user.xml</code>)</h2>
<pre><code class="language-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>
</code></pre>
<hr>
<blockquote>
<p>字符串资源根据含义生成即可</p>
</blockquote>
<h2 id="2-数据层user-userdao-appdatabase-userrepository">2. 数据层(User, UserDao, AppDatabase, UserRepository)</h2>
<pre><code class="language-kotlin">// 创建四个不同的文件
// 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 = , 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()
}
}
</code></pre>
<h2 id="3-viewmodel-层">3. ViewModel 层</h2>
<pre><code class="language-kotlin">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)
}
}
</code></pre>
<h2 id="4-activity-层---observer">4. Activity 层 - Observer</h2>
<pre><code class="language-kotlin">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).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()
})
}
}
</code></pre>
<h2 id="5-recyclerview-adapter">5. RecyclerView Adapter</h2>
<pre><code class="language-kotlin"> 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)
}
}
</code></pre>
<h1 id="数据流分析">数据流分析</h1>
<pre><code>┌─────────────────────────────────────────────────────────────────────┐
│ 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 │ │
││ │ │
││ 写入后 Room 自动触发 LiveData 的 invalidation │ │
│└──────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
</code></pre>
<p>两条数据流详解</p>
<ul>
<li>写入流(用户添加数据)<br>
用户输入 ──> Button click ──> ViewModel.insert() ──> Repository.insert() ──> new Thread {UserDao.insert() } ──> SQLite users1 表</li>
<li>读取流(数据自动刷新到 UI)<br>
SQLite 数据变更<br>
│<br>
▼Room InvalidationTracker 感知到 users1 表变更<br>
LiveData 自动重新查询<br>
│<br>
▼<br>
Observer 回调触发 (MainActivity)<br>
│<br>
▼<br>
adapter.setUsers(newList) → notifyDataSetChanged()<br>
│<br>
▼<br>
RecyclerView 重新绑定、渲染列表项</li>
</ul>
<hr>
<h2 id="核心机制room--livedata-的自动刷新">核心机制:Room + LiveData 的自动刷新</h2>
<p>关键在于 UserDao.getAllUsers() 的返回类型是 LiveData。Room 在编译期生成的 Dao 实现中会:</p>
<ol>
<li>注册一个 InvalidationTracker 监听 users1 表</li>
<li>当任何写操作(insert/update/delete)发生后,Tracker 标记该表为"脏"</li>
<li>如果有活跃的 Observer(Activity 处于 STARTED 以上生命周期),LiveData 自动重新执行 SELECT * FROM users1 ORDER BY name ASC 查询</li>
<li>将新结果分发给所有 Observer</li>
</ol><br><br>
来源:https://www.cnblogs.com/vitastic/p/19867715
頁:
[1]