海韵琴行陈进琪 發表於 2026-4-14 21:39:00

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 &amp; 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">&lt;-- activity_main.xml !--&gt;
&lt;?xml version="1.0" encoding="utf-8"?&gt;
&lt;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"&gt;

    &lt;LinearLayout
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      android:fitsSystemWindows="true"
      android:orientation="vertical"
      android:padding="16dp"&gt;

      &lt;LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            android:spacing="8dp"&gt;

            &lt;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" /&gt;

            &lt;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" /&gt;

            &lt;Button
                android:id="@+id/btn_add_user"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="@string/add" /&gt;
      &lt;/LinearLayout&gt;

      &lt;androidx.recyclerview.widget.RecyclerView
            android:id="@+id/user_rv"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_marginTop="16dp" /&gt;

    &lt;/LinearLayout&gt;
&lt;/androidx.constraintlayout.widget.ConstraintLayout&gt;

&lt;-- item_user.xml !--&gt;
&lt;?xml version="1.0" encoding="utf-8"?&gt;
&lt;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"&gt;

    &lt;LinearLayout
      android:layout_width="0dp"
      android:layout_height="match_parent"
      android:layout_weight="3"
      android:orientation="vertical"&gt;

      &lt;TextView
            android:id="@+id/tv_name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="#888888"
            android:textSize="28sp" /&gt;

      &lt;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" /&gt;
    &lt;/LinearLayout&gt;

&lt;/LinearLayout&gt;
</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&lt;List&lt;User?&gt;?&gt;? // 返回 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&lt;List&lt;User?&gt;?&gt;? {
      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&lt;List&lt;User?&gt;?&gt;? = repository.getUsers()

    fun getUserList(): LiveData&lt;List&lt;User?&gt;?&gt;? {
      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&lt;RecyclerView&gt;(R.id.user_rv)
      adapter = UserAdapter()
      recyclerView.adapter = adapter
      recyclerView.layoutManager = LinearLayoutManager(this)

      ViewModelProvider(this).also {
            userViewModel = it
      }

      val addBtn = findViewById&lt;Button&gt;(R.id.btn_add_user)
      addBtn.setOnClickListener {
            val name: EditText = findViewById(R.id.et_name)
            val password: EditText = findViewById&lt;EditText?&gt;(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&lt;User?&gt;? -&gt;
                // 当数据库变化时,这个回调会被自动触发
                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&lt;User?&gt;? = ArrayList()

    // setUsersView() 的实现
    @SuppressLint("NotifyDataSetChanged")
    fun setUsers(users: List&lt;User?&gt;?) {
      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&lt;EditText&gt;(R.id.tv_name)
      var etPassword: TextView = itemView.findViewById&lt;EditText&gt;(R.id.tv_password)
    }
}
</code></pre>
<h1 id="数据流分析">数据流分析</h1>
<pre><code>┌─────────────────────────────────────────────────────────────────────┐
│                        UI Layer                                 │
│                                                                     │
│┌──────────────────────────────────────────────────────────────┐   │
││ MainActivity                                                 │   │
││                                                            │   │
││┌──────────────┐click┌──────────────────────────────┐   │   │
│││ EditText x2│────────&gt;│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&lt;List&gt;│   │
│└──────────┬───────────────────────────────────────┬───────────┘   │
│             │                                       │               │
│             ▼                                       ▲               │
│┌──────────────────────────────────────────────────────────────┐   │
││   AppDatabase (Room / SQLite)                              │   │
││   表: users1                           │   │
││                                                            │   │
││   写入后 Room 自动触发 LiveData 的 invalidation                │   │
│└──────────────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────────────┘
</code></pre>
<p>两条数据流详解</p>
<ul>
<li>写入流(用户添加数据)<br>
用户输入 ──&gt; Button click ──&gt; ViewModel.insert() ──&gt; Repository.insert() ──&gt; new Thread {UserDao.insert() } ──&gt; 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]
查看完整版本: Room + LiveData + RecyclerView 案例