How to parse json array of objects inside object using Retrofit - android

I am trying to parse JSON (https://raw.githubusercontent.com/Biuni/PokemonGO-Pokedex/master/pokedex.json) to show data in RecyclerView, but I get an error:
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.myapplication, PID: 13534
java.lang.IllegalStateException: Expected BEGIN_ARRAY but was BEGIN_OBJECT at line 1 column 2 path $
at com.google.gson.stream.JsonReader.beginArray(JsonReader.java:350)
at com.google.gson.internal.bind.CollectionTypeAdapterFactory$Adapter.read(CollectionTypeAdapterFactory.java:80)
at com.google.gson.internal.bind.CollectionTypeAdapterFactory$Adapter.read(CollectionTypeAdapterFactory.java:61)
at retrofit2.converter.gson.GsonResponseBodyConverter.convert(GsonResponseBodyConverter.java:39)
at retrofit2.converter.gson.GsonResponseBodyConverter.convert(GsonResponseBodyConverter.java:27)
at retrofit2.OkHttpCall.parseResponse(OkHttpCall.java:243)
at retrofit2.OkHttpCall$1.onResponse(OkHttpCall.java:153)
at okhttp3.internal.connection.RealCall$AsyncCall.run(RealCall.kt:519)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
at java.lang.Thread.run(Thread.java:919)
I see that the problem is that JSON contains just 1 object (pokemon) and inside this object there is array of different pokemons. I don't know how to parse array inside the upper level object in JSON. What should I change to make it work?
I suppose that I should save this pokemon object and then parse it but I don't know hot to get inside it.
Thanks.
API interface:
interface SimpleApi {
#GET("pokedex.json")
suspend fun getCustomPosts(): Response<List<Post>>
}
Repository:
class Repository {
suspend fun getCustomPosts(): Response<List<Post>>{
return RetrofitInstance.api.getCustomPosts()
}
}
ViewModel:
class MainViewModel(val repository: Repository) : ViewModel() {
val myCustomPosts = MutableLiveData<Response<List<Post>>>()
fun getCustomPosts() {
viewModelScope.launch {
val response: Response<List<Post>> = repository.getCustomPosts()
myCustomPosts.value = response
}
}
}
MainActivity:
class MainActivity : AppCompatActivity() {
private lateinit var viewModel: MainViewModel
private lateinit var binding: ActivityMainBinding
private lateinit var recyclerView: RecyclerView
private lateinit var adapter: MyAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
setRecyclerView()
val repository = Repository()
val viewModelFactory = MainViewModelFactory(repository)
viewModel = ViewModelProvider(this, viewModelFactory).get(MainViewModel::class.java)
viewModel.getCustomPosts()
viewModel.myCustomPosts.observe(this, Observer { response ->
if (response.isSuccessful) {
response.body()?.let { adapter.setData(it) }
} else {
Toast.makeText(this, response.code(), Toast.LENGTH_SHORT).show()
}
})
}
private fun setRecyclerView() {
adapter = MyAdapter()
recyclerView = binding.recyclerView
recyclerView.layoutManager = LinearLayoutManager(this)
recyclerView.adapter = adapter
}
}
RecyclerView Adapter:
class MyAdapter() : RecyclerView.Adapter<MyAdapter.MyViewHolder>() {
private var postList = emptyList<Post>()
class MyViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val idView: TextView = view.findViewById(R.id.id_txt)
val nameView: TextView = view.findViewById(R.id.name_txt)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
return MyViewHolder(
LayoutInflater.from(parent.context).inflate(R.layout.row_layout, parent, false)
)
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
val currentItem = postList[position]
holder.idView.text = currentItem.id.toString()
holder.nameView.text = currentItem.name
}
override fun getItemCount(): Int = postList.size
fun setData(newList: List<Post>){
postList = newList
notifyDataSetChanged()
}
}
Post:
data class Post(
#SerializedName("id")
val id: Int,
#SerializedName("name")
val name: String
)

Try to use next class in response object:
data class PokedexResponse (
#SerializedName("pokemon")
val pokemons: List<Post>
)
interface SimpleApi {
#GET("pokedex.json")
suspend fun getCustomPosts(): Response<PokedexResponse>
}
My guess is that you missed to parse pokemon object:
{
"pokemon": [{ ... }]
}

Related

null cannot be cast to non-null type RecyclerView with stateFlow

I got some categories from an api and trying to show them on a recycler view but it doesn't work for some reason.
Although the data appears correctly in the logcat, it is sent as null to the Category adapter.
This is the Main Activity (where I'm trying to show the data):
`
#AndroidEntryPoint
class MainActivity : AppCompatActivity() {
private val TAG = "MEALZ"
private lateinit var binding: ActivityMainBinding
private val viewModel:MealsViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
val adapter = CategoryAdapter(this)
binding.categoriesRv.adapter = adapter
viewModel.getMeals()
lifecycleScope.launch {
viewModel.categories.collect {
adapter.setData(it?.categories as List<Category>)
Log.d(TAG, "onCreate: ${it?.categories}")
}
}
}
}
`
This is Recycler Category Adapter :
`
class CategoryAdapter(private val context: Context?) :
RecyclerView.Adapter<CategoryAdapter.CategoryViewHolder>() {
private var categoryList: MutableList<Category?> = mutableListOf<Category?>()
inner class CategoryViewHolder(itemView: CategoryLayoutBinding) :
RecyclerView.ViewHolder(itemView.root) {
val name = itemView.categoryNameTv
val img = itemView.categoryIv
val des = itemView.categoryDesTv
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CategoryViewHolder {
val binding = CategoryLayoutBinding.inflate(LayoutInflater.from(context), parent, false)
return CategoryViewHolder(binding)
}
override fun onBindViewHolder(holder: CategoryViewHolder, position: Int) {
var category = categoryList[position]
holder.name.text = category?.strCategory
holder.des.text = category?.strCategoryDescription
Glide.with(context as Context).load(category?.strCategoryThumb).into(holder.img)
}
override fun getItemCount(): Int {
return categoryList.size
}
fun setData(CategoryList: List<Category>) {
this.categoryList.addAll(CategoryList)
notifyDataSetChanged() //to notify adapter that new data change has been happened to adapt it
}
}
`
This is the View Model class:
#HiltViewModel
class MealsViewModel #Inject constructor(private val getMealsUseCase: GetMeals): ViewModel() {
private val TAG = "MealsViewModel"
private val _categories: MutableStateFlow<CategoryResponse?> = MutableStateFlow(null)
val categories: StateFlow<CategoryResponse?> = _categories
fun getMeals() = viewModelScope.launch {
try {
_categories.value = getMealsUseCase()
} catch (e: Exception) {
Log.d(TAG, "getMeals: ${e.message.toString()}")
}
}
}
you create your _categories with null as initial value, so first value of categories flow will be null and only second one will contain fetched data. As a workaround, you can check that data is not null:
viewModel.categories.collect {
if (it != null) {
adapter.setData(it?.categories as List<Category>)
Log.d(TAG, "onCreate: ${it?.categories}")
}
}
or introduce some kind of "loading" state

How to load data in recyclerview?

I am creating one Android app and trying to set the data in Recyclerview, I am using MVVM architecture pattern with kotlin, I can see data in logcat but when app loads I am not seeing any data in my recyclerview. Following is my code.
MainActivity
class MainActivity : AppCompatActivity() {
lateinit var productViewModel: ProductViewModel
private lateinit var binding: ActivityMainBinding
val adapter = ProductAdapter()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
val productService = RetrofitHelper.getInstance().create(ProductService::class.java)
val productRepository = ProductRepository(productService)
productViewModel = ViewModelProvider(this, ProductViewModelFactory(productRepository)).get(ProductViewModel::class.java)
binding.recyclerview.adapter = adapter
productViewModel.products.observe(this,{
Log.d("TEST",it.toString())
adapter.notifyDataSetChanged()
})
}
}
ProductAdapter
class ProductAdapter : RecyclerView.Adapter<ProductViewHolder>() {
var movies = mutableListOf<MobileList>()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ProductViewHolder {
val inflater = LayoutInflater.from(parent.context)
val binding = AdapterLayoutBinding.inflate(inflater, parent, false)
return ProductViewHolder(binding)
}
override fun onBindViewHolder(holder: ProductViewHolder, position: Int) {
val movie = movies[position]
holder.binding.name.text = movie.products.get(position).name
Glide.with(holder.itemView.context).load(movie.products.get(position).image_url).into(holder.binding.imageview)
}
override fun getItemCount(): Int {
return movies.size
}
}
class ProductViewHolder(val binding: AdapterLayoutBinding) : RecyclerView.ViewHolder(binding.root) {
}
Repository class
class ProductRepository (private val productService: ProductService) {
private val productLiveData = MutableLiveData<MobileList>()
val products:LiveData<MobileList>
get() = productLiveData
suspend fun getProducts(){
val products = productService.getQuotes()
if(products?.body()!=null)
{
productLiveData.postValue(products.body())
}
}
}
ViewModel
class ProductViewModel (private val productRepository: ProductRepository ) :ViewModel() {
init {
viewModelScope.launch(Dispatchers.IO){
productRepository.getProducts()
}
}
val products : LiveData<MobileList>
get() = productRepository.products
}
Factory
class ProductViewModelFactory (private val productRepository: ProductRepository) : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return ProductViewModel (productRepository) as T
}
}
Model
data class MobileList(
val products: List<Product>
)
data class Product(
val image_url: String,
val name: String,
val price: String,
val rating: Int
)
JSON Response
{
"products": [
{
"name": "test",
"price": "34999",
"image_url": "url",
"rating": 4
},
{
"name": "test2",
"price": "999",
"image_url": "url",
"rating": 4
},]}
First of all make sure you have layoutManager set on the RecyclerView.
The problem here is Your ProductAdapter never had the data . notifyDataSetChanged is not a magic stick to notify the adapter you modify/add/update the dataset and then You will call notifyDataSetChanged . that's how it works .
In your case You have movies list your adapter but you never assigned anything to it its always empty .
There are several ways to it. Just to make it work You can have a method to add the data in your adapter and then notify it.
fun addData(data:List<MobileList>){
movies.addAll(data)
notifyDataSetChanged()
}
Now when you get the data inside on change you call this method .
productViewModel.products.observe(this,{
it?.let{ items ->
adapter.addData(items)
}
})
This should work .
Update on type fix - Seems like your type is messed up . Why is your repo returns a object of MobileList? While you are using a list of MobileList in adapter . Your adapter should hold var movies = mutableListOf<Products>().
productViewModel.products.observe(this,{
it?.let{ item ->
adapter.addData(item.products)
}
})

Retfrofit on response gets java.lang.NullPointerException [duplicate]

This question already has answers here:
What is a NullPointerException, and how do I fix it?
(12 answers)
Closed 1 year ago.
Good day, I'm using Retrofit for the first time and I can't seem to get it to work. working on an app that get crypto currency rates in local currencies, here is the api [link], JSON format
Here are my models for News
data class News(
#field:SerializedName("totalResults")
val totalResults: Int,
#field:SerializedName("articles")
val articles: List<NewsArticle>?=null,
#field:SerializedName("status")
val status: String)
My model calss for NewsArticles:
data class NewsArticle(
#field:SerializedName("urlToImage")
val urlToImage: String? = null,
#field:SerializedName("description")
val description: String? = null,
#field:SerializedName("title")
val title: String? = null,
#field:SerializedName("url")
val url: String? = null,)
My interface:
const val BASE_URL = "https://saurav.tech/NewsAPI/"
interface NewsApiService {
#GET("top-headlines.json")
fun getNews(
#Query("category") category: String,
#Query("country_code") country_code: String
): Call<News>}
object NewsApi{
val newsInstance:NewsApiService
init {
val retrofit=Retrofit.Builder()
.baseUrl(BASE_URL).addConverterFactory(GsonConverterFactory.create())
.build()
newsInstance=retrofit.create(NewsApiService::class.java)
}
}
My MainActivity class
class MainActivity : AppCompatActivity() {
private lateinit var adapter: NewsAdapter
//var totalResult=1
private var article = mutableListOf<NewsArticle>()
lateinit var refreshLayout: SwipeRefreshLayout
private lateinit var recyclerView: RecyclerView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
recyclerView = findViewById(R.id.recyclerViewNews)
refreshLayout = findViewById(R.id.refreshLayout)
adapter = NewsAdapter(this#MainActivity, article)
recyclerView.adapter = adapter
recyclerView.layoutManager = LinearLayoutManager(this#MainActivity)
fetchNews()
}
private fun fetchNews() {
val news = NewsApi.newsInstance.getNews("technology", "in")
news.enqueue(object : Callback<News> {
override fun onResponse(call: Call<News>, response: Response<News>) {
val news = response.body()
article.addAll(news!!.articles)
}
override fun onFailure(call: Call<News>, t: Throwable) {
Toast.makeText(applicationContext, t.message, Toast.LENGTH_SHORT).show()
}
})
}
}
My AdapterClass:
class NewsAdapter(val context: Context, val newsArticles: MutableList<NewsArticle>) :
RecyclerView.Adapter<NewsAdapter.NewsViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NewsViewHolder {
val inflater = LayoutInflater.from(parent.context)
val view = inflater.inflate(R.layout.news_items, parent, false)
return NewsViewHolder(view)
}
override fun onBindViewHolder(holder: NewsViewHolder, position: Int) {
val article = newsArticles[position]
holder.title.text = article.title
holder.description.text = article.description
Glide.with(holder.itemView.context).load(article.urlToImage).into(holder.image)
}
override fun getItemCount() = newsArticles.size
class NewsViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val title = view.findViewById<TextView>(R.id.titleText)!!
val description = view.findViewById<TextView>(R.id.descriptionText)!!
val image = view.findViewById<ImageView>(R.id.NewsImage)!!
}
}
When I run the code and check my log I get this error:
java.lang.NullPointerException
at com.ashish.technews.uis.MainActivity$fetchNews$1.onResponse(MainActivity.kt:44)
at retrofit2.DefaultCallAdapterFactory$ExecutorCallbackCall$1.lambda$onResponse$0$DefaultCallAdapterFactory$ExecutorCallbackCall$1(DefaultCallAdapterFactory.java:89)
at retrofit2.-$$Lambda$DefaultCallAdapterFactory$ExecutorCallbackCall$1$hVGjmafRi6VitDIrPNdoFizVAdk.run(lambda)
at android.os.Handler.handleCallback(Handler.java:754)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:165)
at android.app.ActivityThread.main(ActivityThread.java:6375)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:912)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:802)
What's Happening?
Article List in the response is not coming and it is not being handled properly.
Solution
Replace this: article.addAll(news!!.articles)
With this: news?.let { article.addAll(it.articles) }

How to send data From One RecyclerView Item to load another RecyclerView using Nested JSON using Kotlin

I have two activity and each activity has recyclerview, I have nested json.I want when user tap first activity recyclerview then they goes to next activity and show the recyclerview.Better understanding please follow the json link and code.
Json Link
: https://run.mocky.io/v3/8c3f6be2-a53f-47da-838c-72e603af844d
CropData.kt
data class CropData(
#SerializedName("cropImage")
val cropImage: String,
#SerializedName("cropName")
val cropName: String,
#SerializedName("cropTime")
val cropTime: String,
#SerializedName("cropDetails")
val cropDetails: String,
#SerializedName("cropProcess")
val cropProcess: String,
#SerializedName("cropType")
val cropType: String,
#SerializedName("cropFertilizer")
val cropFertilizer: List<CropFertilizer>
)
CropFertilizer.kt
data class CropFertilizer(
#SerializedName("fertilizerName")
val fertilizerName: String,
#SerializedName("fertilizerFirst")
val fertilizerFirst: String,
#SerializedName("fertilizerSecond")
val fertilizerSecond: String,
#SerializedName("fertilizerThird")
val fertilizerThird: String
)
CropActivity.kt
class CropActivity : AppCompatActivity(), CropOnItemClickListener {
private lateinit var viewModel: CropActivityViewModel
private val cropAdapter by lazy { CropAdapter(this) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_crop)
val repository = Repository()
val viewModelFactory = CropActivityViewModelFactory(repository)
viewModel = ViewModelProvider(this, viewModelFactory).get(CropActivityViewModel::class.java)
viewModel.getCropData()
viewModel.cropResponse.observe(this, Observer { response ->
cropAdapter.setData(response)
Log.d("dCrop", response.toString())
})
setUpCrops()
}
private fun setUpCrops() {
crop_recyclerview.layoutManager = LinearLayoutManager(
this,
LinearLayoutManager.VERTICAL, false
)
crop_recyclerview.setHasFixedSize(true)
crop_recyclerview.adapter = cropAdapter
}
override fun onClick(item: CropData, position: Int) {
val intent = Intent(this, CropDetailsActivity::class.java)
intent.putExtra("name", item.cropName)
intent.putExtra("image", item.cropImage)
intent.putExtra("intro", item.cropDetails)
intent.putExtra("process", item.cropProcess)
intent.putExtra("type", item.cropType)
intent.putExtra("time", item.cropTime)
startActivity(intent)
}
}
CropAdapter.kt
class CropAdapter(private val cropOnItemClickListener: CropOnItemClickListener) :
RecyclerView.Adapter<CropAdapter.ViewHolder>() {
private var cropList = emptyList<CropData>()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.crop_row,parent,false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.itemView.crop_name.text = cropList[position].cropName
holder.itemView.crop_details.text = cropList[position].cropDetails
holder.itemView.crop_time.text = cropList[position].cropTime
holder.itemView.setOnClickListener{
cropOnItemClickListener.onClick(cropList[position],position)
}
}
override fun getItemCount(): Int {
return cropList.size
}
fun setData(newList: List<CropData>){
notifyDataSetChanged()
cropList = newList
notifyDataSetChanged()
}
class ViewHolder(itemView: View): RecyclerView.ViewHolder(itemView)
}
CropDetailsActivity.kt
class CropDetailsActivity : AppCompatActivity() {
private lateinit var viewModel: CropDetailsActivityViewModel
private val fertlizerAdapter by lazy { FertilizerAdapter() }
private val cropFertilizerAdapter by lazy { FertilizerAdapter() }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_crop_details)
val bundle:Bundle? = intent.extras
crop_details_name.text = bundle!!.getString("name")
val i: String = bundle!!.getString("intro")
crop_details_intro.text = i
Glide.with(this).load(bundle.getString("image")).into(crop_details_image);
val j: String = bundle!!.getString("process")
crop_details_process.text = j
crop_details_time.text = bundle!!.getString("time")
crop_details_type.text = bundle!!.getString("type")
val repository = Repository()
val viewModelFactory = CropDetailsActivityViewModelFactory(repository)
viewModel = ViewModelProvider(this, viewModelFactory).get(CropDetailsActivityViewModel::class.java)
viewModel.getFertilizer()
viewModel.fResponse.observe(this, Observer {
cropFertilizerAdapter.setData(it)
})
crop_details_recyclerview.layoutManager = LinearLayoutManager(
this,
LinearLayoutManager.VERTICAL, false
)
crop_details_recyclerview.setHasFixedSize(true)
crop_details_recyclerview.adapter = cropFertilizerAdapter
}
}
Better understanding see the image
[1]: https://i.stack.imgur.com/BtPqe.jpg
I want to show CropFertilizer list in CropDetailsActivity.How can i do this?

How to parse Json in Kotlin MVVM Data binding

I'm trying it implement following Json string:
{
"msg":[
"football",
"cricket",
"baseball",
"rugby",
"gulf"
],
"status":"success"
}
I have created the data classes as below:
class Sports(
val msg : List<String>,
val status : String
)
And
class Msg (
val football : List<String>,
val cricket : List<String>,
val baseball : List<String>,
val rugby : List<String>,
val gulf : List<String>
)
Now I'm trying to get the objects and view it in a recyclerview list as per the tutorial.
How could I change it below & call it in the adapter?
interface PostApi {
/**
* Get the list of the pots from the API
*/
#GET("/posts")
fun getPosts(): Observable<List<Post>>
}
Edit:
MY adapter class as below:
class PostListAdapter: RecyclerView.Adapter<PostListAdapter.ViewHolder>() {
private lateinit var postList:Sports
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PostListAdapter.ViewHolder {
val binding: ItemPostBinding = DataBindingUtil.inflate(LayoutInflater.from(parent.context), R.layout.item_post, parent, false)
return ViewHolder(binding)
}
override fun onBindViewHolder(holder: PostListAdapter.ViewHolder, position: Int) {
holder.bind(postList)
}
override fun getItemCount(): Int {
//Getting error in .isInitialied 'Unresolved reference'
return if(::postList.isInitialized) postList.message.size else 0
}
fun updatePostList(postList: Sports){
this.postList = postList
notifyDataSetChanged()
}
class ViewHolder(private val binding:
ItemPostBinding):RecyclerView.ViewHolder(binding.root){ //Getting error in root 'Unresolved reference'
private val viewModel = PostViewModel()
fun bind(post: Sports){
viewModel.bind(post) //Getting error saying No value passed for parameter 'position'
binding.viewModel = viewModel
}
}
}
If you get the Json from server then call it like below:
interface SportsApi {
/**
* Get the Sports from the API
*/
#GET("/sports")
fun getSports(): Observable<Sports>
}
Or if you want to to check it in locally then you have to convert this Json
Using Gson:
val sports = Gson().fromJson(json, Sports::java.class)
Using Moshi:
val sports = Moshi.Builder().build().adapter(Sports::java.class).fromJson(json)

Categories

Resources