Rifqi Ananda
Rifqi Ananda

Reputation: 1

Data From API Has Not Appeared Using Paging 3

I'm learning paging 3, but the data from the API doesn't appear. My code is like below:

interface PokeAPI {
    @GET("pokemon")
    fun getPokemonList() : Call<PokemonList>
 
    @GET("pokemon")
    fun getAllPokemon(
        @Query("limit") limit: Int,
        @Query("offset") offset: Int) : PokemonList
 
    @GET("pokemon/{name}")
    fun getPokemonInfo(
        @Path("name") name: String
    ) : Call<Pokemon>
}


class PokePagingSource(private val apiService: PokeAPI): PagingSource<Int, Result>() {
    private companion object {
        const val INITIAL_PAGE_INDEX = 1
    }
 
    override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Result> {
        return try {
            val position = params.key ?: INITIAL_PAGE_INDEX
            val responseData = apiService.getAllPokemon(position, params.loadSize)
 
            if (responseData.results.isEmpty()) {
                Log.e("Response Succeed!", responseData.results.toString())
            } else {
                Log.e("Response Failed!", responseData.results.toString())
            }
 
            LoadResult.Page(
                data = responseData.results,
                prevKey = if (position == INITIAL_PAGE_INDEX) null else position - 1,
                nextKey = if (responseData.results.isNullOrEmpty()) null else position + 1
            )
        } catch (exception: Exception) {
            return LoadResult.Error(exception)
        }
    }
 
    override fun getRefreshKey(state: PagingState<Int, Result>): Int? {
        return state.anchorPosition?.let { anchorPosition ->
            val anchorPage = state.closestPageToPosition(anchorPosition)
            anchorPage?.prevKey?.plus(1) ?: anchorPage?.nextKey?.minus(1)
        }
    }
}


class PokemonRepository(private val apiService: PokeAPI) {
    fun getAllPokemon(): LiveData<PagingData<Result>>{
        return Pager(
            config = PagingConfig(
                pageSize = 10
            ),
            pagingSourceFactory = {
                PokePagingSource(apiService)
            }
        ).liveData
    }
}

object Injection {
    private val api by lazy { RetrofitClient().endpoint }
 
    fun provideRepository(): PokemonRepository {
        return PokemonRepository(api)
    }
}


class PokemonViewModel(pokemonRepository: PokemonRepository) : ViewModel() {
    val allPokemonList: LiveData<PagingData<Result>> =
        pokemonRepository.getAllPokemon().cachedIn(viewModelScope)
 
}
 
class ViewModelFactory : ViewModelProvider.Factory {
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        if (modelClass.isAssignableFrom(PokemonViewModel::class.java)) {
            @Suppress("UNCHECKED_CAST")
            return PokemonViewModel(Injection.provideRepository()) as T
        }
        throw IllegalArgumentException("Unknown ViewModel class")
    }
}

`class PokemonPagingAdapter(private val context: Context) : PagingDataAdapter<Result, PokemonPagingAdapter.ViewHolder>(DIFF_CALLBACK) {

private var onItemClick: OnAdapterListener? = null

fun setOnItemClick(onItemClick: OnAdapterListener) {
    this.onItemClick = onItemClick
}

class ViewHolder(val binding: AdapterPokemonBinding) : RecyclerView.ViewHolder(binding.root) {
}

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
    return ViewHolder(
        AdapterPokemonBinding.inflate(
            LayoutInflater.from(parent.context),
            parent,
            false
        )
    )
}

override fun onBindViewHolder(holder: ViewHolder, position: Int) {
    val pokemonData = getItem(position)

    if (pokemonData != null) {

        holder.binding.apply {
            val number = if (pokemonData.url.endsWith("/")) {
                pokemonData.url.dropLast(1).takeLastWhile { it.isDigit() }
            } else {
                pokemonData.url.takeLastWhile { it.isDigit() }
            }

            val url = "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/${number}.png"

            Glide.with(context)
                .load(url)
                .transition(DrawableTransitionOptions.withCrossFade())
                .centerCrop()
                .circleCrop()
                .into(ivPokemon)

            tvNamePokemon.text = pokemonData.name

            btnDetail.setOnClickListener {
                onItemClick?.onClick(pokemonData, pokemonData.name, url)

            }
        }

    }
}

companion object {
    val DIFF_CALLBACK = object : DiffUtil.ItemCallback<Result>() {
        override fun areItemsTheSame(
            oldItem: Result,
            newItem: Result
        ): Boolean {
            return oldItem == newItem
        }

        override fun areContentsTheSame(
            oldItem: Result,
            newItem: Result
        ): Boolean {
            return oldItem.name == newItem.name
        }
    }
}

interface OnAdapterListener {
    fun onClick(data: Result, name: String, url: String)
}

}`

class FragmentPokemon: Fragment(R.layout.fragment_pokemon) {
    private var _binding : FragmentPokemonBinding? = null
    private val binding get() = _binding!!
 
    private lateinit var dataPagingAdapter: PokemonPagingAdapter
 
    private val viewModel: PokemonViewModel by viewModels {
        ViewModelFactory()
    }
 
    private lateinit var comm: Communicator
 
    override fun onStart() {
        super.onStart()
        getData()
    }
 
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        _binding = FragmentPokemonBinding.bind(view)
 
        val toolBar = requireActivity().findViewById<View>(R.id.tool_bar)
        toolBar.visibility = View.VISIBLE
        val navBar = requireActivity().findViewById<BottomNavigationView>(R.id.bottom_navigation)
        navBar.visibility = View.VISIBLE
 
        comm = requireActivity() as Communicator
 
        setupListPokemon()
    }
 
    private fun setupListPokemon(){
        dataPagingAdapter = PokemonPagingAdapter(requireContext())
        dataPagingAdapter.setOnItemClick(object: PokemonPagingAdapter.OnAdapterListener{
            override fun onClick(data: Result, name: String, url: String) {
                comm.passDataCom(name, url)
            }
        })
 
        binding.apply {
            rvPokemon.layoutManager = LinearLayoutManager(context)
            rvPokemon.setHasFixedSize(true)
            rvPokemon.adapter = dataPagingAdapter
        }
    }
 
 
    private fun getData(){
        viewModel.allPokemonList.observe(viewLifecycleOwner){
            dataPagingAdapter.submitData(lifecycle, it)
            binding.btnCoba.setOnClickListener { btn ->
                if (it == null){
                    Log.e("ResponseFailed", it.toString())
                } else Log.e("ResponseSucceed", it.toString())
            }
        }
    }
}

What's the reason? I have followed the step by step implementation of paging 3 but the data still doesn't appear either.

Upvotes: 0

Views: 160

Answers (2)

Fabrizio Farfan
Fabrizio Farfan

Reputation: 1

I think you made a mistake in your Paging Source class: these parameters don't seem to follow the API:

val position = params.key ?: INITIAL_PAGE_INDEX
val responseData = apiService.getAllPokemon(position, params.loadSize)

Instead they should be switched: in API services, limit is the first parameter, and offset (position) is the second one:

val position = params.key ?: INITIAL_PAGE_INDEX
val responseData = apiService.getAllPokemon(params.loadSize, position)

We can clean this up a bit though:

val position = params.key ?: INITIAL_PAGE_INDEX
val limit = params.loadSize
val responseData = apiService.getAllPokemon(limit, position)

Upvotes: 0

Jan B&#237;na
Jan B&#237;na

Reputation: 7278

I don't know the API you are using, but it seems that you are using it incorrectly. The getAllPokemon method has limit and offset parameters and you are calling it like apiService.getAllPokemon(position, params.loadSize), so you are using position as a limit and params.loadSize as an offset.

You should pass params.loadSize as a limit, rename INITIAL_PAGE_INDEX to INITIAL_OFFSET and set it to 0, since your API uses offsets instead of pages (at least it seems so from what you provided). The load function should then look something like this:

// get current offset
val offset = params.key ?: INITIAL_OFFSET
val responseData = apiService.getAllPokemon(limit = params.loadSize, offset = offset)
val prevKey = offset - params.loadSize
val nextKey = offset + params.loadSize

Upvotes: 0

Related Questions