android – Recursive synthetic property accessor, Can’t get the size of list in PagingDataAdapter casue java.lang.StackOverflowError

In this app, I tried to use Paging3, Retrofit, and RXJava to get a list of popular movies, this is the link of API, the problem is in MoviesDataAdapter class when I didn’t override getItemCount fun the recyclerView didn’t show anything but the result showing in Log when I return item count, the AS shows warning notify on this method

java.lang.StackOverflowError: stack size 8192KB
        at com.mml.moviemvvm.ui.MoviesDataAdapter.getItemCount(MoviesDataAdapter.kt:32)
        at com.mml.moviemvvm.ui.MoviesDataAdapter.getItemCount(MoviesDataAdapter.kt:32)
        at com.mml.moviemvvm.ui.MoviesDataAdapter.getItemCount(MoviesDataAdapter.kt:32)
        at com.mml.moviemvvm.ui.MoviesDataAdapter.getItemCount(MoviesDataAdapter.kt:32)
        at com.mml.moviemvvm.ui.MoviesDataAdapter.getItemCount(MoviesDataAdapter.kt:32)
        at com.mml.moviemvvm.ui.MoviesDataAdapter.getItemCount(MoviesDataAdapter.kt:32)
        at com.mml.moviemvvm.ui.MoviesDataAdapter.getItemCount(MoviesDataAdapter.kt:32)
        at com.mml.moviemvvm.ui.MoviesDataAdapter.getItemCount(MoviesDataAdapter.kt:32)
        at com.mml.moviemvvm.ui.MoviesDataAdapter.getItemCount(MoviesDataAdapter.kt:32)
        at com.mml.moviemvvm.ui.MoviesDataAdapter.getItemCount(MoviesDataAdapter.kt:32)
        at com.mml.moviemvvm.ui.MoviesDataAdapter.getItemCount(MoviesDataAdapter.kt:32)
2022-06-10 22:48:58.080 10543-10543/com.mml.moviemvvm E/AndroidRuntime:     at com.mml.moviemvvm.ui.MoviesDataAdapter.getItemCount(MoviesDataAdapter.kt:32)

let’s start with model classes Movie

data class Movie(
    val id: Int,
    @SerializedName("poster_path")
    val posterPath: String,
    @SerializedName("release_date")
    val releaseDate: String,
    val title: String
)

and MovieResponse

data class MovieResponse(
    val page: Int,
    @SerializedName("results")
    val movieList: List<Movie>,
    @SerializedName("total_pages")
    val totalPages: Int,
    @SerializedName("total_results")
    val totalResults: Int
)

enum Status & NetworkState class

enum class Status {
    LOADING,
    SUCCESS,
    FAILED

}

class NetworkState(val status: Status, val msg: String) {

    companion object {

        val SUCCEED: NetworkState = NetworkState(Status.SUCCESS, "Success")
        val LOADING: NetworkState = NetworkState(Status.LOADING, "Running")
        val ERROR: NetworkState = NetworkState(Status.FAILED, "Something went wrong")
        val ENDOFLIST = NetworkState(Status.FAILED, "You have reached the end")

    }
}

TheMovieDbApi interface

interface TheMovieDbApi {

    @GET("movie/popular")
    fun getPopularMovies(@Query("page") page: Int): Single<MovieResponse>

    @GET("movie/{movie_id}")
    fun getMovieDetails(@Path("movie_id") id: Int): Single<MovieDetails>
}

TheMovieClient

const val API_KEY = "some key"
const val BASE_URL = "https://api.themoviedb.org/3/"

const val POSTER_BASE_URL = "https://image.tmdb.org/t/p/w342"

const val FIRST_PAGE = 1
const val POST_PER_PAGE = 20


object TheMovieDBClient {

    fun getClient(): TheMovieDbApi {

        val requestInterceptor = Interceptor { chain ->
            // Interceptor take only one argument which is a lambda function so parenthesis can be omitted

            val url = chain.request()
                .url()
                .newBuilder()
                .addQueryParameter("api_key", API_KEY)
                .build()

            val request = chain.request()
                .newBuilder()
                .url(url)
                .build()

            return@Interceptor chain.proceed(request)   //explicitly return a value from whit @ annotation. lambda always returns the value of the last expression implicitly
        }

        val okHttpClient = OkHttpClient.Builder()
            .addInterceptor(requestInterceptor)
            .connectTimeout(60, TimeUnit.SECONDS)
            .build()

        return Retrofit.Builder()
            .client(okHttpClient)
            .baseUrl(BASE_URL)
            .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
            .addConverterFactory(GsonConverterFactory.create())
            .build()
            .create(TheMovieDbApi::class.java)

    }
}

MoviesPagingSource class

class MoviesPagingSource(
    private val apiService: TheMovieDbApi,
) : PagingSource<Int, Movie>() {

    override fun getRefreshKey(state: PagingState<Int, Movie>): Int? {
        TODO("Not yet implemented")
    }

    override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Movie> {

        val position = params.key ?: FIRST_PAGE


            return try {
                var movieResponse = MovieResponse(3,
                    listOf(),1,1)
                 apiService.getPopularMovies(position)
                    .subscribeOn(Schedulers.io())
                    .subscribe(
                        {
                            if(it != null){
                                movieResponse = it
                                Log.e(TAG, "load: $it" )
                            }
                        }, { throwable ->
                            Log.e(TAG, "load: ${throwable.cause?.message}")
                        }
                    )



                LoadResult.Page(
                    data = movieResponse.movieList,
                    prevKey = if (position == FIRST_PAGE) null else
                        position - 1,
                    nextKey = if (movieResponse.movieList.isEmpty()) null else position + 1
                )

            } catch (ex: IOException) {
                LoadResult.Error(ex)
            } catch (ex: HttpException) {
                LoadResult.Error(ex)
            }

    }
}

MoviesLoadStateAdapter

class MoviesLoadStateAdapter(private val retry: () -> Unit) :
    LoadStateAdapter<MoviesLoadStateAdapter.LoadStateViewHolder>() {


    inner class LoadStateViewHolder(private val binding: MoviesLoadStateFooterBinding) :
        RecyclerView.ViewHolder(binding.root) {

        init {
            binding.buttonRetry.setOnClickListener {
                retry.invoke()
            }
        }

        fun bind(loadState: LoadState) {
            binding.apply {
                progressBar.isVisible = loadState is LoadState.Loading
                buttonRetry.isVisible = loadState !is LoadState.Loading
                textViewError.isVisible = loadState !is LoadState.Loading
            }
        }
    }

    override fun onBindViewHolder(holder: LoadStateViewHolder, loadState: LoadState) {
        holder.bind(loadState)
    }

    override fun onCreateViewHolder(parent: ViewGroup, loadState: LoadState): LoadStateViewHolder {
        val binding: MoviesLoadStateFooterBinding = MoviesLoadStateFooterBinding.inflate(
            LayoutInflater.from(parent.context), parent, false
        )

        return LoadStateViewHolder(binding)
    }
}

MoviesReposory class

class MoviesRepository(private val api: TheMovieDbApi) {

    fun getMoviesResults() =
        Pager(
            config = PagingConfig(
                pageSize = 20,
                maxSize = 100,
                enablePlaceholders = false,
            ),
            pagingSourceFactory = { MoviesPagingSource(api) }
        ).liveData
}

MovieDataAdapter

class MoviesDataAdapter(private val listener: MainActivity) :
    PagingDataAdapter<Movie, MoviesDataAdapter.MovieItemViewHolder>(MOVIE_COMPARATOR) {

    override fun onBindViewHolder(holder: MovieItemViewHolder, position: Int) {
        val movie = getItem(position)
        holder.bind(movie)
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MovieItemViewHolder {
        val binding = MovieListItemBinding.inflate(
            LayoutInflater.from(parent.context),
            parent, false
        )

        return MovieItemViewHolder(binding)
    }

    override fun getItemCount(): Int {
        return itemCount
    }

    companion object {
        private val MOVIE_COMPARATOR = object : DiffUtil.ItemCallback<Movie>() {
            override fun areItemsTheSame(oldItem: Movie, newItem: Movie) =
                oldItem.id == newItem.id

            override fun areContentsTheSame(oldItem: Movie, newItem: Movie) =
                oldItem == newItem
        }
    }

    interface OnClickListener{
        fun onItemClick(movie: Movie?)
    }


    inner class MovieItemViewHolder(private val binding: MovieListItemBinding) :
        RecyclerView.ViewHolder(binding.root) {

        init {
            binding.root.setOnClickListener {
                val position = bindingAdapterPosition
                if (position != RecyclerView.NO_POSITION) {
                    val item = getItem(position)
                    if (item != null) {
                        listener.onItemClick(item)
                    }
                }
            }
        }

        fun bind(movie: Movie?) {
            binding.apply {
                cvMovieTitle.text = movie?.title
                cvMovieReleaseDate.text = movie?.releaseDate
                val moviePosterURL = POSTER_BASE_URL + movie?.posterPath
                Glide.with(cardView.context)
                    .load(moviePosterURL)
                    .into(cvIvMoviePoster)
            }
        }
    }
    
}

when I run the app without overriding getItemCount fun it’s showing no result but it printed in the log

no result view

Leave a Comment