Displaying images in recyclerview retrieved from retrofit - android

I'm new in android development and building a movie app in which I'm using recyclerview to display movie images and using retrofit to fetch data from themoviedb.org API.
I'm successfully getting the data from the mention URL but not able to display it on the screen.
GenreModel class:
data class Genre (
var genres: ArrayList<GenreList>
): Serializable
data class GenreList (
val id: Int,
val name: String,
val results: ArrayList<Results>
): Serializable
data class Results (
var id: Int,
var title: String,
var poster_path: String,
var release_date: String,
var original_language: String,
var overview: String
): Serializable
Function to get movie details from API:
private fun getGenreName() {
val retrofit = RetrofitClient.getInstance()
val apiInterface = retrofit.create(ApiInterface::class.java)
val genreListCall: Call<Genre> = apiInterface.getGenreDetails(Constants.APP_ID)
genreListCall.enqueue(object: Callback<Genre> {
override fun onResponse(call: Call<Genre>, response: Response<Genre>) {
if(response.isSuccessful) {
val result: Genre? = response.body() // returns genres object which contains ID and Name
val genres: ArrayList<GenreList> = result!!.genres// saves ID and Names of genres
for(x in genres) {
val id = x.id
val name = x.name // returns the genre names. Ex- action, family, drama....
val listCall: Call<GenreList> = apiInterface.getGenreMovieDetails(id, Constants.APP_ID)
listCall.enqueue(object: Callback<GenreList> {
override fun onResponse(call: Call<GenreList>, response: Response<GenreList>) {
if(response.isSuccessful) { // returns id, results[]
val result1: GenreList? = response.body()
results = result1!!.results // results[id, title, poster, language....]
}
}
override fun onFailure(call: Call<GenreList>, t: Throwable) {
Log.e("ERRORInGenreMovieList", t.message.toString())
}
})
genreList.add(GenreList(id, name, results))
genreCategories.add(name)
}
} else {
val rc = response.code()
when (rc) {
400 -> {
Log.e("Error 400", "Bad Connection")
}
404 -> {
Log.e("Error 404", "Not Found")
}
else -> {
Log.e("Error", "Generic Error")
}
}
}
}
override fun onFailure(call: Call<Genre>, t: Throwable) {
Log.e("ERROR in Genre Name", t.message.toString())
}
})
}
GenreAdapter for recyclerview:
class GenreFragmentAdapter(private val genreList: ArrayList<GenreList>, val context: Context): RecyclerView.Adapter<GenreFragmentAdapter.ViewHolder>() {
class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
var genreName:TextView = view.findViewById(R.id.genreName)
var genreListRecyclerView: RecyclerView = view.findViewById(R.id.genreListRecyclerView)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view: View = LayoutInflater.from(context).inflate(R.layout.genre_list_item, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val name = genreList[position].name
val results = genreList[position].results
holder.genreName.text = name
println("Name: $name")
setItemRecyclerView(holder.genreListRecyclerView, results)
println("Results: $results")
}
override fun getItemCount(): Int {
return genreList.size
}
private fun setItemRecyclerView(recyclerView: RecyclerView, results: ArrayList<Results>) {
val genreListAdapter = GenreResultsAdapter(results, context)
recyclerView.layoutManager = LinearLayoutManager(context, RecyclerView.HORIZONTAL, false)
recyclerView.adapter = genreListAdapter
}
}
BaseUrl: https://api.themoviedb.org
ApiInterface:
interface ApiInterface {
#GET("/3/genre/movie/list") // returns genre list
fun getGenreDetails(
#Query("api_key") api_key: String
): Call<Genre>
#GET("/3/genre/{id}/movies") // returns movie details according to specified genre
fun getGenreMovieDetails(
#Path("id") id: Int,
#Query("api_key") api_key: String
): Call<GenreList>
#GET("/3/movie/upcoming") // returns upcoming movies
fun getUpcomingMovies(
#Query("api_key") api_key: String
): Call<GenreList>
#GET("/3/movie/{id}")
fun getMovie(
#Path("id") id: Int,
#Query("api_key") api_key: String
): Call<Results>
}
RetrofitClient:
object RetrofitClient {
fun getInstance(): Retrofit {
return Retrofit.Builder()
.baseUrl(Constants.BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.client(getUnsafeOkHttpClient()!!.build())
.build()
}
private fun getUnsafeOkHttpClient(): OkHttpClient.Builder? {
return try {
// Create a trust manager that does not validate certificate chains
val trustAllCerts = arrayOf<TrustManager>(
object : X509TrustManager {
#Throws(CertificateException::class)
override fun checkClientTrusted(chain: Array<X509Certificate?>?, authType: String?) {
}
#Throws(CertificateException::class)
override fun checkServerTrusted(chain: Array<X509Certificate?>?, authType: String?) {
}
override fun getAcceptedIssuers(): Array<X509Certificate?>? {
return arrayOf()
}
}
)
// Install the all-trusting trust manager
val sslContext = SSLContext.getInstance("SSL")
sslContext.init(null, trustAllCerts, SecureRandom())
// Create an ssl socket factory with our all-trusting manager
val sslSocketFactory = sslContext.socketFactory
val builder = OkHttpClient.Builder()
builder.sslSocketFactory(sslSocketFactory, trustAllCerts[0] as X509TrustManager)
builder.hostnameVerifier { _, _ -> true }
builder
} catch (e: Exception) {
throw RuntimeException(e)
}
}
}

Related

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

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

Recyclerview doesn't show, with message error No adapter attached; skipping layout

Ii trying to run the recyclerview, but list item didn't show, and error message is No adapter attached; skipping layout
There is no error with the code, but when try to run, recyclerview doesn't show. Here the main activity, I try to show recyclerview using Parsing Json
companion object {
private val TAG = MainActivity::class.java.simpleName
}
private lateinit var binding: ActivityMainBinding
// private lateinit var adapter: UserAdapter
private var listUser = ArrayList<DataUser>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
supportActionBar?.title = "Github User Search"
binding.rvUser.setHasFixedSize(true)
val layoutManager = LinearLayoutManager(this)
val itemDecoration = DividerItemDecoration(this, layoutManager.orientation)
binding.rvUser.addItemDecoration(itemDecoration)
searchUsername()
getDataUser()
}
fun searchUsername () {
val searchManager = getSystemService(Context.SEARCH_SERVICE) as SearchManager
val searchView = binding.searchView
searchView.setSearchableInfo(searchManager.getSearchableInfo(componentName))
searchView.queryHint = resources.getString(R.string.search_hint)
searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener{
override fun onQueryTextSubmit(query: String): Boolean {
if(query.isEmpty()){
return true
} else{
listUser.clear()
getSearchUsername(query)
}
return true
}
override fun onQueryTextChange(newText: String?): Boolean {
return false
}
})
}
private fun getSearchUsername(username: String) {
binding.progressBar.visibility = View.VISIBLE
val client = AsyncHttpClient()
client.addHeader("Authorization", "token<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>")
client.addHeader("User-Agent", "request")
client.get(
"https://api.github.com/search/users?q=$username",
object : AsyncHttpResponseHandler() {
override fun onSuccess(
statusCode: Int,
headers: Array<out Header>,
responseBody: ByteArray
) {
binding.progressBar.visibility = View.VISIBLE
val result = String(responseBody)
Log.d(TAG, result)
try {
val jsonArray = JSONArray(result)
for (i in 0 until jsonArray.length()) {
val responseObject = jsonArray.getJSONObject(i)
val userName = responseObject.getString("login")
val id = responseObject.getString("id")
val avatar = responseObject.getString("avatar_url")
listUser.add(DataUser(
userName,
id.toInt(),
avatar
))
}
val adapter = UserAdapter(listUser)
binding.rvUser.adapter = adapter
} catch (e: Exception) {
Log.d(TAG, e.message.toString())
}
}
override fun onFailure(
statusCode: Int,
headers: Array<out Header>,
responseBody: ByteArray,
error: Throwable
) {
binding.progressBar.visibility = View.INVISIBLE
val errorMessage = when (statusCode) {
401 -> "$statusCode : Bad Request"
403 -> "$statusCode : Forbidden"
404 -> "$statusCode : Not Found"
else -> "$statusCode : ${error.message}"
}
Toast.makeText(
this#MainActivity, errorMessage, Toast
.LENGTH_SHORT
).show()
}
})
}
private fun getDataUser() {
binding.progressBar.visibility = View.VISIBLE
val client = AsyncHttpClient()
client.addHeader("Authorization", "token YOUR_GITHUB_API_TOKEN")
client.addHeader("User-Agent", "request")
val url = "https://api.github.com/users"
client.get(url, object : AsyncHttpResponseHandler() {
override fun onSuccess(statusCode: Int, headers: Array<out Header>?, responseBody: ByteArray?) {
binding.progressBar.visibility = View.INVISIBLE
val result = String(responseBody!!)
Log.d(TAG, result)
try {
val jsonArray = JSONArray(result)
for (i in 0 until jsonArray.length()) {
val jsonObject = jsonArray.getJSONObject(i)
val username: String = jsonObject.getString("login")
getSearchUsername(username)
}
} catch (e: Exception) {
Toast.makeText(this#MainActivity, e.message, Toast.LENGTH_SHORT)
.show()
e.printStackTrace()
}
}
override fun onFailure(statusCode: Int, headers: Array<out Header>?, responseBody: ByteArray?, error: Throwable?) {
binding.progressBar.visibility = View.INVISIBLE
val errorMessage = when (statusCode) {
401 -> "$statusCode : Bad Request"
403 -> "$statusCode : Forbidden"
404 -> "$statusCode : Not Found"
else -> "$statusCode : ${error?.message + " GIT"}"
}
Toast.makeText(this#MainActivity, errorMessage, Toast.LENGTH_LONG)
.show()
}
})
}
}
And here the adapter
class UserAdapter(private val listDataDetailUser: ArrayList<DataUser>) :
RecyclerView.Adapter<UserAdapter.ListViewHolder>() {
private lateinit var onItemClickCallback: OnItemClickCallback
fun setOnItemCLickCallback(onItemClickCallback: OnItemClickCallback) {
this.onItemClickCallback = onItemClickCallback
}
override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): ListViewHolder {
val view = LayoutInflater.from(viewGroup.context).inflate(R.layout.item_profile_list, viewGroup, false)
return ListViewHolder(view)
}
override fun onBindViewHolder(holder: ListViewHolder, position: Int) {
val data = listDataDetailUser[position]
Glide.with(holder.itemView.context)
.load(data.avatar_url)
.into(holder.avatar)
holder.username.text = data.login
}
override fun getItemCount(): Int = listDataDetailUser.size
inner class ListViewHolder(view : View) : RecyclerView.ViewHolder(view) {
val avatar: CircleImageView = view.findViewById(R.id.img_profile)
val username: TextView = view.findViewById(R.id.tv_username)
}
interface OnItemClickCallback {
fun onItemClicked(data: DataUser)
}
}
What can I try to resolve this?
You have not added LayoutManager to your Recyclerview.
Try adding LinearLayoutManager which you have created for your ItemDecoration.
binding.rvUser.setLayoutManager(layoutManager);

Why data is not loaded to the RecyclerView MVVM?

I have an android application that requests a list from the network and should display it in a recycler view but this does not happen. There are no errors in the logcat. I think the problem is in my function in ViewModel. Help me to understand
My CategoryClient class:
class CategoryClient {
companion object {
const val KEY = "5de979d34658275ac9dc2375"
}
var category: List<Category>? = null
fun loadCategory(): List<Category>? {
// x-apikey interceptor for restdb API
fun createOkHttpClient(): OkHttpClient? {
val httpClient = OkHttpClient.Builder()
httpClient.addInterceptor(object : Interceptor {
#Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): okhttp3.Response {
val original = chain.request()
val originalHttpUrl = original.url
val url = originalHttpUrl.newBuilder()
.addQueryParameter("apikey", KEY)
.build()
val requestBuilder = original.newBuilder()
.url(url)
val request = requestBuilder.build()
return chain.proceed(request)
}
})
// logging interceptor
val logging = HttpLoggingInterceptor()
logging.level = HttpLoggingInterceptor.Level.BODY
httpClient.addInterceptor(logging)
return httpClient.build()
}
val retrofit = Retrofit.Builder()
.baseUrl("https://testcategory-d6d7.restdb.io/rest/")
.addConverterFactory(GsonConverterFactory.create())
.client(createOkHttpClient())
.build()
val api = retrofit.create(CategoryApi::class.java)
api.fetchAllCategory().enqueue(object : Callback<List<Category>> {
override fun onFailure(call: Call<List<Category>>, t: Throwable) {
}
override fun onResponse(call: Call<List<Category>>, response: Response<List<Category>>) {
//Log.d(TAG, "onResponse: ${response.body()!![0].name}")
category = response.body()!!
//presenter.setupCategoryList(categoryList = category as ArrayList<Category>)
}
})
return category
}
}
My Category Activity class
val binding: ActivityCategoryBinding =
DataBindingUtil.setContentView(this#CategoryActivity, R.layout.activity_category)
val categoryViewModel =
ViewModelProviders.of(this#CategoryActivity).get(CategoryViewModel::class.java)
binding.categoryViewModel = categoryViewModel
categoryViewModel.getArrayList().observe(this#CategoryActivity, Observer { category ->
mAdapter = CategoryAdapter(this#CategoryActivity, categoryList = category )
recycler_category.layoutManager = LinearLayoutManager(applicationContext, OrientationHelper.VERTICAL, false)
recycler_category.adapter = mAdapter
recycler_category.setHasFixedSize(true)
})
My RecyclerView Adapter class
class CategoryAdapter(private val context: Context, private val categoryList: ArrayList<Category>?)
: RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private var mCategoryList: ArrayList<Category> = ArrayList()
private var mSourceList: ArrayList<Category> = ArrayList()
fun setupCategory(categoryList: ArrayList<Category>) {
mSourceList.clear()
mSourceList.addAll(categoryList)
search(query = "")
}
fun search(query: String) {
mCategoryList.clear()
mSourceList.forEach {
if (it.name.contains(query, ignoreCase = true)) {
mCategoryList.add(it)
}
}
notifyDataSetChanged()
}
fun sortByName() {
mCategoryList.sortBy { it.name }
notifyDataSetChanged()
}
fun sortByPrice() {
mCategoryList.sortBy { it.price }
notifyDataSetChanged()
}
fun filter() {
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
if (holder is CategoryViewHolder) {
holder.bind(categoryModel = mCategoryList[position])
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val itemView = layoutInflater.inflate(R.layout.cell_category, parent, false)
return CategoryViewHolder(itemView = itemView)
}
override fun getItemCount(): Int {
return mCategoryList.count()
}
class CategoryViewHolder(itemView: View): RecyclerView.ViewHolder(itemView) {
var mCategoryIcon: CircleImageView = itemView.findViewById(R.id.category_icon)
var mCategoryName: TextView = itemView.findViewById(R.id.category_name)
var mCategoryPrice: TextView = itemView.findViewById(R.id.category_price)
private var mCategoryType: TextView = itemView.findViewById(R.id.category_type)
fun bind(categoryModel: Category) {
categoryModel.icon.let { url ->
Picasso.with(itemView.context).load(url)
.into(mCategoryIcon)
}
mCategoryName.text = categoryModel.name
mCategoryPrice.text = categoryModel.price.toString()
mCategoryType.text = categoryModel.category
}
}
}
And my ViewModel class
class CategoryViewModel : ViewModel() {
var mutableLiveData = MutableLiveData<ArrayList<Category>>()
fun getArrayList(): MutableLiveData<ArrayList<Category>> {
mutableLiveData.value = CategoryClient().loadCategory() as ArrayList<Category>?
return mutableLiveData
}
}
Your data is not updated from asynchronous API call. Change you implementation like below to get updated data:
class CategoryViewModel : ViewModel() {
fun getArrayList(): MutableLiveData<ArrayList<Category>> {
return CategoryClient().loadCategory()
}
}
And your loadCategory
var mutableLiveData = MutableLiveData<ArrayList<Category>>()
fun loadCategory(): MutableLiveData<ArrayList<Category>> {
api.fetchAllCategory().enqueue(object : Callback<List<Category>> {
override fun onFailure(call: Call<List<Category>>, t: Throwable) {
}
override fun onResponse(call: Call<List<Category>>, response: Response<List<Category>>) {
mutableLiveData.postValue(response.body())
}
})
return mutableLiveData
}
And your adapter should be corrected to use single source
class CategoryAdapter(private val context: Context, private val mCategoryList: ArrayList<Category>?)
: RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private var mSourceList: ArrayList<Category> = ArrayList(mCategoryList)
.....
}

parse JSON object using retrofit in kotlin

I am trying to show json data using retrofit library in kotlin
This is my Json:
[
{
"login": "mojombo",
"id": 1,
},
{
"login": "defunkt",
"id": 2,
}
]
My Main activity
call.enqueue(object : Callback<UserResponse> {
override fun onResponse(call: Call<UserResponse>, response: Response<UserResponse>) {
Log.e("list","list")
val countrylist = response.body()
for (size in response.body()) {
System.out.println(size.toString())
}
// var listOfMovies: List<UserResponse> = response.body()?.results!!
// myCustomAdapter = UserListAdapter(applicationContext, listOfMovies)
// recyclerView.setAdapter(myCustomAdapter)
progressBar.visibility = View.GONE
}
override fun onFailure(call: Call<UserResponse>?, t: Throwable?) {
progressBar.visibility = View.GONE
Log.e("list", t.toString())
}
})
This an app that I build in kotlin using retrofit and rxjava in the best way possible using a test API.
Model
data class Post( val userID:Int, val title:String, val body: String)
Retrofit Package
IMyApi interface
interface IMyApi {
#get:GET("posts")
val posts: Observable<List<Post>>
}
RetrofitClient Object class
object RetrofitClient {
val instance: Retrofit by lazy {
Retrofit.Builder()
.baseUrl("https://jsonplaceholder.typicode.com/")
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.build()
}
}
Adapter Package
PostAdapter class
class PostAdapter(private val context: Context, private val postList: List<Post>)
:RecyclerView.Adapter<PostViewHolder>()
{
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int):
PostViewHolder {
val itemView = LayoutInflater.from(parent.context)
.inflate(R.layout.post_item, parent, false)
return PostViewHolder(itemView)
}
override fun getItemCount(): Int {
return postList.size
}
override fun onBindViewHolder(holder: PostViewHolder, position: Int)
{
holder.userId.text = postList[position].userID.toString()
holder.title.text = postList[position].title
holder.body.text = StringBuilder(postList[position].body.substring(0,20))
.append("...").toString()
}
}
PostViewHolder class
class PostViewHolder(itemView: View):RecyclerView.ViewHolder(itemView) {
var userId = itemView.txtID
var title = itemView.txtTitle
var body = itemView.txtBody
}
MainActivity
class MainActivity : AppCompatActivity() {
private lateinit var jsonApi: IMyApi
private var compositeDisposable: CompositeDisposable = CompositeDisposable()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Init api
val retrofit = RetrofitClient.instance
jsonApi = retrofit.create(IMyApi::class.java)
// View
recycler_posts.layoutManager = LinearLayoutManager(this)
recycler_posts.setHasFixedSize(true)
fetchData()
}
private fun fetchData() {
compositeDisposable.add(jsonApi.posts
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe { posts->displayData(posts)})
}
private fun displayData(posts: List<Post>?) {
val adapter = PostAdapter(this, posts!!)
recycler_posts.adapter = adapter
}
}
Using this as displayed above should help you solve your issue hopefully. Also when in the code you come across "recycler_posts". This is a id to the recycler added in activity_main. If you need me to include that let me know
That's what we have on our app
object GetFAQsAPI {
private val LOG_TAG = GetFAQsAPI.javaClass.simpleName
interface ThisCallback {
fun onSuccess(getFAQs: GetFAQs)
fun onFailure(failureMessage: String)
fun onError(errorMessage: String)
}
/* POST */
fun postData(jo: JsonObject, callback: GetFAQsAPI.ThisCallback) {
val call = Service.getService().get_faqs(jo)
call.enqueue(object : Callback<JsonObject> {
override fun onResponse(call: Call<JsonObject>, response: Response<JsonObject>) {
//Log.e(LOG_TAG, response.body().toString());
try {
if (response.body()?.get("success")!!.asBoolean) {
val gson = GsonBuilder().setPrettyPrinting().create()
val getFAQs = gson.fromJson(response.body(), GetFAQs::class.java)
callback.onSuccess(getFAQs)
} else {
Log.e(LOG_TAG, "else")
val error = response.body()!!.get("err").asString
callback.onError(error)
}
} catch (e: Exception) {
Log.e(LOG_TAG, "exception" + e.localizedMessage)
callback.onFailure(e.message!!)
}
}
override fun onFailure(call: Call<JsonObject>, t: Throwable) {
Log.e(LOG_TAG, "onFailure: " + t.message)
callback.onFailure(t.message!!)
}
})
}
}
That's how we call it from our fragment - getFAQs is the object parsed.
private fun getFAQsAPI() {
showLoading(true)
val jo = JsonObject().apply {
addProperty("faq_category", "admin")
}
GetFAQsAPI.postData(jo, object : GetFAQsAPI.ThisCallback {
override fun onSuccess(getFAQs: GetFAQs) {
Log.i(LOG_TAG, "onSuccess")
showLoading(false)
updateUI(getFAQs)
}
override fun onFailure(failureMessage: String) {
Log.e(LOG_TAG, failureMessage)
}
override fun onError(errorMessage: String) {
Log.e(LOG_TAG, errorMessage)
}
})
}
Hope that helps.

Categories

Resources