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

返回含FirestoreRecyclerAdapter的MainActivity时触发索引越界错误求助

问题

开发的Android应用中,MainActivity内的RecyclerView使用FirestoreRecyclerAdapter,点击CardView跳转至PackDetailActivity正常,但从PackDetailActivity返回MainActivity时触发IndexOutOfBoundsException,错误日志如下:

E/AndroidRuntime: FATAL EXCEPTION: main
Process: edu.stanford.rkpandey.emojistatus, PID: 8891
java.lang.IndexOutOfBoundsException: Inconsistency detected. Invalid view holder adapter positionUserViewHolder{7fba77d position=2 id=-1, oldPos=0, pLpos:0 scrap [attachedScrap] tmpDetached no parent} androidx.recyclerview.widget.RecyclerView{135dcb5 VFED..... ......I. 0,0-1080,1647 #7f080177 app:id/rvUsers}, adapter:edu.stanford.rkpandey.emojistatus.MainActivity$onCreate$adapter$1@f39ef34, layout:androidx.recyclerview.widget.LinearLayoutManager@62c4f4a, context:edu.stanford.rkpandey.emojistatus.MainActivity@e5f340e
at androidx.recyclerview.widget.RecyclerView$Recycler.validateViewHolderForOffsetPosition(RecyclerView.java:6156)
at androidx.recyclerview.widget.RecyclerView$Recycler.tryGetViewHolderForPositionByDeadline(RecyclerView.java:6339)
at androidx.recyclerview.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:6300)
at androidx.recyclerview.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:6296)
at androidx.recyclerview.widget.LinearLayoutManager$LayoutState.next(LinearLayoutManager.java:2330)
at androidx.recyclerview.widget.LinearLayoutManager.layoutChunk(LinearLayoutManager.java:1631)
at androidx.recyclerview.widget.LinearLayoutManager.fill(LinearLayoutManager.java:1591)
at androidx.recyclerview.widget.LinearLayoutManager.onLayoutChildren(LinearLayoutManager.java:668)
at androidx.recyclerview.widget.RecyclerView.dispatchLayoutStep1(RecyclerView.java:4255)
at androidx.recyclerview.widget.RecyclerView.dispatchLayout(RecyclerView.java:4010)
at androidx.recyclerview.widget.RecyclerView.onLayout(RecyclerView.java:4578)
at android.view.View.layout(View.java:23694)
at android.view.ViewGroup.layout(ViewGroup.java:6413)
at androidx.constraintlayout.widget.ConstraintLayout.onLayout(ConstraintLayout.java:1873)
at android.view.View.layout(View.java:23694)
at android.view.ViewGroup.layout(ViewGroup.java:6413)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:332)
at android.widget.FrameLayout.onLayout(FrameLayout.java:270)
at android.view.View.layout(View.java:23694)
at android.view.ViewGroup.layout(ViewGroup.java:6413)
at androidx.appcompat.widget.ActionBarOverlayLayout.onLayout(ActionBarOverlayLayout.java:536)
at android.view.View.layout(View.java:23694)
at android.view.ViewGroup.layout(ViewGroup.java:6413)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:332)
at android.widget.FrameLayout.onLayout(FrameLayout.java:270)
at android.view.View.layout(View.java:23694)
at android.view.ViewGroup.layout(ViewGroup.java:6413)
at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1829)
at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1673)
at android.widget.LinearLayout.onLayout(LinearLayout.java:1582)
at android.view.View.layout(View.java:23694)
at android.view.ViewGroup.layout(ViewGroup.java:6413)
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:797)
at android.view.View.layout(View.java:23694)
at android.view.ViewGroup.layout(ViewGroup.java:6413)
at android.view.ViewRootImpl.performLayout(ViewRootImpl.java:3911)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:3298)
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:2286)
at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:8948)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1231)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1239)
at android.view.Choreographer.doCallbacks(Choreographer.java:899)
at android.view.Choreographer.doFrame(Choreographer.java:832)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:1214)
at android.os.Handler.handleCallback(Handler.java:942)
at android.os.Handler.dispatchMessage(Handler.java:99)
E/AndroidRuntime:     at android.os.Looper.loopOnce(Looper.java:201)
at android.os.Looper.loop(Looper.java:288)
at android.app.ActivityThread.main(ActivityThread.java:7898)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:936)

相关代码

Kotlin代码

MainActivity

package edu.stanford.rkpandey.emojistatus

import android.content.Intent
import android.os.Bundle
import android.view.*
import androidx.appcompat.app.AppCompatActivity
import androidx.cardview.widget.CardView
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.firebase.ui.firestore.FirestoreRecyclerAdapter
import com.firebase.ui.firestore.FirestoreRecyclerOptions
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.auth.ktx.auth
import com.google.firebase.firestore.ktx.firestore
import com.google.firebase.ktx.Firebase
import kotlinx.android.synthetic.main.activity_main.*


class MainActivity : AppCompatActivity() {

    private val db = Firebase.firestore
    private lateinit var auth: FirebaseAuth

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

    private val query = db.collection("users")
    val options = FirestoreRecyclerOptions.Builder<User>()
        .setQuery(query, User::class.java)
        .setLifecycleOwner(this).build()

    override fun onCreate(savedInstanceState: Bundle?) {

        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        auth = Firebase.auth

        val adapter = object: FirestoreRecyclerAdapter<User, UserViewHolder>(options) {

            override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserViewHolder {
                val view = LayoutInflater.from(this@MainActivity).inflate(R.layout.item_pack, parent, false)
                return UserViewHolder(view)
            }

            override fun onBindViewHolder(
                holder: UserViewHolder,
                position: Int,
                model: User
            ) {
                val tv: CardView = holder.itemView.findViewById(R.id.card_view)

                tv.setOnClickListener {
                    val intent = Intent(tv.context , PackDetailActivity::class.java)
                    startActivity(intent)
                }

            }

        }
        rvUsers.layoutManager = LinearLayoutManager(this)
        rvUsers.adapter = adapter
    }
}

PackDetailActivity

package edu.stanford.rkpandey.emojistatus

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle

class PackDetailActivity : AppCompatActivity() {
    
    override fun onCreate(savedInstanceState: Bundle?) {
    
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_pack_detail)    
    }
}

XML代码

activity_main

<?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"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/rvUsers"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

item_pack

<?xml version="1.0" encoding="utf-8"?>

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="100sp">

<androidx.cardview.widget.CardView
    android:id="@+id/card_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_marginTop="12sp"
    android:focusable="true"
    android:clickable="true"
    android:foreground="?android:attr/selectableItemBackground">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/colorPack">

    </RelativeLayout>
</androidx.cardview.widget.CardView>
</RelativeLayout>

原因与解决方案

错误原因

这个异常是由于RecyclerView的视图持有者(ViewHolder)位置与适配器数据不一致导致的,具体诱因:

  1. FirestoreRecyclerOptions被声明为类成员变量,初始化时传入的this(Activity)生命周期尚未完全就绪,导致Firestore监听的生命周期绑定出现异常。
  2. 从详情页返回时,FirestoreRecyclerAdapter的数据集可能发生更新,但RecyclerView的视图缓存未同步刷新,导致视图持有者的位置信息失效。

修复步骤

步骤1:调整FirestoreRecyclerOptions初始化时机

将options的初始化移到onCreate方法内部,确保Activity生命周期初始化完成后再绑定:

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    auth = Firebase.auth

    // 移到onCreate内部初始化options
    val query = db.collection("users")
    val options = FirestoreRecyclerOptions.Builder<User>()
        .setQuery(query, User::class.java)
        .setLifecycleOwner(this).build()

    val adapter = object: FirestoreRecyclerAdapter<User, UserViewHolder>(options) {
        // 原有代码不变
    }
    rvUsers.layoutManager = LinearLayoutManager(this)
    rvUsers.adapter = adapter
}

步骤2:手动管理适配器生命周期(推荐)

虽然setLifecycleOwner会自动管理,但手动在Activity生命周期方法中调用适配器的启动/停止方法,可避免潜在的生命周期不一致问题:

// 将adapter改为类成员变量
private lateinit var adapter: FirestoreRecyclerAdapter<User, UserViewHolder>

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    auth = Firebase.auth

    val query = db.collection("users")
    val options = FirestoreRecyclerOptions.Builder<User>()
        .setQuery(query, User::class.java)
        .build() // 不再设置LifecycleOwner

    adapter = object: FirestoreRecyclerAdapter<User, UserViewHolder>(options) {
        // 原有代码不变
    }
    rvUsers.layoutManager = LinearLayoutManager(this)
    rvUsers.adapter = adapter
}

override fun onStart() {
    super.onStart()
    adapter.startListening()
}

override fun onStop() {
    super.onStop()
    adapter.stopListening()
}

步骤3:优化点击事件设置

当前每次onBindViewHolder都会重复设置点击事件,可能导致不必要的内存占用或事件冲突,建议将点击事件移到onCreateViewHolder中:

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserViewHolder {
    val view = LayoutInflater.from(this@MainActivity).inflate(R.layout.item_pack, parent, false)
    val holder = UserViewHolder(view)
    // 在这里设置点击事件
    holder.itemView.findViewById<CardView>(R.id.card_view).setOnClickListener {
        val intent = Intent(this@MainActivity, PackDetailActivity::class.java)
        startActivity(intent)
    }
    return holder
}

override fun onBindViewHolder(holder: UserViewHolder, position: Int, model: User) {
    // 仅处理数据绑定,不再设置点击事件
}

内容的提问来源于stack exchange,提问作者Thomas Tripodi

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.09 18:05:38