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 实现中会:

  1. 注册一个 InvalidationTracker 监听 users1 表
  2. 当任何写操作(insert/update/delete)发生后,Tracker 标记该表为"脏"
  3. 如果有活跃的 Observer(Activity 处于 STARTED 以上生命周期),LiveData 自动重新执行 SELECT * FROM users1 ORDER BY name ASC 查询
  4. 将新结果分发给所有 Observer

posted on 2026-04-14 21:39  _pepsicola  阅读(4)  评论(0)    收藏  举报

导航