RecyclerView提示Adapter未绑定求助:故事应用无法加载列表
RecyclerView空白及“Adapter未绑定”问题排查与修复
我正在开发一款故事分享应用,通过模拟API获取其他用户的帖子,但RecyclerView始终无法正常加载,提示“Adapter未绑定”。登录后本应通过账号token获取数据并展示列表,实际仅显示空白页面。以下是相关代码:
MainActivity代码
package com.example.makemystory.view.main import android.animation.AnimatorSet import android.animation.ObjectAnimator import android.content.Intent import android.os.Build import android.os.Bundle import android.view.Menu import android.view.View import android.view.WindowInsets import android.view.WindowManager import androidx.activity.enableEdgeToEdge import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.LinearLayoutManager import com.example.makemystory.R import com.example.makemystory.data.helper.ViewModelFactory import com.example.makemystory.data.network.StoryResponse import com.example.makemystory.view.welcome.WelcomeActivity import com.example.makemystory.databinding.ActivityMainBinding class MainActivity : AppCompatActivity() { private val viewModel by viewModels<MainViewModel> { ViewModelFactory.getInstance(this) } private lateinit var binding: ActivityMainBinding override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge() binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) setTitle("Make my Story") ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets -> val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()) v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom) insets } val layoutManager = LinearLayoutManager(this) binding.rvStory.layoutManager = layoutManager val itemDecoration = DividerItemDecoration(this, layoutManager.orientation) binding.rvStory.addItemDecoration(itemDecoration) viewModel.stories.observe(this) {storyResponse -> setStoryData(storyResponse) } viewModel.isLoading.observe(this) { showLoading(it) } viewModel.getSession().observe(this) { user -> if (!user.isLogin) { startActivity(Intent(this, WelcomeActivity::class.java)) finish() } else { viewModel.getStories() } } setupView() } private fun setStoryData(storyResponse: StoryResponse) { val consumerStory = storyResponse.listStory val adapter = StoryAdapter() adapter.submitList(consumerStory) binding.rvStory.adapter = adapter } override fun onCreateOptionsMenu(menu: Menu?): Boolean { menuInflater.inflate(R.menu.menu_main, menu) return super.onCreateOptionsMenu(menu) } private fun setupView() { @Suppress("DEPRECATION") if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { window.insetsController?.hide(WindowInsets.Type.statusBars()) } else { window.setFlags( WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN ) } supportActionBar?.hide() } private fun showLoading(isLoading: Boolean) { binding.progressBar.visibility = if (isLoading) View.VISIBLE else View.GONE } }
ViewModel代码
package com.example.makemystory.view.main import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.asLiveData import androidx.lifecycle.viewModelScope import com.example.makemystory.data.pref.UserModel import com.example.makemystory.data.helper.UserRepository import com.example.makemystory.data.network.ErrorResponse import com.example.makemystory.data.network.StoryResponse import com.google.gson.Gson import kotlinx.coroutines.launch import retrofit2.HttpException class MainViewModel(private val repository: UserRepository) : ViewModel() { private val _stories = MutableLiveData<StoryResponse>() val stories: LiveData<StoryResponse> = _stories private val _isLoading = MutableLiveData<Boolean>() val isLoading: LiveData<Boolean> = _isLoading fun getStories() { viewModelScope.launch { _isLoading.value = true try { val response = repository.getStories() _stories.value = response } catch (e: HttpException) { val errorBody = e.response()?.errorBody()?.string() if (errorBody != null && errorBody.startsWith("<html>")) { handleError("JSON not found, instead HTML was found.") } else { val errorResponse = Gson().fromJson(errorBody, ErrorResponse::class.java) val errorMessage = errorResponse?.message ?: "Unknown error" handleError(errorMessage) } } catch (e: Exception) { handleError("An error occurred: ${e.message}") } finally { _isLoading.value = false } } } private fun handleError(errorMessage: String) { _stories.value = StoryResponse(error = true, message = errorMessage) } fun getSession(): LiveData<UserModel> { return repository.getSession().asLiveData() } fun logout() { viewModelScope.launch { repository.logout() } } }
Adapter代码
package com.example.makemystory.view.main import android.content.Intent import android.view.LayoutInflater import android.view.ViewGroup import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView import com.bumptech.glide.Glide import com.example.makemystory.data.network.ListStoryItem import com.example.makemystory.databinding.ItemStoryBinding class StoryAdapter : ListAdapter<ListStoryItem, StoryAdapter.MyViewHolder>(DIFF_CALLBACK) { class MyViewHolder (val binding: ItemStoryBinding) : RecyclerView.ViewHolder(binding.root){ fun bind(story: ListStoryItem) { binding.tvTitle.text = story.name binding.tvDescription.text = story.description Glide.with(binding.root.context) .load(story.photoUrl) .into(binding.ivItem) } } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder { val binding = ItemStoryBinding.inflate(LayoutInflater.from(parent.context), parent, false) return MyViewHolder(binding) } companion object{ val DIFF_CALLBACK = object : DiffUtil.ItemCallback<ListStoryItem>() { override fun areItemsTheSame(oldItem: ListStoryItem, newItem: ListStoryItem): Boolean { return oldItem == newItem } override fun areContentsTheSame(oldItem: ListStoryItem, newItem: ListStoryItem): Boolean { return oldItem == newItem } } } }
问题排查与修复
1. Adapter未实现onBindViewHolder(核心问题)
StoryAdapter继承了ListAdapter但未重写onBindViewHolder方法,导致RecyclerView无法将数据绑定到ViewHolder,直接表现为列表空白,同时触发“Adapter未绑定”提示。
修复代码:在StoryAdapter中添加以下方法:
override fun onBindViewHolder(holder: MyViewHolder, position: Int) { val story = getItem(position) holder.bind(story) }
2. DiffUtil判断逻辑错误
当前DIFF_CALLBACK中,areItemsTheSame和areContentsTheSame均用oldItem == newItem判断。areItemsTheSame应使用item的唯一标识(如id)判断是否为同一条目,否则DiffUtil无法正确识别列表项变化。
修复代码(假设ListStoryItem包含id字段):
companion object{ val DIFF_CALLBACK = object : DiffUtil.ItemCallback<ListStoryItem>() { override fun areItemsTheSame(oldItem: ListStoryItem, newItem: ListStoryItem): Boolean { // 用唯一id判断是否为同一个item return oldItem.id == newItem.id } override fun areContentsTheSame(oldItem: ListStoryItem, newItem: ListStoryItem): Boolean { // 比较内容是否一致 return oldItem == newItem } } }
3. 错误场景下的列表处理
ViewModel的handleError方法返回的StoryResponse中listStory可能为null,直接传入submitList会清空列表。需在setStoryData中判断错误状态,显示提示而非空白。
修复代码(MainActivity的setStoryData方法):
private fun setStoryData(storyResponse: StoryResponse) { if (storyResponse.error == true) { Toast.makeText(this, storyResponse.message, Toast.LENGTH_SHORT).show() storyAdapter.submitList(emptyList()) return } val consumerStory = storyResponse.listStory ?: emptyList() storyAdapter.submitList(consumerStory) }
4. 优化Adapter实例化(可选)
每次数据变化新建StoryAdapter可行,但复用实例更高效。可在onCreate中初始化一次Adapter,后续仅更新数据:
// MainActivity添加成员变量 private lateinit var storyAdapter: StoryAdapter override fun onCreate(savedInstanceState: Bundle?) { // ... 其他代码 storyAdapter = StoryAdapter() binding.rvStory.adapter = storyAdapter // ... 其他代码 }
内容的提问来源于stack exchange,提问作者Aruvin
相关产品推荐
相关产品推荐

