使用MVVM架构时,Retrofit获取数据填充RecyclerView频繁触发IndexOutOfBoundsException异常
我来帮你拆解下这个问题——你遇到的索引越界异常,核心原因是异步请求的回调时机和RecyclerView初始化时机不匹配,再加上代码里缺少必要的非空判断,咱们一步步理清楚:
问题根源分析
Retrofit异步调用的时机差
Retrofit的enqueue是异步执行的,你在ViewModel发起请求后,可能还没等网络请求返回、回调里的集合(actorList/pictureList等)被填充数据,就已经调用了createRV方法去初始化RecyclerView。这时候集合还是空的,直接访问actorList[0]必然触发IndexOutOfBoundsException。缺少集合非空校验
就算请求成功返回数据,你在回调里直接用characterData[0],在createRV里直接取actorList[0],都没有先判断集合是否为空。万一API返回的Staff列表是空(虽然你说现在能打印数据,但极端场景要考虑),也会直接报错。
解决方案
1. 用LiveData实现数据驱动更新(符合MVVM架构)
MVVM的核心就是数据驱动UI,ViewModel应该把数据通过LiveData暴露给Activity,Activity观察LiveData的变化,只有当数据真正就绪时才更新RecyclerView:
ViewModel里的改造:
// 定义LiveData,用来承载返回的Staff数据 val staffLiveData = MutableLiveData<List<YourStaffModel>>() // 替换成你的实际数据模型 fun fetchStaffData() { val retrofit = Retrofit.Builder() .baseUrl("http://hp-api.herokuapp.com/") .addConverterFactory(GsonConverterFactory.create()) .build() val service = retrofit.create(HarryPotterApi::class.java) service.staff().enqueue(object : Callback<Staff> { override fun onResponse(call: Call<Staff>, response: Response<Staff>) { if (response.isSuccessful) { response.body()?.let { staffList -> // 数据就绪后,通过LiveData通知Activity staffLiveData.postValue(staffList) } } } override fun onFailure(call: Call<Staff>, t: Throwable) { // 这里可以处理请求失败的逻辑,比如给LiveData发空值或者错误状态 } }) }
Activity里的改造:
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.your_activity_layout) // 初始化ViewModel并观察LiveData val viewModel = ViewModelProvider(this)[YourViewModel::class.java] viewModel.staffLiveData.observe(this) { staffList -> // 只有当数据非空时才初始化RecyclerView if (!staffList.isNullOrEmpty()) { initRecyclerView(staffList) } } // 发起数据请求 viewModel.fetchStaffData() } private fun initRecyclerView(staffList: List<YourStaffModel>) { val recyclerview = findViewById<RecyclerView>(R.id.recyclerview) recyclerview.layoutManager = LinearLayoutManager(this) // 把数据转换成RecyclerView需要的ItemsViewModel val data = ArrayList<ItemsViewModel>() val firstStaff = staffList[0] data.add(ItemsViewModel(R.drawable.ic_launcher_background, firstStaff.name, "fds", "gsd")) val adapter = RVAdapter(data) recyclerview.adapter = adapter }
2. 临时修复:确保回调完成后再更新RecyclerView(如果暂时不想用LiveData)
如果你想先快速验证问题,可以在Retrofit的onResponse回调里,确认数据填充到集合后,再切回主线程调用createRV,同时加上非空判断:
override fun onResponse(call: Call<Staff>, response: Response<Staff>) { println("the response code is " +response.code()) if (response.code() == 200) { val characterData = response.body() // 先判断数据非空且有内容 characterData?.takeIf { it.isNotEmpty() }?.let { data -> pictureList.add(data[0].name) actorList.add(data[0].name) characterList.add(data[0].name) houseList.add(data[0].name) // 切回主线程更新UI runOnUiThread { createRV(pictureList, actorList, characterList, houseList) } } } }
同时在createRV里加一层校验:
fun createRV( pictureList: ArrayList<String?>, actorList: ArrayList<String>, characterList: ArrayList<String?>, houseList: ArrayList<String?>){ // 先判断集合是否为空,避免越界 if (actorList.isEmpty()) return val recyclerview = findViewById<RecyclerView>(R.id.recyclerview) recyclerview.layoutManager = LinearLayoutManager(this) val data = ArrayList<ItemsViewModel>() data.add(ItemsViewModel(R.drawable.ic_launcher_background, actorList[0],"fds","gsd")) val adapter = RVAdapter(data) recyclerview.adapter = adapter }
3. 优化数据结构(可选但推荐)
你现在用多个独立集合(pictureList/actorList等)来管理数据,很容易出现数据不同步的问题。建议把这些字段封装到一个数据类里,比如:
data class CharacterInfo( val picture: String?, val actorName: String, val characterName: String?, val house: String? )
然后用一个List<CharacterInfo>来统一管理,这样既安全又符合代码规范。
内容的提问来源于stack exchange,提问作者zain mahmood

