数据未显示在回收站视图中
Posted
技术标签:
【中文标题】数据未显示在回收站视图中【英文标题】:Data not showing in recyclerview 【发布时间】:2021-10-01 05:08:59 【问题描述】:我目前正在学习 android 开发,并且正在构建一个显示来自 OMDB api 的电影的应用程序。但是,在运行应用程序时,recyclerview 不会显示任何数据。我什至尝试对数据进行硬编码,但它仍然没有显示任何内容。我感谢任何形式的帮助。谢谢。
这是文件。
movie_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_
android:layout_
app:cardCornerRadius="30dp"
android:layout_marginTop="50dp"
xmlns:app="http://schemas.android.com/apk/res-auto"
app:cardElevation="10dp"
app:cardPreventCornerOverlap="false">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_
android:layout_>
<LinearLayout
android:id="@+id/linearLayout"
android:layout_
android:layout_
android:layout_marginStart="208dp"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="@id/movie_image"
app:layout_constraintStart_toEndOf="@id/movie_image"
app:layout_constraintTop_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.0">
<TextView
android:id="@+id/movie_title"
android:layout_
android:layout_
android:paddingTop="10dp" />
<TextView
android:id="@+id/movie_year"
android:layout_
android:layout_
android:paddingTop="10dp">
</TextView>
<TextView
android:id="@+id/movie_rating"
android:layout_
android:layout_
android:paddingTop="10dp" />
</LinearLayout>
<ImageView
android:id="@+id/movie_image"
android:layout_
android:layout_
android:layout_margin="5dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
fragment_movies_list.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_
android:layout_
tools:context=".MoviesListFragment">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_
android:layout_
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:listitem="@layout/movie_layout" />
</RelativeLayout>
MoviesListFragment.kt
package com.example.moviesapp
import android.os.Bundle
import android.view.View
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.recyclerview.widget.LinearLayoutManager
import com.example.moviesapp.databinding.FragmentMoviesListBinding
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
class MoviesListFragment : Fragment(R.layout.fragment_movies_list)
private val viewModel by viewModels<MoviesListViewModel>()
private var _binding: FragmentMoviesListBinding? = null
private val binding get() = _binding!!
override fun onViewCreated(view: View, savedInstanceState: Bundle?)
super.onViewCreated(view, savedInstanceState)
//View is inflated layout
_binding = FragmentMoviesListBinding.bind(view)
val adapter = MoviesListAdapter()
binding.apply
recyclerView.layoutManager = LinearLayoutManager(requireContext())
recyclerView.setHasFixedSize(true)
recyclerView.adapter = adapter
//Observe the movies livedata
//Use viewLifecycleOwner instead of this because the UI should stop being updated when the fragment view is destroyed
viewModel.movies.observe(viewLifecycleOwner)
//This is the lifecycle of the view of the fragment, not an instance of a fragment
//It is the paging data itself
adapter.submitData(viewLifecycleOwner.lifecycle, it)
override fun onDestroyView()
super.onDestroyView()
_binding = null
MoviesListViewModel.kt
package com.example.moviesapp
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.switchMap
import androidx.lifecycle.viewModelScope
import androidx.paging.cachedIn
import com.example.moviesapp.network.MoviesRepository
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
@HiltViewModel
class MoviesListViewModel @Inject constructor(private val repository: MoviesRepository): ViewModel()
//const of type mutable live data to observe changes for the query
private val currentQuery = MutableLiveData(DEFAULT_QUERY)
//results from search requests
//The switchMap takes a lambda parameter that will be executed when the value of currentQuery changes
//We get passed a parameter that has the new value of currentQuery
val movies = currentQuery.switchMap queryString ->
repository.getSearchResults(queryString).cachedIn(viewModelScope)//Use viewModelScope to cache in the livedata
//This function will be called from the fragment when something is typed into the search field
fun searchMovies(movieTitle: String)
currentQuery.value = movieTitle
companion object
//Creating a default value
private const val DEFAULT_QUERY = "Joker"
MoviesListAdapter.kt
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.paging.PagingDataAdapter
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions
import com.example.moviesapp.databinding.MovieLayoutBinding
import com.example.moviesapp.network.Movies
class MoviesListAdapter : PagingDataAdapter<Movies, MoviesListAdapter.MoviesListViewHolder>(
MOVIE_COMPARATOR
)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MoviesListViewHolder
val binding = MovieLayoutBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return MoviesListViewHolder(binding)
override fun onBindViewHolder(holder: MoviesListViewHolder, position: Int)
val currentItem = getItem(position)
if (currentItem != null)
holder.bind(currentItem)
class MoviesListViewHolder(private val binding: MovieLayoutBinding) :
RecyclerView.ViewHolder(binding.root)
fun bind(movie: Movies)
binding.apply
movieTitle.text = movie.title
movieYear.text = movie.year
movieRating.text = movie.rating
Glide.with(itemView)
.load(movie.imageUrl)
.centerCrop()
.transition(DrawableTransitionOptions.withCrossFade())
.error(R.drawable.ic_baseline_error_outline_24)
.into(movieImage)
companion object
private val MOVIE_COMPARATOR = object : DiffUtil.ItemCallback<Movies>()
override fun areItemsTheSame(oldItem: Movies, newItem: Movies) =
oldItem.id == newItem.id
override fun areContentsTheSame(oldItem: Movies, newItem: Movies) =
oldItem == newItem
电影.kt
package com.example.moviesapp.network
import android.os.Parcelable
import com.squareup.moshi.Json
import kotlinx.android.parcel.Parcelize
@Parcelize
data class Movies(
@Json(name= "Title") val title: String,
@Json(name="Year") val year: String,
@Json(name="Plot") val plot: String,
@Json(name="imdbRating") val rating: String,
@Json(name="imdbID") val id: String,
@Json(name="Actors") val cast: String,
@Json(name="Writer") val writers: String,
@Json(name="Director") val director: String,
@Json(name="Poster") val imageUrl: String,
): Parcelable
MoviesApi.kt
package com.example.moviesapp.network
import retrofit2.http.GET
import retrofit2.http.Query
const val OMDB_API_KEY="[mykey]"
interface MoviesApi
companion object
const val BASE_URL = "http://www.omdbapi.com/"
@GET("/")
suspend fun getMovies(
@Query("t") movieTitle: String,
@Query("page") page: Int,
@Query("type") type: String,
@Query("apikey") key: String = OMDB_API_KEY
): MoviesResponse
MoviesRepository.kt
package com.example.moviesapp.network
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.liveData
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
//We use Inject because I own this class, unlike the Retrofit and MoviesApi class
class MoviesRepository @Inject constructor(private val moviesApi: MoviesApi)
//This function will be called later on in the ViewModel
fun getSearchResults(movieTitle: String) =
Pager(
config = PagingConfig(
pageSize = 10,
//Value at which we want to start dropping items
maxSize = 50,
//Disabling placeholders for objects that haven't been loaded yet
enablePlaceholders = false
),
pagingSourceFactory = MoviesPagingSource(moviesApi, movieTitle)
//Turn this pager into a stream of paging data to get live updates
).liveData
MoviesPagingSource.kt
package com.example.moviesapp.network
import androidx.paging.PagingSource
import androidx.paging.PagingState
//Declare the const outside of class because it is not related to the class
private const val MOVIES_STARTING_PAGE_INDEX = 1
class MoviesPagingSource(
private val moviesApi: MoviesApi,
private val movieTitle: String
): PagingSource<Int, Movies>()
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Movies>
return try
val position = params.key ?: MOVIES_STARTING_PAGE_INDEX
val response = moviesApi.getMovies(movieTitle, position, "movie")
LoadResult.Page(
//Data you want to load
data = response.results,
//Calculate the number of the previous and next page
prevKey = if (position == MOVIES_STARTING_PAGE_INDEX) null else position - 1,
nextKey = if (response.results.isEmpty()) null else position + 1
)
catch (exception: Exception)
LoadResult.Error(exception)
override fun getRefreshKey(state: PagingState<Int, Movies>): Int?
//Used for subsequent refresh calls to PagingSource.load()
return state.anchorPosition?.let anchorPosition ->
state.closestPageToPosition(anchorPosition)?.prevKey?.plus(1)
?: state.closestPageToPosition(anchorPosition)?.nextKey?.minus(1)
MoviesResponse.kt
package com.example.moviesapp.network
data class MoviesResponse(
val results: List<Movies>
)
MoviesRepository.kt
package com.example.moviesapp.network
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.liveData
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
//We use Inject because I own this class, unlike the Retrofit and MoviesApi class
class MoviesRepository @Inject constructor(private val moviesApi: MoviesApi)
//This function will be called later on in the ViewModel
fun getSearchResults(movieTitle: String) =
Pager(
config = PagingConfig(
pageSize = 10,
//Value at which we want to start dropping items
maxSize = 50,
//Disabling placeholders for objects that haven't been loaded yet
enablePlaceholders = false
),
pagingSourceFactory = MoviesPagingSource(moviesApi, movieTitle)
//Turn this pager into a stream of paging data to get live updates
).liveData
【问题讨论】:
您是如何开始调试的?您是否检查过 API 是否被调用?如果 API 正在返回一些东西?如果调用存储库方法?如果调用 LiveData 的观察回调?给我们一些见解,进行调试,这就是开发的意义所在。 我尝试对字符串进行硬编码,但没有成功。所以问题不在于 API,可能与片段和/或视图模型有关,但我找不到它 【参考方案1】:您似乎没有调用此方法searchMovies
。
//This function will be called from the fragment when something is typed into the search field
fun searchMovies(movieTitle: String)
currentQuery.value = movieTitle
如果您在某处调用它并且没有为其添加代码,请使用该部分编辑您的问题,我会尽力提供帮助 :)
【讨论】:
不,我没有,但我什至尝试对字符串进行硬编码,但它仍然没有显示,所以我认为这个函数不是问题 好的,那么您可以编辑您的问题并为您的MoviesPagingSource
添加代码吗?
刚刚添加的文件【参考方案2】:
从您的@GET
中删除"/"
,因为您已经添加了baseUrl(它是baseUrl 的一部分)。这就是这个 api 的设计方式,你在 @GET
中添加了 "/"
现在你的 baseUrl 是这样的:http://www.omdbapi.com//
看到了吗?末尾多出/
,这是不显示数据的原因。
【讨论】:
@Ahmadddd4 它现在说什么? 将?
放在@GET
中,而不是/
它什么也没说。另外我是 100% 它不是 api,因为我硬编码了字符串的文本并且没有显示。我可以看到滚动阴影,但看不到我的卡片视图
据我所知,这可能是 api 问题不显示数据意味着如果其他一切看起来都很好,您必须对您的 api 端点做错事,您是否尝试过 ?
而不是 @987654331 @ 并请发布您的MovieResponse
课程
在布局的设计视图中,我可以看到卡片视图和硬编码的字符串,但是当我运行应用程序时,我什么也看不到,当我尝试滚动时,我只会看到滚动阴影以上是关于数据未显示在回收站视图中的主要内容,如果未能解决你的问题,请参考以下文章