You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Room数据未显示在RecyclerView,ManageUsers页面崩溃求助

问题:导航至ManageUsers Activity时应用崩溃,RecyclerView无法显示Room数据

我正在为修改用户名功能设置ClickListener,认为Google教程中的Fragment过于冗余。当前导航至ManageUsers Activity时应用崩溃,且Room数据未显示在RecyclerView中。LogCat提示NullPointerException:itemView.findViewById(R.id.tv_UserName) must not be null。

错误日志

2022-11-25 11:43:59.427 8217-8217/com.neillbarrett.debitsandcredits E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.neillbarrett.debitsandcredits, PID: 8217
    java.lang.NullPointerException: itemView.findViewById(R.id.tv_UserName) must not be null
        at com.neillbarrett.debitsandcredits.UserListAdapter$UserViewHolder.<init>(UserListAdapter.kt:30)
        at com.neillbarrett.debitsandcredits.UserListAdapter$UserViewHolder$Companion.create(UserListAdapter.kt:51)
        at com.neillbarrett.debitsandcredits.UserListAdapter.onCreateViewHolder(UserListAdapter.kt:21)
        at com.neillbarrett.debitsandcredits.UserListAdapter.onCreateViewHolder(UserListAdapter.kt:15)
        at androidx.recyclerview.widget.RecyclerView$Adapter.createViewHolder(RecyclerView.java:7078)
        at androidx.recyclerview.widget.RecyclerView$Recycler.tryGetViewHolderForPositionByDeadline(RecyclerView.java:6235)
        at androidx.recyclerview.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:6118)
        at androidx.recyclerview.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:6114)
        at androidx.recyclerview.widget.LinearLayoutManager$LayoutState.next(LinearLayoutManager.java:2303)
        at androidx.recyclerview.widget.LinearLayoutManager.layoutChunk(LinearLayoutManager.java:1627)
        at androidx.recyclerview.widget.LinearLayoutManager.fill(LinearLayoutManager.java:1587)
        at androidx.recyclerview.widget.LinearLayoutManager.onLayoutChildren(LinearLayoutManager.java:665)
        at androidx.recyclerview.widget.RecyclerView.dispatchLayoutStep2(RecyclerView.java:4134)
        at androidx.recyclerview.widget.RecyclerView.onMeasure(RecyclerView.java:3540)
        at android.view.View.measure(View.java:26411)
        at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:7845)
        at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1552)
        at android.widget.LinearLayout.measureVertical(LinearLayout.java:842)
        at android.widget.LinearLayout.onMeasure(LinearLayout.java:721)
        at android.view.View.measure(View.java:26411)
        at androidx.constraintlayout.widget.ConstraintLayout$Measurer.measure(ConstraintLayout.java:811)
        at androidx.constraintlayout.core.widgets.analyzer.BasicMeasure.measure(BasicMeasure.java:466)
        at androidx.constraintlayout.core.widgets.analyzer.BasicMeasure.measureChildren(BasicMeasure.java:134)
        at androidx.constraintlayout.core.widgets.analyzer.BasicMeasure.solverMeasure(BasicMeasure.java:278)
        at androidx.constraintlayout.core.widgets.ConstraintWidgetContainer.measure(ConstraintWidgetContainer.java:120)
        at androidx.constraintlayout.widget.ConstraintLayout.resolveSystem(ConstraintLayout.java:1594)
        at androidx.constraintlayout.widget.ConstraintLayout.onMeasure(ConstraintLayout.java:1708)
        at android.view.View.measure(View.java:26411)
        at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:7845)
        at android.widget.FrameLayout.onMeasure(FrameLayout.java:194)
        at androidx.appcompat.widget.ContentFrameLayout.onMeasure(ContentFrameLayout.java:145)
        at android.view.View.measure(View.java:26411)
        at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:7845)
        at androidx.appcompat.widget.ActionBarOverlayLayout.onMeasure(ActionBarOverlayLayout.java:496)
        at android.view.View.measure(View.java:26411)
        at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:7845)
        at android.widget.FrameLayout.onMeasure(FrameLayout.java:194)
        at android.view.View.measure(View.java:26411)
        at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:7845)
        at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1552)
        at android.widget.LinearLayout.measureVertical(LinearLayout.java:842)
        at android.widget.LinearLayout.onMeasure(LinearLayout.java:721)
        at android.view.View.measure(View.java:26411)
        at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:7845)
        at android.widget.FrameLayout.onMeasure(FrameLayout.java:194)
        at com.android.internal.policy.DecorView.onMeasure(DecorView.java:1050)
        at android.view.View.measure(View.java:26411)
        at android.view.ViewRootImpl.performMeasure(ViewRootImpl.java:3635)

相关代码

UserListAdapter.kt

package com.neillbarrett.debitsandcredits

import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.EditText
import android.widget.TextView
import android.widget.Toast
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.neillbarrett.debitsandcredits.database.UsersTable
import com.neillbarrett.debitsandcredits.databinding.ActivityManageUsersBinding

class UserListAdapter(private val userSelect: (UsersTable?) -> Unit) :
    ListAdapter<UsersTable, UserListAdapter.UserViewHolder>(UsersComparator()) {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserListAdapter.UserViewHolder {
        val view = LayoutInflater.from(parent.context)
            .inflate(R.layout.activity_manage_users, parent, false)
        return UserViewHolder.create(parent)
    }

    override fun onBindViewHolder(holder: UserViewHolder, position: Int) {
        val current = getItem(position)
        holder.bind(current, userSelect)
    }

    class UserViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {

        private val userView: TextView = itemView.findViewById(R.id.tv_UserName)
//        var text: String? = null

        fun bind(usersTable: UsersTable?, userSelect: (UsersTable?) -> Unit, text: String = usersTable?.userName.toString()) {

            userView.text = text

            itemView.setOnClickListener { View.OnClickListener {
/*                if (View.) { }*/

                val nameSelected = userSelect(usersTable)
                //userSelect(usersTable)
                //need to assign the result of the clicklistener to the editText
                //binding.etEditName.setText(R.layout.activity_list_of_users.toString())
            }}
        }

        companion object {
            fun create(parent: ViewGroup) : UserViewHolder {
                val view: View = LayoutInflater.from(parent.context)
                    .inflate(R.layout.activity_manage_users, parent, false)
                return UserViewHolder(view)
            }
        }
    }

    class UsersComparator : DiffUtil.ItemCallback<UsersTable>() {
        override fun areItemsTheSame(oldItem: UsersTable, newItem: UsersTable): Boolean {
            return oldItem.userName == newItem.userName

        }

        override fun areContentsTheSame(oldItem: UsersTable, newItem: UsersTable): Boolean {
            return oldItem == newItem
        }
    }
}

ManageUsers.kt

package com.neillbarrett.debitsandcredits

import android.content.Context
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.text.TextUtils
import android.widget.Button
import android.widget.EditText
import android.widget.Toast
import androidx.activity.viewModels
import androidx.lifecycle.Observer
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.neillbarrett.debitsandcredits.database.CreditsAndDebitsApp
import com.neillbarrett.debitsandcredits.database.UsersTable
import com.neillbarrett.debitsandcredits.databinding.ActivityManageUsersBinding

class ManageUsers : AppCompatActivity() {

    lateinit var binding: ActivityManageUsersBinding
    lateinit var recyclerView: RecyclerView
    lateinit var editTextAddUser: EditText
    lateinit var editTextChangeUser: EditText
    lateinit var newUser: String
    var userSelect: ((UsersTable?) -> Unit) = {}
    var position: Long = 0

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        binding = ActivityManageUsersBinding.inflate(layoutInflater)
        val view = binding.root
        setContentView(view)
        //setContentView(R.layout.activity_manage_users)

        val userViewModel: UserViewModel by viewModels {
            UserViewModelFactory((application as CreditsAndDebitsApp).repository)
        }

        recyclerView = findViewById(R.id.rec_view_userList)
        editTextAddUser = findViewById(R.id.et_AddUser)
        editTextChangeUser = findViewById(R.id.et_Edit_Name)

        val adapter = UserListAdapter(userSelect)
        binding.recViewUserList.adapter = adapter
        binding.recViewUserList.layoutManager = LinearLayoutManager(this)
        //recyclerView.adapter = adapter
        //recyclerView.layoutManager = LinearLayoutManager(this)

        userViewModel.allUsers.observe(this, Observer() {user ->
            user?.let { adapter.submitList(it) }
        })

        val btnAddUser = findViewById<Button>(R.id.btn_AddUser)
        binding.btnAddUser.setOnClickListener {
//        btnAddUser.setOnClickListener {
            if (TextUtils.isEmpty(editTextAddUser.text)) {
                Toast.makeText(this, "User name cannot be empty", Toast.LENGTH_SHORT).show()
            } else {
                newUser = editTextAddUser.text.toString()
//                Log.i("Add user button", "Username put into newUser")
                userViewModel.insertUser(UsersTable(0, newUser))
//                Toast.makeText(this, "Username added to table", Toast.LENGTH_SHORT).show()
//                Log.i("Add user button", "Username added to table")
            }
        }

        val btnChangeUser = findViewById<Button>(R.id.btn_ChangeUserName)
        binding.btnChangeUserName.setOnClickListener {
//        btnChangeUser.setOnClickListener {
            Toast.makeText(this, "Selected position is ${recyclerView.getChildAdapterPosition(it)}", Toast.LENGTH_SHORT).show()
/*            if (recyclerView.getChildAdapterPosition(it) == -1) {
                Toast.makeText(this, "Select a name.", Toast.LENGTH_SHORT).show()
            } else {
                if (editTextChangeUser.text.toString() == recyclerView.adapter.toString()) {
                    Toast.makeText(this, "Name has not been changed.", Toast.LENGTH_SHORT).show()
                } else {
                    Toast.makeText(this, "Name would have been changed.", Toast.LENGTH_SHORT).show()
                    val rvItemRecId: Long
                    rvItemRecId = adapter.getItemId(position.toInt())
                    userViewModel.updateUser(UsersTable(rvItemRecId.toInt(), adapter.toString()))
                }
            }*/
        }
    }
}

UserViewModel.kt

package com.neillbarrett.debitsandcredits

import androidx.lifecycle.*
import com.neillbarrett.debitsandcredits.database.UsersTable
import kotlinx.coroutines.launch
import java.lang.IllegalArgumentException

class UserViewModel(private val repository: UserRepository) : ViewModel() {

    // Using LiveData and caching what allWords returns has several benefits:
    // - We can put an observer on the data (instead of polling for changes) and only update the
    //   the UI when the data actually changes.
    // - Repository is completely separated from the UI through the ViewModel.
    val allUsers: LiveData<List<UsersTable>> = repository.allUsers.asLiveData()

    /**
     * Launching a new coroutine to insert the data in a non-blocking way
     */

    fun insertUser(user: UsersTable) = viewModelScope.launch {
        repository.insertUser(user)
    //repository.insertUser(usersTable = List<UsersTable>())
    //repository.insertUser(UsersTable(0, userName = user.userName))
    }

    fun updateUser(user: UsersTable) = viewModelScope.launch {
        repository.updateUser(user)
    }

    fun deleteUser(user: UsersTable) = viewModelScope.launch {
        repository.deleteUser(user)
    }
}

class UserViewModelFactory(private val repository: UserRepository) : ViewModelProvider.Factory{
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        if (modelClass.isAssignableFrom(UserViewModel::class.java)) {
            @Suppress("UNCHECKED_CAST")
            return UserViewModel(repository) as T
        }
        throw IllegalArgumentException("Unknown ViewModel class")
    }
}

问题修复步骤

1. 修正RecyclerView Item布局加载错误

崩溃的核心原因是Adapter加载了Activity的布局R.layout.activity_manage_users,而不是单个Item的布局。tv_UserName属于Item布局,因此findViewById返回null。

  • 创建单独的Item布局文件(如item_user_list.xml),添加tv_UserName控件:
    <?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:padding="16dp"
        android:orientation="horizontal">
    
        <TextView
            android:id="@+id/tv_UserName"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="18sp"/>
    </LinearLayout>
    
  • 修改UserListAdapter中布局加载的代码:
    // onCreateViewHolder方法
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserListAdapter.UserViewHolder {
        return UserViewHolder.create(parent)
    }
    
    // UserViewHolder.create方法
    companion object {
        fun create(parent: ViewGroup) : UserViewHolder {
            val view: View = LayoutInflater.from(parent.context)
                .inflate(R.layout.item_user_list, parent, false) // 改为Item布局
            return UserViewHolder(view)
        }
    }
    

2. 修复ClickListener嵌套问题

原代码中嵌套了两层OnClickListener,导致点击事件无法触发:

// 原错误代码
itemView.setOnClickListener { View.OnClickListener { ... } }

// 修复后
itemView.setOnClickListener {
    userSelect(usersTable)
}

3. 实现有效的userSelect回调

在ManageUsers中创建Adapter时,传入真正的回调逻辑,用于传递选中的用户:

private var selectedUser: UsersTable? = null

// 替换原Adapter初始化代码
val adapter = UserListAdapter { user ->
    selectedUser = user
    editTextChangeUser.setText(user?.userName)
}

4. 修正DiffUtil的Item判断逻辑

原代码用用户名判断是否为同一个Item,存在重复用户名的风险,应改用主键id:

class UsersComparator : DiffUtil.ItemCallback<UsersTable>() {
    override fun areItemsTheSame(oldItem: UsersTable, newItem: UsersTable): Boolean {
        return oldItem.id == newItem.id // 改用id判断
    }

    override fun areContentsTheSame(oldItem: UsersTable, newItem: UsersTable): Boolean {
        return oldItem.userName == newItem.userName // 仅比较内容变化
    }
}

5. 修复修改用户名的逻辑

更新btnChangeUserName的点击事件,利用选中的用户对象完成更新:

binding.btnChangeUserName.setOnClickListener {
    selectedUser?.let { user ->
        val newName = editTextChangeUser.text.toString().trim()
        if (TextUtils.isEmpty(newName)) {
            Toast.makeText(this, "用户名不能为空", Toast.LENGTH_SHORT).show()
            return@setOnClickListener
        }
        if (newName == user.userName) {
            Toast.makeText(this, "用户名未修改", Toast.LENGTH_SHORT).show()
            return@setOnClickListener
        }
        // 复制用户对象并修改用户名,避免直接修改原对象
        userViewModel.updateUser(user.copy(userName = newName))
        Toast.makeText(this, "用户名已更新", Toast.LENGTH_SHORT).show()
        editTextChangeUser.setText("") // 清空输入框
    } ?: run {
        Toast.makeText(this, "请先选择一个用户", Toast.LENGTH_SHORT).show()
    }
}

内容的提问来源于stack exchange,提问作者N.Barrett

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.11 02:15:35