How to parse Json in Kotlin MVVM Data binding - android

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)

Related

Kotlin okhttp parse error to recycle view

I have a json file which contain some data I am trying to parse this data into kotlin in array using modal class and display in recycler view but unable to get it, the app keep crashing while I start the activity.
The Json data what I want to parse
MemberĀ BankĀ API: [MemberBankModel(bankName=Alliance Bank, memberBankAccNumber=11111111), MemberBankModel(bankName=Bank Simpanan Nasional, memberBankAccNumber=222222222)]
Log from Log.d("Member Bank API","${saveBankResponseModel.data!!.memberBank}")
Activity.kt
okHttpClient.newCall(request).enqueue(object : Callback {
override fun onResponse(call: Call, response: Response) {
if(response.isSuccessful) {
saveBankResponseModel = json.decodeFromString(
response.body!!.string()
)
Log.d("Member Bank API","${saveBankResponseModel.data!!.memberBank}")
val noBankView = findViewById<LinearLayout>(R.id.nobank_layout)
newRecyclerView = findViewById(R.id.recyclerView)
noBankView.visibility = View.GONE
newRecyclerView.apply {
layoutManager = LinearLayoutManager(this#MyWalletActivity)
adapter = saved_bank_adapter(saveBankResponseModel.data!!.memberBank)
}
}
}
override fun onFailure(call: Call, e: IOException) {
mHandler.post {
println(e)
}
}
})
data Class
#Serializable
data class SaveBankResponseModel(
val responseCode:Int,
val msgType:String,
val message:List<String>,
val data:SaveBankDataModel? = null
)
#Serializable
data class SaveBankDataModel(
val accountHolder:AccountModel,
val memberBank:List<MemberBankModel>
)
#Serializable
data class AccountModel(
val name:String,
)
#Serializable
data class MemberBankModel(
val bankName:String,
val memberBankAccNumber:String
)
RecycleAdapter
class saved_bank_adapter(private val bankList: List<MemberBankModel>): RecyclerView.Adapter<saved_bank_adapter.BankViewHolder>() {
private var selectedItemPosition: Int = 0
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BankViewHolder {
val itemView = LayoutInflater.from(parent.context).inflate(R.layout.withdrawbank_layout, parent, false)
return BankViewHolder(itemView)
}
override fun onBindViewHolder(holder: BankViewHolder, #SuppressLint("RecyclerView") position: Int) {
val currentItem = bankList[position]
holder.itemName.text = currentItem.bankName
holder.itemAccNum.text = currentItem.memberBankAccNumber
}
Can anyone please help me to check what step I'm doing wrong, I'm new to Kotlin API call
Error I get...
2022-08-22 16:44:06.584 8526-8632/com.example.app E/AndroidRuntime: FATAL EXCEPTION: OkHttp Dispatcher
Process: com.example.app, PID: 8526
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
As I can see memberBankAccNumber field value is an integer in your API response, But you have declared that build as String, So the error is here.
val memberBankAccNumber:String
Replace this with
val memberBankAccNumber:Int

Error 401 Retrofit when using Paging 3 library

I was making a call using Retrofit before I tried to use Paging 3 library and I was getting code 200 and all working good.Then, I tried to implement Paging 3 libray and using Retrofit with it and I don't know why, I'm getting error 401. I tried some Paging 3 sample and all of this samples don't use headers in the API call, is there any problem to add headers in API call using Paging 3? I'm not sure if there is a problem with the use of headers or I'm just doing something wrong implementing Paging 3 library.
My code:
Service:
interface APICalls{
#GET(MYAPIURL)
suspend fun getData(
#Header("Auth") auth : String,
#Query("pageSize") pageSize:Int
):Response<ResponseData>
}
Models:
data class ResponseData(
#SerializedName("listData") val listData:MutableList<DataAPI>,
#SerializedName("pageSize") val pageSize : Int
):Serializable
data class DataAPI(
#SerializedName("id") val id:Int,
#SerializedName("data")val data: String
): Serializable
Result wrapper:
class Result<out T:Any>{
data class Success<T:Any>(val value: T): Result<T>()
data class Failure(val message:String, val errorCode:Int?):Result<Nothing>()
}
PagingSource:
val responseData = mutableListOf<DataAPI>()
class DataAPIPagingSource(private val token:String,private val apiCalls:APICalls) : PagingSource<Int,DataAPI>{
override fun getRefreshKey(...):Int?{
return null
}
override suspend fun load(params : LoadParams<Int>):LoadResult<Int,DataAPI>{
return try{
val currentPage = params.key ?: 1
val response = apiCalls.getData(token)
response.body()?.let{
Result.Success(it)
}?: run{
Result.Failure(response.message(),response.code())
}
val data = response.body()?.listData ?: emptyList()
responseData.addAll(data)
LoadResult.Page(responseData,if(currentPage ==1) null else -1),currentPage.plus(1)
}catch(e:Exception){
LoadResult.Error(e)
}
}
}
ViewModel:
class DataViewModel(private val apiCalls:APICalls): ViewModel {
//I get this token in shared preference
val token = .....
val mydata = getDataList()
.map{pagingData -> pagingData.map{DataModel.DataItem(it)}}
private fun getDataList(){
return Pager(PagingConfig(25)){
DataAPIPagingSource(token,apiCalls)
}.flow.cachedIn(viewModelScope)
}
}
sealed class DataModel{
data class DataItem(val dataitem: DataAPI) : DataModel()
}
private val DataModel.DataItem.identificator : Int
get() = this.dataItem.id
Fragment:
class MyFragment : Fragment(){
private val myAdapter : DataAdapter by lazy{
DataAdapter()
}
private val viewModelFactory : ViewModelFactory by inject()
private val dataViewModel : DataViewModel by navGraphViewModels(R.id.nav_graph){viewModelFactory}
override fun onViewCreated(...){
super.onViewCreated(...)
binding.myRecyclerView.apply{
adapter = myAdapter
layoutManager = StaggeredGridLayoutManager(1,StaggeredGridLayoutManager.VERTICAL)
setHasFixedSize(true)
}
lyfecycleScope.launch{
dataViewModel.mydata.collect{myAdapter.submitData(it)}
}
}
}
Adapter:
class DataAdapter : PagingDataAdapter<DataModel,RecyclerView.ViewHolder>(DataModelComparator){
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position:Int){
val dataModel : DataModel? = getItem(position)
dataModel.let{
when(dataModel){
is DataModel.DataItem ->{
val viewHolder = holder as DataItemViewHolder
viewHolder.binding.textview1.text = dataModel.dataitem.data
}
}
}
}
override fun getItemViewType(position:Int):Int{
return when(getItem(position)){
is DataModel.DataItem -> R.layout.item_data
null -> throw UnsupportedOperationException("Unknown view")
}
}
override fun onCreateViewHolder(...){
return when(viewType){
R.layout.item_data ->{
DataItemViewHolder(ItemDataBinding.inflate(...))
}
}
}
class DataItemViewHolder(val binding: DataItemBinding): RecyclerView.ViewHolder(binding.root)
companion object {
val DataModelComparator = object : DiffUtil.ItemCallback<DataModel>() {
override fun areItemsTheSame(oldItem: Movie, newItem: Movie): Boolean {
return oldItem.dataitem.id == newItem.dataitem.id
}
override fun areContentsTheSame(oldItem: DataModel, newItem: DataModel): Boolean {
return oldItem == newItem
}
}
}
}
I don't think the 401 error is related to paging 3.
You can use OkHttp Interceptor - authenticator.
Gist

How to parse json array of objects inside object using Retrofit

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": [{ ... }]
}

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)
}
})

Can't populate a recycler using a collection

I want each row of my RecyclerView to display all the details of one document of the collection.
I've used this exact same adapter code, albeit with a different class to serialize into. And it works well. But in this instance, it's simply not working.
But the code just doesn't get into populating the views.
My database is like:
reviews--Orange--vault--|
|-firstReview
|-secondReview
|-sjdeifhaih5aseoi
...
My query and adapter from the fragment:
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
viewModel = ViewModelProviders.of(this).get(ReviewViewModel::class.java)
val reviewQuery = FirebaseFirestore.getInstance().collection("reviews").document("Orange").collection("vault")
val reviewBurnOptions = FirestoreRecyclerOptions.Builder<Review>()
.setQuery(reviewQuery, object : SnapshotParser<Review> {
override fun parseSnapshot(snapshot: DocumentSnapshot): Review {
return snapshot.toObject(Review::class.java)!!.also {
it.id = snapshot.id
}
}
}).setLifecycleOwner(this)
reviewRecycler.adapter=ReviewBurnAdapter(reviewBurnOptions.build())}
class ReviewBurnAdapter(options: FirestoreRecyclerOptions<Review>) :
FirestoreRecyclerAdapter<Review, ReviewBurnAdapter.ViewHolder>(options) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {//I never reach this point
val view = LayoutInflater.from(parent.context).inflate(R.layout.row_review, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int, item: Review) {
holder.apply {
holder.itemView.rowAuthor.text = item.author
}
}
inner class ViewHolder(override val containerView: View) :
RecyclerView.ViewHolder(containerView), LayoutContainer
}
Class to serialize into:
import com.google.firebase.firestore.Exclude
import com.google.firebase.firestore.PropertyName
import java.util.*
class Review(
#get:Exclude var id: String = "DEVIL",
#JvmField #PropertyName(AUTHOR) var author: String = "",
#JvmField #PropertyName(WRITEUP) var writeup: String = "",
//#JvmField #PropertyName(MOMENT) var moment:Date=Date(1997,12,1),
#JvmField #PropertyName(RATING) var rating: Int = 0
) {
companion object {
const val AUTHOR = "author"
const val WRITEUP = "writeup"
const val RATING = "rating"
//const val MOMENT="moment"
}
}
Also, there's no errors, it just never reaches the code that would generate and populate with viewHolders.
Alright, the fix was ultra simple, as #Prashant Jha pointed out, I hadn't specified a layout manager for my RecyclerView -_-
To be crystal clear, I added app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
to my xml, and everything worked.

Categories

Resources