新增数据返回后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的绑定执行顺序错乱:
- 你在
onViewCreated里先给binding赋值了adapter和vm,此时DataBinding会立即触发所有绑定表达式求值,但这时候homeRecyclerView还没设置LayoutManager,自定义的setNoteAdapter绑定适配器刚执行完把adapter绑定到控件,submitNoteList就立刻触发了,此时首次加载数据还能正常显示。 - 从第二个Fragment返回时,Fragment的View生命周期销毁重建,DataBinding重新执行绑定逻辑,你在代码里手动给RecyclerView设置LayoutManager、adapter的操作晚于DataBinding的绑定执行时机,
submitNoteList适配器运行时,recyclerView.adapter还是null,强转抛出的异常被DataBinding框架静默捕获,最终给适配器传入空列表。 - 后续LiveData数据更新时,绑定适配器拿到的RecyclerView实例和你代码里手动设置adapter的实例状态不同步,列表自然无法刷新。
修复方案
- 把RecyclerView的基础配置(LayoutManager、adapter)全部通过DataBinding绑定完成,不要在代码里手动赋值,保证绑定顺序一致
- 给绑定适配器增加空安全判断,避免静默崩溃
- 调整
onViewCreated里的代码执行顺序,先完成控件基础配置,再设置DataBinding的lifecycleOwner和vm参数
具体修改点:
- 新增LayoutManager对应的BindingAdapter,或者直接在XML里赋值:
@BindingAdapter("bindLayoutManager") fun bindLayoutManager(recyclerView: RecyclerView, manager: RecyclerView.LayoutManager) { recyclerView.layoutManager = manager }
- 修改
submitNoteList适配器,增加空安全判断,同时把数据类型改为不可变List,避免不必要的ArrayList强转:
@BindingAdapter("submitNoteList") fun submitNoteList(recyclerView: RecyclerView, data : List<Note>?){ val adapter = recyclerView.adapter as? NoteCardAdapter ?: return adapter.setData(data ?: emptyList()) }
- 在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" />
- 调整
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
相关产品推荐
相关产品推荐

