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

新增数据返回后DataBinding适配器未向RecyclerView传值致列表为空

问题场景

基于MVVM架构开发的应用包含2个Fragment,二者分别持有独立ViewModel,从同一个Repository获取数据:

  • 第一个Fragment通过RecyclerView展示Note实体列表
  • 第二个Fragment用于向Room数据库新增Note实体
  • 项目整体接入DataBinding框架实现数据绑定
异常现象

从第一个Fragment导航到第二个Fragment,新增实体完成Room数据库更新后,返回第一个Fragment时RecyclerView列表为空。

已完成排查

经调试确认,第一个Fragment ViewModel的LiveData变量始终可以正常接收到数据,但数据传入DataBinding适配器处理时会变为空。已尝试调整LifecycleOwner与ViewModel的绑定声明顺序等公开解决方案,均未生效。

相关实现代码

Fragment 1 代码

@AndroidEntryPoint
class HomeFragment : Fragment(),
NoteCardAdapter.NoteTouchListener,PopupMenu.OnMenuItemClickListener,
SearchView.OnQueryTextListener{

    private var _binding : FragmentHomeBinding? = null
    val binding get() = _binding!!

    private val adapter by lazy {
        NoteCardAdapter(this as NoteCardAdapter.NoteTouchListener)
    }

    private val vm : HomeViewModel by viewModels()

    private val noteSharedViewModel : NoteSharedViewModel by activityViewModels()

    private var currentOption : SortOption = SortOption.ByDate

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        _binding = FragmentHomeBinding.inflate(inflater)
        return binding.root
    }


    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {

        binding.lifecycleOwner = viewLifecycleOwner
        binding.adapter = adapter
        binding.vm = vm

        vm.sortNotes(currentOption)

        binding.apply {
            homeRecyclerView.isNestedScrollingEnabled = false
            homeRecyclerView.layoutManager = LinearLayoutManager(view.context)
            homeRecyclerView.itemAnimator= NotesItemAnimator()
        }
        binding.homeSearchView.isSubmitButtonEnabled  = true
        binding.homeSearchView.setOnQueryTextListener(this as SearchView.OnQueryTextListener)

        binding.addNoteButton.setOnClickListener{
            val note = Note(
                "","","",false, activity?.getDate(), folderId = -1
            )
            noteSharedViewModel.selectNote(note)
            val action = HomeFragmentDirections.actionHomeFragmentToSingleNoteFragment(
                isNew = true
            )

            findNavController().navigate(action)
        }

        binding.ibSort.setOnClickListener{
            showSortMenu()
        }

        binding.foldersButton.setOnClickListener{
            findNavController().navigate(HomeFragmentDirections.actionHomeFragmentToFoldersFragment())
        }
    }

Fragment 1 对应ViewModel代码

@HiltViewModel
class HomeViewModel @Inject constructor (
    private val noteRepository : NoteRepository
) : ViewModel() {


    private val _notesLiveData  = MutableLiveData<ArrayList<Note>>()
    val notesLiveData : LiveData<ArrayList<Note>> = _notesLiveData

    private var currentJob : Job = Job()

    fun searchNotes(query : String, option: SortOption){
        currentJob.cancel()
        currentJob = viewModelScope.launch(Dispatchers.IO){
            noteRepository.searchAllNotes(query, option).cancellable().collect{
                _notesLiveData.postValue(it as ArrayList<Note>)
            }
        }
    }

    fun sortNotes(option: SortOption){
        currentJob.cancel()
        currentJob = viewModelScope.launch(Dispatchers.IO) {
            noteRepository.getAllNotes(option).cancellable().collect{
                _notesLiveData.postValue(it as ArrayList<Note>)
            }
        }
    }

    fun deleteNote(note : Note){
        viewModelScope.launch(Dispatchers.IO){
            noteRepository.deleteNote(note)
        }
    }

    fun updateNoteChecked(note : Note){
        viewModelScope.launch(Dispatchers.IO){
            noteRepository.insertNote(note, false)
        }
    }

}

DataBinding适配器代码

@BindingAdapter("submitNoteList")
fun submitNoteList(recyclerView: RecyclerView, data : ArrayList<Note>?){
    val adapter = recyclerView.adapter as NoteCardAdapter
    adapter.setData((data ?: arrayListOf()))
    Log.d("Debug", "Submitted ${data?.size.toString()} items to recyclerview")
}

RecyclerView 对应XML布局代码

<androidx.recyclerview.widget.RecyclerView
            tools:listitem="@layout/note_card_view"
            android:scrollbars="vertical"
            android:scrollbarStyle="outsideInset"
            android:id="@+id/homeRecyclerView"
            android:layout_width="match_parent"
            android:requiresFadingEdge="vertical"
            android:fadingEdge="vertical"
            android:fadingEdgeLength="10dp"
            android:layout_height="515dp"
            android:layout_marginTop="2dp"
            app:setNoteAdapter="@{adapter}"
            app:submitNoteList="@{vm.notesLiveData}"
            app:layout_constraintTop_toBottomOf="@id/tvRecentTitle"
            app:layout_constraintStart_toStartOf="parent"
            />
问题根因

核心问题是DataBinding的绑定执行顺序错乱:

  1. 你在onViewCreated里先给binding赋值了adapter和vm,此时DataBinding会立即触发所有绑定表达式求值,但这时候homeRecyclerView还没设置LayoutManager,自定义的setNoteAdapter绑定适配器刚执行完把adapter绑定到控件,submitNoteList就立刻触发了,此时首次加载数据还能正常显示。
  2. 从第二个Fragment返回时,Fragment的View生命周期销毁重建,DataBinding重新执行绑定逻辑,你在代码里手动给RecyclerView设置LayoutManager、adapter的操作晚于DataBinding的绑定执行时机,submitNoteList适配器运行时,recyclerView.adapter还是null,强转抛出的异常被DataBinding框架静默捕获,最终给适配器传入空列表。
  3. 后续LiveData数据更新时,绑定适配器拿到的RecyclerView实例和你代码里手动设置adapter的实例状态不同步,列表自然无法刷新。
修复方案
  • 把RecyclerView的基础配置(LayoutManager、adapter)全部通过DataBinding绑定完成,不要在代码里手动赋值,保证绑定顺序一致
  • 给绑定适配器增加空安全判断,避免静默崩溃
  • 调整onViewCreated里的代码执行顺序,先完成控件基础配置,再设置DataBinding的lifecycleOwner和vm参数

具体修改点:

  1. 新增LayoutManager对应的BindingAdapter,或者直接在XML里赋值:
@BindingAdapter("bindLayoutManager")
fun bindLayoutManager(recyclerView: RecyclerView, manager: RecyclerView.LayoutManager) {
    recyclerView.layoutManager = manager
}
  1. 修改submitNoteList适配器,增加空安全判断,同时把数据类型改为不可变List,避免不必要的ArrayList强转:
@BindingAdapter("submitNoteList")
fun submitNoteList(recyclerView: RecyclerView, data : List<Note>?){
    val adapter = recyclerView.adapter as? NoteCardAdapter ?: return
    adapter.setData(data ?: emptyList())
}
  1. 在XML里直接配置LayoutManager,移除代码里手动设置layoutManager、adapter的逻辑:
<androidx.recyclerview.widget.RecyclerView
            tools:listitem="@layout/note_card_view"
            android:scrollbars="vertical"
            android:scrollbarStyle="outsideInset"
            android:id="@+id/homeRecyclerView"
            android:layout_width="match_parent"
            android:requiresFadingEdge="vertical"
            android:fadingEdge="vertical"
            android:fadingEdgeLength="10dp"
            android:layout_height="515dp"
            android:layout_marginTop="2dp"
            app:bindLayoutManager="@{new LinearLayoutManager(context)}"
            app:setNoteAdapter="@{adapter}"
            app:submitNoteList="@{vm.notesLiveData}"
            app:layout_constraintTop_toBottomOf="@id/tvRecentTitle"
            app:layout_constraintStart_toStartOf="parent"
            />
  1. 调整onViewCreated里的代码顺序:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    // 先配置RecyclerView非绑定属性
    binding.apply {
        homeRecyclerView.isNestedScrollingEnabled = false
        homeRecyclerView.itemAnimator= NotesItemAnimator()
    }
    // 再设置DataBinding核心参数
    binding.lifecycleOwner = viewLifecycleOwner
    binding.adapter = adapter
    binding.vm = vm

    vm.sortNotes(currentOption)
    // 其余搜索、点击事件逻辑保持不变
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 15:27:18