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
相关产品推荐
相关产品推荐

