Android RecyclerView无数据显示问题排查求助
Android搜索应用RecyclerView无内容显示问题排查与修复
问题描述
开发了一个简易搜索应用,API调用能正确获取对象并加载至movies.list,通过ViewModel观察该列表并传递给Adapter(但多数时候列表为空),Adapter已关联RecyclerView,但RecyclerView始终无内容显示。尝试调整ViewModel初始化方式、使用ViewModelFactory,怀疑列表加载速度慢于Adapter发布时机,还添加了加载动画但无效。
相关代码
ResultsFragment
package com.dragos.moviesearch.ui.main import android.os.Bundle import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment import androidx.lifecycle.ViewModelProvider import androidx.recyclerview.widget.LinearLayoutManager import com.dragos.moviesearch.databinding.FragmentResultsBinding import com.dragos.moviesearch.viewmodel.MainViewModel class ResultsFragment : Fragment() { private val viewModel: MainViewModel by lazy { ViewModelProvider(this).get(MainViewModel::class.java) } private var myadapter = ResultsAdapter() override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { val binding = FragmentResultsBinding.inflate(inflater) binding.lifecycleOwner = this viewModel.moviesList.observe(viewLifecycleOwner) { if (it != null){ println("dra avem urmatoarele filme atasate la adaptor: " + it) myadapter.info = it myadapter.notifyDataSetChanged() for(i in 0..5){ println("dra avem urmatoarele filme in resultsfragment la onCreate: " + (viewModel.moviesList.value?.get(i))) } } } binding.resultsRecyclerview.adapter = myadapter binding.resultsRecyclerview.layoutManager = LinearLayoutManager(context) return binding.root } }
MainViewModel
package com.dragos.moviesearch.viewmodel import androidx.lifecycle.* import com.dragos.moviesearch.data.Movie import com.dragos.moviesearch.data.MoviesList import com.dragos.moviesearch.repository.DataRepository import kotlinx.coroutines.launch enum class MovieApiStatus { LOADING, ERROR, DONE } class MainViewModel : ViewModel() { private val repository: DataRepository = DataRepository private var _moviesList = MutableLiveData<List<Movie>>() val moviesList: LiveData<List<Movie>> get() = _moviesList private val _status = MutableLiveData<MovieApiStatus>() val status: LiveData<MovieApiStatus> = _status private var _query = MutableLiveData<String>() val query: LiveData<String> = _query fun getMovieInfo() { viewModelScope.launch { _status.value = MovieApiStatus.LOADING try { _moviesList.value = _query.value?.let { repository.getMovieInfo(it)} _status.value = MovieApiStatus.DONE println("dra here we have the movie list at done: " + _moviesList.value) } catch (e: Exception) { _status.value = MovieApiStatus.ERROR _moviesList.value = ArrayList() } } } fun updateQuery(rawInputQuery: String) { _query.value = rawInputQuery.replace(" ","+") println("dra query-ul este: " + _query.value) } }
Binding Adapters
package com.dragos.moviesearch.utils import android.os.Build.VERSION_CODES.M import android.view.View import android.widget.ImageView import androidx.core.net.toUri import androidx.databinding.BindingAdapter import com.dragos.moviesearch.R import coil.load import com.dragos.moviesearch.viewmodel.MovieApiStatus @BindingAdapter("showPhoto") fun bindImage(imgView: ImageView, imgUrl: String?) { imgUrl?.let { val imgUri = imgUrl.toUri().buildUpon().scheme("https").build() imgView.load(imgUri) { placeholder(R.drawable.loading_animation) error(R.drawable.ic_broken_image) } } } @BindingAdapter("movieApiStatus") fun bindStatus(statusImageView: ImageView, status: MovieApiStatus?) { when (status) { MovieApiStatus.LOADING -> { statusImageView.visibility = View.VISIBLE statusImageView.setImageResource(R.drawable.loading_animation) } MovieApiStatus.ERROR -> { statusImageView.visibility = View.VISIBLE statusImageView.setImageResource(R.drawable.ic_connection_error) } else -> {statusImageView.visibility = View.GONE} }}
ResultsAdapter
package com.dragos.moviesearch.ui.main 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.dragos.moviesearch.data.Movie import com.dragos.moviesearch.data.MoviesList import com.dragos.moviesearch.databinding.MovieItemBinding import com.dragos.moviesearch.utils.bindImage class ResultsAdapter: RecyclerView.Adapter<ResultsAdapter.MovieHolder>() { var info: List<Movie> = listOf() class MovieHolder(private var binding: MovieItemBinding): RecyclerView.ViewHolder(binding.root) { fun bind(movie: Movie){ binding.itemTitle.text = movie.show.name binding.itemGenres.text = movie.show.genres.joinToString(separator = " ") bindImage(binding.movieImg, movie.show.image.medium) binding.executePendingBindings() } } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MovieHolder { return MovieHolder(MovieItemBinding.inflate(LayoutInflater.from(parent.context))) } override fun onBindViewHolder(holder: MovieHolder, position: Int) { holder.bind(info[position]) } override fun getItemCount() = info.size }
fragment_results.xml
<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <data> <variable name="viewModel" type="com.dragos.moviesearch.viewmodel.MainViewModel" /> </data> <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/secondaryTextColor"> <EditText android:id="@+id/searchMovie" style="@style/editTextStyle" android:layout_marginTop="20dp" android:layout_marginEnd="17dp" android:background="@drawable/round_corners_shape_empty" android:hint="@string/search" android:textColor="@color/whiteText" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <ImageView android:id="@+id/magnifierButton" android:layout_width="30dp" android:layout_height="30dp" android:layout_marginStart="5dp" app:layout_constraintBottom_toBottomOf="@+id/searchMovie" app:layout_constraintStart_toEndOf="@+id/searchMovie" app:layout_constraintTop_toTopOf="@+id/searchMovie" app:srcCompat="@drawable/baseline_search_24" /> <androidx.recyclerview.widget.RecyclerView android:id="@+id/resultsRecyclerview" android:layout_width="match_parent" android:layout_height="0dp" android:layout_marginTop="10dp" app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/searchMovie" /> <ImageView android:id="@+id/status_image" android:layout_width="wrap_content" android:layout_height="wrap_content" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" app:movieApiStatus="@{viewModel.status}" /> </androidx.constraintlayout.widget.ConstraintLayout> </layout>
问题排查与修复方案
1. 核心问题:搜索按钮未绑定点击事件
布局中的magnifierButton没有设置点击监听,导致用户输入查询后从未触发API请求,moviesList始终无数据。
修复:在ResultsFragment的onCreateView中添加按钮点击逻辑:
binding.magnifierButton.setOnClickListener { val query = binding.searchMovie.text.toString().trim() if (query.isNotEmpty()) { viewModel.updateQuery(query) viewModel.getMovieInfo() } }
2. 核心问题:ViewModel未绑定到布局变量
布局xml中定义了viewModel变量,但代码未将ViewModel实例赋值给binding.viewModel,导致加载状态的BindingAdapter无法生效,加载动画不显示。
修复:在binding.lifecycleOwner = this后添加:
binding.viewModel = viewModel
3. Adapter优化(可选)
改用ListAdapter自动处理数据更新,避免手动调用notifyDataSetChanged(),同时增强空安全:
class ResultsAdapter : ListAdapter<Movie, ResultsAdapter.MovieHolder>(DiffCallback) { companion object DiffCallback : DiffUtil.ItemCallback<Movie>() { override fun areItemsTheSame(oldItem: Movie, newItem: Movie): Boolean { return oldItem.show.id == newItem.show.id // 根据实际数据结构调整唯一标识 } override fun areContentsTheSame(oldItem: Movie, newItem: Movie): Boolean { return oldItem == newItem } } class MovieHolder(private var binding: MovieItemBinding) : RecyclerView.ViewHolder(binding.root) { fun bind(movie: Movie) { binding.itemTitle.text = movie.show.name binding.itemGenres.text = movie.show.genres.joinToString(separator = " ") // 处理image为空的情况,避免空指针 bindImage(binding.movieImg, movie.show.image?.medium) binding.executePendingBindings() } } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MovieHolder { return MovieHolder(MovieItemBinding.inflate(LayoutInflater.from(parent.context))) } override fun onBindViewHolder(holder: MovieHolder, position: Int) { val movie = getItem(position) holder.bind(movie) } }
同时修改Fragment中的Observer:
viewModel.moviesList.observe(viewLifecycleOwner) { movies -> movies?.let { myadapter.submitList(it) } }
4. 空安全增强
在MainViewModel的getMovieInfo()中,处理查询为空的情况,避免moviesList被赋值为null:
_moviesList.value = _query.value?.let { repository.getMovieInfo(it) } ?: emptyList()
修复后的ResultsFragment完整代码
package com.dragos.moviesearch.ui.main import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment import androidx.lifecycle.ViewModelProvider import androidx.recyclerview.widget.LinearLayoutManager import com.dragos.moviesearch.databinding.FragmentResultsBinding import com.dragos.moviesearch.viewmodel.MainViewModel class ResultsFragment : Fragment() { private val viewModel: MainViewModel by lazy { ViewModelProvider(this).get(MainViewModel::class.java) } private var myadapter = ResultsAdapter() override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { val binding = FragmentResultsBinding.inflate(inflater) binding.lifecycleOwner = this // 绑定ViewModel到布局 binding.viewModel = viewModel viewModel.moviesList.observe(viewLifecycleOwner) { movies -> movies?.let { println("dra avem urmatoarele filme atasate la adaptor: " + it) myadapter.submitList(it) // 避免索引越界 for(i in 0 until minOf(5, it.size)){ println("dra avem urmatoarele filme in resultsfragment la onCreate: " + it[i]) } } } // 设置搜索按钮点击事件 binding.magnifierButton.setOnClickListener { val query = binding.searchMovie.text.toString().trim() if (query.isNotEmpty()) { viewModel.updateQuery(query) viewModel.getMovieInfo() } } binding.resultsRecyclerview.adapter = myadapter binding.resultsRecyclerview.layoutManager = LinearLayoutManager(context) return binding.root } }
内容的提问来源于stack exchange,提问作者Dragos Toader
相关产品推荐
相关产品推荐

