Данные, не отображаемые в recyclerview

#android #xml #kotlin #android-recyclerview

Вопрос:

В настоящее время я изучаю разработку Android и создаю приложение, которое отображает фильмы из api OMDB. Однако при запуске приложения 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_width="match_parent"
android:layout_height="wrap_content"
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_width="match_parent"
        android:layout_height="wrap_content">


        <LinearLayout
            android:id="@ id/linearLayout"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            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_width="match_parent"
                android:layout_height="wrap_content"
                android:paddingTop="10dp" />


            <TextView
                android:id="@ id/movie_year"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:paddingTop="10dp">

            </TextView>

            <TextView
                android:id="@ id/movie_rating"
                android:layout_width="match_parent"
                android:layout_height="51dp"
                android:paddingTop="10dp" />


        </LinearLayout>

        <ImageView
            android:id="@ id/movie_image"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            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_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MoviesListFragment">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@ id/recycler_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
        tools:listitem="@layout/movie_layout" />



</RelativeLayout>

 

Фрагмент списка фильмов.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



        }

    }
}


 

Movies.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>
)


 

Кинорепозиция.кт

 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

}

 

Комментарии:

1. Как вы начали отладку? Вы проверяли, вызывается ли API? Если API что-то возвращает? Если вызывается метод репозитория? Если вызывается обратный вызов наблюдения LiveData? Дайте нам некоторое представление, сделайте отладку, вот в чем суть разработки.

2. Я попытался жестко закодировать строки, но это не сработало. Таким образом, проблема не в 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
}
 

Если вы где-то его назвали и не добавили код для него, пожалуйста, отредактируйте свой вопрос с этой частью, и я постараюсь помочь 🙂

Комментарии:

1. Нет, я этого не делал, но я даже пытался жестко закодировать строки, но они все равно не появлялись, так что я не думаю, что проблема в этой функции

2. хорошо, тогда вы можете отредактировать свой вопрос и добавить код для своего MoviesPagingSource ?

3. Просто добавил файл

Ответ №2:

Удалите "/" из вашего @GET , так как вы уже добавили в baseUrl (который является частью baseUrl). Вот как разработан этот api, вы добавили "/" в свой @GET теперь ваш базовый файл вот так:
http://www.omdbapi.com//
видишь это? в / конце концов, это является причиной того, что данные не отображаются.

Комментарии:

1. @Ahmadddd4 что там сейчас написано?

2. положите ? @GET вместо этого в свой /

3. В нем ничего не говорится. Кроме того, я на 100% уверен, что это не api, так как я жестко закодировал тексты строки, и ничего не видно. Я вижу тень прокрутки, но не вижу своего вида карты

4. насколько я мог понять, это может быть проблема с api, отсутствие отображения данных означает, что вы, должно быть, сделали что-то не так с конечными точками api, если все остальное кажется нормальным, вы пробовали ? вместо / этого и, пожалуйста, опубликуйте свой MovieResponse класс

5. В представлении дизайна макетов я могу видеть изображение карты и жестко закодированные строки просто отлично, но когда я запускаю приложение, я ничего не вижу, я просто получаю тень прокрутки при попытке прокрутки