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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 05:01:03