Android Kotlin中EditText搜索Firebase用户致RecyclerView崩溃
问题描述
我在Android项目中采用EditText作为搜索框,结合Firebase Realtime Database实现用户搜索并将结果展示在RecyclerView中。使用addTextChangedListener()监听输入字符变化来触发搜索功能,但每次输入字母时应用都会崩溃。直接将所有用户数据加载到RecyclerView时,适配器工作正常。
相关代码
TextWatcher实现
searchBar.addTextChangedListener(object : TextWatcher{ override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) { TODO("Not yet implemented") } override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) { searchUser(p0.toString().lowercase()) } override fun afterTextChanged(p0: Editable?) { TODO("Not yet implemented") } })
搜索方法
private fun searchUser(s :String){ val query = FirebaseDatabase.getInstance().getReference("Users").orderByChild("username") query.startAt(s) query.endAt(s+"\uf8ff") //Toast.makeText(requireContext(), "Search", Toast.LENGTH_SHORT).show() query.addValueEventListener(object : ValueEventListener { @SuppressLint("NotifyDataSetChanged") override fun onDataChange(snapshot: DataSnapshot) { userList.clear() for(theSnapshot in snapshot.children){ val user = snapshot.getValue(User::class.java) userList.add(user!!) } userAdapter.notifyDataSetChanged() } override fun onCancelled(error: DatabaseError) { Toast.makeText(requireContext(), error.message, Toast.LENGTH_SHORT).show() } }) }
User数据类
data class User(var id: String? = null, var username: String? = null, var imageurl: String? = null, var fullname: String? = null )
崩溃Logcat信息
2022-10-02 04:12:26.041 17094-17094/com.data.app E/AndroidRuntime: FATAL EXCEPTION: main Process: com.data.app, PID: 17094 java.lang.NullPointerException at com.data.app.adapter.UserAdapter.onBindViewHolder(UserAdapter.kt:47) at com.data.app.adapter.UserAdapter.onBindViewHolder(UserAdapter.kt:18) at androidx.recyclerview.widget.RecyclerView$Adapter.onBindViewHolder(RecyclerView.java:7065) at androidx.recyclerview.widget.RecyclerView$Adapter.bindViewHolder(RecyclerView.java:7107) at androidx.recyclerview.widget.RecyclerView$Recycler.tryBindViewHolderByDeadline(RecyclerView.java:6012) at androidx.recyclerview.widget.RecyclerView$Recycler.tryGetViewHolderForPositionByDeadline(RecyclerView.java:6279) 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.dispatchLayout(RecyclerView.java:3851) at androidx.recyclerview.widget.RecyclerView.onLayout(RecyclerView.java:4404) at android.view.View.layout(View.java:23147) at android.view.ViewGroup.layout(ViewGroup.java:6475) at androidx.constraintlayout.widget.ConstraintLayout.onLayout(ConstraintLayout.java:1873) at android.view.View.layout(View.java:23147) at android.view.ViewGroup.layout(ViewGroup.java:6475) at android.widget.FrameLayout.layoutChildren(FrameLayout.java:332) at android.widget.FrameLayout.onLayout(FrameLayout.java:270) at android.view.View.layout(View.java:23147) at android.view.ViewGroup.layout(ViewGroup.java:6475) at androidx.constraintlayout.widget.ConstraintLayout.onLayout(ConstraintLayout.java:1873) at android.view.View.layout(View.java:23147) at android.view.ViewGroup.layout(ViewGroup.java:6475) at android.widget.FrameLayout.layoutChildren(FrameLayout.java:332) at android.widget.FrameLayout.onLayout(FrameLayout.java:270) at android.view.View.layout(View.java:23147) at android.view.ViewGroup.layout(ViewGroup.java:6475) at androidx.appcompat.widget.ActionBarOverlayLayout.onLayout(ActionBarOverlayLayout.java:536) at android.view.View.layout(View.java:23147) at android.view.ViewGroup.layout(ViewGroup.java:6475) at android.widget.FrameLayout.layoutChildren(FrameLayout.java:332) at android.widget.FrameLayout.onLayout(FrameLayout.java:270) at android.view.View.layout(View.java:23147) at android.view.ViewGroup.layout(ViewGroup.java:6475) at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1841) at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1673) at android.widget.LinearLayout.onLayout(LinearLayout.java:1582) at android.view.View.layout(View.java:23147) at android.view.ViewGroup.layout(ViewGroup.java:6475) at android.widget.FrameLayout.layoutChildren(FrameLayout.java:332) at android.widget.FrameLayout.onLayout(FrameLayout.java:270) at com.android.internal.policy.DecorView.onLayout(DecorView.java:877) at android.view.View.layout(View.java:23147) at android.view.ViewGroup.layout(ViewGroup.java:6475) at android.view.ViewRootImpl.performLayout(ViewRootImpl.java:3861) at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:3272) at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:2266) at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:8854) at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1473) at android.view.Choreographer.doCallbacks(Choreographer.java:1270) at android.view.Choreographer.doFrame(Choreographer.java:981) 2022-10-02 04:12:26.042 17094-17094/com.project.thoughtwave E/AndroidRuntime: at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:1458) at android.os.Handler.handleCallback(Handler.java:938) at android.os.Handler.dispatchMessage(Handler.java:99) at android.os.Looper.loop(Looper.java:263) at android.app.ActivityThread.main(ActivityThread.java:8296) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:612) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1006)
错误原因分析
- TextWatcher中的TODO触发异常:Kotlin的
TODO()函数默认会抛出NotImplementedError,输入时beforeTextChanged和afterTextChanged方法会被调用,直接引发崩溃。 - Firebase查询条件未生效:Firebase的
startAt()、endAt()方法会返回新的Query对象,原代码单独调用这两个方法但未赋值,导致搜索条件没有应用到查询上。 - Snapshot解析错误:遍历子节点时,错误使用父节点
snapshot而非循环变量theSnapshot解析User对象,导致无法正确获取子节点数据,得到null实例,后续Adapter绑定访问null属性触发空指针。 - 强制非空断言风险:使用
user!!强制非空,若解析失败会直接抛出空指针异常。
修复方案
1. 修正TextWatcher实现
移除TODO,改为空实现:
searchBar.addTextChangedListener(object : TextWatcher{ override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) { // 无需操作 } override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) { p0?.let { searchUser(it.toString().lowercase()) } } override fun afterTextChanged(p0: Editable?) { // 无需操作 } })
2. 修正搜索方法
- 链式调用Firebase查询方法,正确应用搜索条件
- 使用循环变量
theSnapshot解析子节点数据 - 增加空判断,避免null对象加入列表
- 使用
addListenerForSingleValueEvent避免重复监听(若需实时更新,可保留addValueEventListener但需管理监听引用)
private fun searchUser(s: String) { // 链式调用,正确构建查询 val query = FirebaseDatabase.getInstance() .getReference("Users") .orderByChild("username") .startAt(s) .endAt(s + "\uf8ff") query.addListenerForSingleValueEvent(object : ValueEventListener { @SuppressLint("NotifyDataSetChanged") override fun onDataChange(snapshot: DataSnapshot) { userList.clear() for (theSnapshot in snapshot.children) { // 使用循环变量获取子节点数据 val user = theSnapshot.getValue(User::class.java) // 空判断后加入列表 user?.let { userList.add(it) } } userAdapter.notifyDataSetChanged() } override fun onCancelled(error: DatabaseError) { Toast.makeText(requireContext(), error.message, Toast.LENGTH_SHORT).show() } }) }
3. 可选优化
如果需要实时更新搜索结果,避免每次搜索新增监听,可保存之前的ValueEventListener引用,每次搜索前移除:
// 全局保存监听引用 private var searchListener: ValueEventListener? = null private fun searchUser(s: String) { // 移除之前的监听 searchListener?.let { FirebaseDatabase.getInstance().getReference("Users").removeEventListener(it) } val query = FirebaseDatabase.getInstance() .getReference("Users") .orderByChild("username") .startAt(s) .endAt(s + "\uf8ff") searchListener = object : ValueEventListener { @SuppressLint("NotifyDataSetChanged") override fun onDataChange(snapshot: DataSnapshot) { userList.clear() for (theSnapshot in snapshot.children) { val user = theSnapshot.getValue(User::class.java) user?.let { userList.add(it) } } userAdapter.notifyDataSetChanged() } override fun onCancelled(error: DatabaseError) { Toast.makeText(requireContext(), error.message, Toast.LENGTH_SHORT).show() } } query.addValueEventListener(searchListener!!) }
内容的提问来源于stack exchange,提问作者spunky.sukh
相关产品推荐
相关产品推荐

