I'm Using GSON to parse my JSON. I have an object that has 2 array object. I'm having a problem on getting the TransactionDetails position.
I have a recyclerView that populates the "Transactions" and then when I tap on that it should open the "TransactionDetails". But I'm having trouble on getting the position on TransactionDetails what array to read.
As you can see on the code I need to put a position on [0] because it only loads the 1st array on transaction details.
JSON Url
DetailedTransactionAdapter
class DetailedTransactionAdapter(val transactionFeed: TransactionFeed) : RecyclerView.Adapter<DetailCustomViewHolder>() {
override fun onBindViewHolder(holder: DetailCustomViewHolder, position: Int) {
val tr = transactionFeed.Transactions[1].TransactionDetails[position]
holder.view.txt_programType.text = transactionDetail.ProgramType
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DetailCustomViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val cellForRow = layoutInflater.inflate(R.layout.list_detailed_transaction, parent, false)
return DetailCustomViewHolder(cellForRow)
}
override fun getItemCount(): Int {
return transactionFeed.Transactions[0].TransactionDetails.count()
}
}
class DetailCustomViewHolder(val view: View): RecyclerView.ViewHolder(view) {
init{
}
}
class TransactionFeed(val Transactions: List<Transactions>)
class Transactions(val TransactionCode: String,
val ProgramID: Int,
val ProgramName: String,
val ProgramType: String,
val ProgramDescription: String,
val TransactionID: Int,
val UserID: String,
val TransactionAmount: Int,
val TransactionDate: String,
val TransactionDetails: List<TransactionDetails>)
class TransactionDetails(val Clamied: Boolean,
val NextToClaim: Boolean,
val ProgramDetailID: Int,
val TransactionDetailID: Int,
val ProgramDetails: String,
val ProgramType: String,
val TransactionDetailAmount: Int,
val TransactionDetailMonth: String)
TransactionsAdapter
lass TransactionsAdapter(val transactionFeed: TransactionFeed) : RecyclerView.Adapter<CustomViewHolder>() {
override fun onBindViewHolder(holder: CustomViewHolder, position: Int) {
val tr = transactionFeed.Transactions.get(position)
holder.view.txt_transaction_id.text = tr.TransactionID.toString()
holder.view.txt_transaction_date.text = tr.TransactionDate
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CustomViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val cellForRow = layoutInflater.inflate(R.layout.list_transaction, parent, false)
return CustomViewHolder(cellForRow)
}
override fun getItemCount(): Int {
return transactionFeed.Transactions.count()
}
}
class CustomViewHolder(val view: View):RecyclerView.ViewHolder(view) {
init{
view.setOnClickListener {
val intent = Intent(view.context, DetailedTransactionActivity::class.java)
view.context.startActivity(intent)
}
}
}
You'll have to set appropriate position in getItemCount() and onBindViewHolder() of DetailedTransactionAdapter. Here is how you can go about doing it:
First pass selected transaction position in TransactionsAdapter (when user clicks on any transaction) via intent to DetailedTransactionActivity.
class CustomViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
init {
view.setOnClickListener {
val intent = Intent(view.context, DetailedTransactionActivity::class.java)
intent.putExtra("selectedTransaction", adapterPosition)
view.context.startActivity(intent)
}
}
}
Then get that position in DetailedTransactionActivity and pass it to DetailedTransactionAdapter.
val transactionPos = intent.getIntExtra("selectedTransaction", -1)
detailedTransactionAdapter = DetailedTransactionAdapter(transactionFeed, transactionPos)
You'll have to change your DetailedTransactionAdapter to also have selectedTransaction as a field. This will help in dynamically populating adapter and binding appropriate data to view. Change adapter impl to this:
class DetailedTransactionAdapter(val transactionFeed: TransactionFeed, val selectedTransaction:Int) : RecyclerView.Adapter<DetailCustomViewHolder>() {
override fun onBindViewHolder(holder: DetailCustomViewHolder, position: Int) {
if(selectedTransaction == -1){
return
}
val tr = transactionFeed.Transactions[selectedTransaction].TransactionDetails[position]
holder.view.txt_programType.text = transactionDetail.ProgramType
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DetailCustomViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val cellForRow = layoutInflater.inflate(R.layout.list_detailed_transaction, parent, false)
return DetailCustomViewHolder(cellForRow)
}
override fun getItemCount(): Int {
return transactionFeed.Transactions[selectedTransaction].TransactionDetails.count()
}
}
This is where magic happens. You are returning appropriate transaction details count
return transactionFeed.Transactions[selectedTransaction].TransactionDetails.count()
and binding data.
val tr = transactionFeed.Transactions[selectedTransaction].TransactionDetails[position]
So when you have selected first transaction, this will return count of transaction details of transaction[0] which is first transaction. And when any other transaction is selected, this will return its transaction details and populate adapter with its data.
Related
When i make Adapter in regular way, with findViewById it looks ok: like this, but when i do it with view binding looks like this 2
Adapter with ViewBinding:
class HomePageFoldersAdapter() : RecyclerView.Adapter<HomePageFoldersAdapter.FolderHolder>() {
var list = emptyList<Folder>()
set(value) {
field = value
notifyDataSetChanged()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FolderHolder {
val inflater = LayoutInflater.from(parent.context)
val binding = FolderHomeCardBinding.inflate(inflater)
return FolderHolder(binding)
}
override fun onBindViewHolder(holder: FolderHolder, position: Int) {
val folder = list[position]
with(holder.binding) {
val context = root.context
nameTextView.text = folder.name
sizeTextView.text = context.getString(R.string.modules_count, folder.modulesIds.size)
}
}
override fun getItemCount() = list.size
class FolderHolder(val binding: FolderHomeCardBinding) : RecyclerView.ViewHolder(binding.root)
}
try replace instead
val binding = FolderHomeCardBinding.inflate(inflater)
with
val binding = FolderHomeCardBinding.inflate(inflater, parent, false)
How to solve the child adapter image that changes and show wrong images after scrolling?
this is my code
Parent Data Class
data class Review(
val author: String,
val date: String,
val rating: Float,
val comment: String,
val images: List<Image>)
Child Data Class
For author and comment, I only take it from the parent data class
data class Image(
var author: String? = null,
var comment: String? = null,
val large: String,
val thumbnail: String)
ParentAdapter
class ReviewAdapter(private val callback: ProductReviewImageAdapterCallback) :
PagingDataAdapter<Review, ReviewAdapter.ListViewHolder>(DIFF_CALLBACK) {
private val viewPool = RecyclerView.RecycledViewPool()
private val imageAdapter: ProductReviewImageAdapter by lazy {
ProductReviewImageAdapter(callback)
}
companion object {
private val DIFF_CALLBACK = object : DiffUtil.ItemCallback<Review>() {
override fun areItemsTheSame(oldItem: Review, newItem: Review): Boolean {
return oldItem.author == newItem.author
}
override fun areContentsTheSame(oldItem: Review, newItem: Review): Boolean {
return oldItem.comment == newItem.comment
}
}
}
inner class ListViewHolder(itemBinding: ItemProductReviewBinding) :
RecyclerView.ViewHolder(itemBinding.root) {
val binding = ItemProductReviewBinding.bind(itemBinding.root)
fun bind(data: Review, position: Int) {
with(binding) {
if (data.images.isEmpty()) {
rvImageReview.gone()
} else {
setupImagesRecyclerView(rvImageReview)
rvImageReview.visible()
imageAdapter.differ.submitList(data.images)
}
tvNameReviewer.text = data.author
tvReviewDesc.text = data.comment
tvDateReview.text = data.date
ratingBarReview.rating = data.rating
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ListViewHolder =
ListViewHolder(
ItemProductReviewBinding.inflate(LayoutInflater.from(parent.context), parent, false)
)
override fun onBindViewHolder(holder: ListViewHolder, position: Int) {
val review = getItem(position)
if (review != null) {
holder.bind(review, position)
}
}
private fun setupImagesRecyclerView(recyclerView: RecyclerView) {
recyclerView.apply {
layoutManager = LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)
adapter = imageAdapter
setRecycledViewPool(viewPool)
setItemViewCacheSize(20)
}
}
}
ChildAdapter
class ProductReviewImageAdapter(private val callback: ProductReviewImageAdapterCallback) : RecyclerView.Adapter<ProductReviewImageAdapter.ListViewHolder>() {
private val diffCallback = object : DiffUtil.ItemCallback<Image>() {
override fun areItemsTheSame(
oldItem: Image,
newItem: Image
): Boolean {
return oldItem.author == newItem.author
}
override fun areContentsTheSame(
oldItem: Image,
newItem: Image
): Boolean {
return oldItem.comment == newItem.comment
}
}
val differ = AsyncListDiffer(this, diffCallback)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ListViewHolder =
ListViewHolder(
ItemImageBinding.inflate(LayoutInflater.from(parent.context), parent, false)
)
override fun getItemCount(): Int = differ.currentList.size
override fun onBindViewHolder(holder: ListViewHolder, position: Int) {
holder.bind(differ.currentList[position], position)
}
inner class ListViewHolder(itemBinding: ItemImageBinding) :
RecyclerView.ViewHolder(itemBinding.root) {
private val itemBinding = ItemImageBinding.bind(itemBinding.root)
fun bind(data: Image, position: Int) {
with(itemBinding) {
image.loadImageRoundedCorner(data.thumbnail)
image.setOnClickListener {
callback.onProductReviewImageClicked(position, differ.currentList)
}
}
}
}
}
Before scrolling
on first load, the first position data displays the appropriate image
before scrolling
After a bit scrolling
the image in the first position data changes to the image in the next data
after a bit scrolling
Scroll to last data/page
the last image shows an unsuitable image
scroll to last data/page
*Note
there is only 5 data, and only first and last data contains image
The image in the first position data should be a person image and the last position image should be a pink image
I use Coil as the image loader and have used the ImageView.clear() method before loading the image
Thanks in advance, sorry if my english is bad. I'm not a native speaker
Update
Fixed :
it turned out that it was only wrong at the time of adapter initialization, the adapter should be initialized in onBindViewHolder, not in the parent adapter so that not all items have the same adapter which causes all data to change
This is my RecyclerView Adaper
class RecyclerAdapter(private val recyclerList: List): RecyclerView.Adapter(){
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): mainRecyclerViewHolder {
val itemView = LayoutInflater.from(parent.context).inflate(R.layout.quiz_item_recycler_view,
parent, false)
return mainRecyclerViewHolder(itemView)
}
override fun getItemCount() = recyclerList.size
override fun onBindViewHolder(holder: mainRecyclerViewHolder, position: Int) {
val currentItem = recyclerList[position]
holder.imageView.setImageResource(currentItem.imageResource)
holder.textView.text = currentItem.recyclerCardText
}
class mainRecyclerViewHolder(itemView: View): RecyclerView.ViewHolder(itemView){
val imageView: ImageView = itemView.rec_image
val textView: TextView = itemView.text_view_1
}
}
And this is my data class
data class RecyclerItemMain(val imageResource: Int, val recyclerCardText: String, val button: Button)
Like this:
override fun onBindViewHolder(holder: CustomViewHolder, position: Int) {
holder.yourButton.setOnClickListener { v ->
// Here your start your other activity or navigate to another fragment
}
}
Remember that if you intent to send the user to diferent activities depending on the button, you have to create a when expression what will send the user to an activity depending on another value in the recyclerItem, for example the text of the item, in your case currentItem.recyclerCardText
Here is your full adapter, re-organized:
class RecyclerAdapter(recyclerList: List<CategorySectionIcon>) :
RecyclerView.Adapter<YourAdapter.CustomViewHolder>() {
private var recyclerList: List<CategorySectionIcon>? = recyclerList
inner class CustomViewHolder(
//Get a reference to the Views in our layout//
val myView: View
) : RecyclerView.ViewHolder(myView) {
var textView: TextView = myView.findViewById(R.id.your_text)
var imageView: ImageView = myView.findViewById(R.id.your_image)
var yourButton: Button = myView.findViewById(R.id.iv_category_imagen_icon)
}
override//Construct a RecyclerView.ViewHolder//
fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CustomViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val itemView =
layoutInflater.inflate(R.layout.quiz_item_recycler_view, parent, false)
return CustomViewHolder(itemView)
}
override fun onBindViewHolder(holder: CustomViewHolder, position: Int) {
holder.textView.text = recyclerList!![position].recyclerCardText
holder.imageView.setImageResource(recyclerList!![position].image)
holder.yourButton.setOnClickListener { v ->
// Here your start your other activity or navigate to another fragment
}
}
//Calculate the item count for the RecylerView//
override fun getItemCount(): Int {
return recyclerList!!.size
}
}
After seen all the similiar questions, I don't find a solution of my problem, which the description as follows:
In my android app, I use Kotlin as language. In my app, I have a lot of category and each category have a list of products. In my home activity, I create a vertical RecyclerView named "productListOfCategory". In the "productListOfCategory" adapter,a textView to display a category name and a recyclerView to display all related product list. The following code is the description of "ProductListOfProductTypeAdapter" adapter : (ProductType = category)
class ProductListOfProductTypeAdapter(private val context: Context, private val ProductTypeIDWithProduct: Map<String, Array<ProductData>>, private val productTypeDetail: Array<ProductTypeData>)
: RecyclerView.Adapter<ProductListOfProductTypeAdapter.ViewHolder>(),ProductOfProductTypeAdapter.OnItemClickListener{
override fun onItemClick(view: View, viewModel: ProductData) {
val intent = Intent(context,ProductDetail::class.java)
context.startActivity(intent)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_list_product_product_type, parent, false)
return ViewHolder(view)
}
override fun getItemCount() = ProductTypeIDWithProduct.size
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val itemType = ProductTypeIDWithProduct.keys.elementAt(position)
holder.productTypeName.text = getName(itemType)
val arrayProductOfProductType = ProductTypeIDWithProduct[itemType] as ArrayList<ProductData>
holder.productListOfProductType.apply {
holder.productListOfProductType.layoutManager = LinearLayoutManager(context, RecyclerView.HORIZONTAL, false)
holder.productListOfProductType.adapter = ProductOfProductTypeAdapter(arrayProductOfProductType,context,this#ProductListOfProductTypeAdapter)
}
}
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val productTypeName: TextView = itemView.textViewProductTypeName
val productListOfProductType: RecyclerView = itemView.recyclerViewProductOfProductType
}
"ProductOfProductTypeAdapter" is the second adapter which the code as the following :
class ProductOfProductTypeAdapter(private val products: ArrayList<ProductData>, private val context: Context,private val mListener: OnItemClickListener)
: RecyclerView.Adapter<ProductOfProductTypeAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_product_featured, parent, false)
return ViewHolder(view)
}
override fun getItemCount() = products.size
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val productItem = products[position] as Map<String, Map<String, String>>
holder.productName.text = productItem["name"]?.get("En").toString()
holder.productWeight.text = productItem["weight"].toString().plus(context.resources.getString(R.string.ml))
holder.productPrice.text = "$".plus(productItem["price"].toString()).plus("/")
holder.productRating.text = productItem["reviewsValue"].toString()
holder.itemView.setOnClickListener {
mListener.onItemClick(holder.itemView, products[position])
notifyDataSetChanged()
}
}
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val productName: TextView = itemView.itemProdFeaturedName
val productPrice: TextView = itemView.itemProdFeaturedPrice
val productImage: ImageView = itemView.itemProdFeaturedImage
val productWeight: TextView = itemView.txtViewWeight
val productRating: TextView = itemView.itemProdFeaturedRate
}
interface OnItemClickListener {
fun onItemClick(view: View,viewModel: Map<String, Map<String, String>>)
}
My problem is How to click on product and dispaly the product detail activity.I try as the following but still not get what I want.
Shouldn't your OnItemClickListener be like the following?
interface OnItemClickListener {
fun onItemClick(view: View, product: ProductData)
}
And you need to change the way you start your ProductDetail activity, and put your product ID or something to identify the product selected in the extra data of the intent. For example:
val intent = Intent(context,ProductDetail::class.java)
intent.putExtra("PRODUCT_ID", product.id)
context.startActivity(intent)
I have a problem with:
RecyclerView: No adapter attached; skipping layout
I can't define adapter in OnCreate because the list is not ready.
How I can define adapter in OnCreate? or what is a possible solution for resolve my problem?
In onCreate I did:
adapter = MyAdapter(this#MyActivity)
adapter.data = ArrayList()
Then later I just set adapter.date = xxx
In my adapter I have:
class MyAdapter(val activity: MyActivity) :
RecyclerView.Adapter<MyAdapter.BodyViewHolder>() {
var data: MutableList<MyModel>? = null
set(value) {
field = value
notifyDataSetChanged()
}
override fun getItemCount() = data?.size ?: 0
fun ViewGroup.inflate(#LayoutRes layoutRes: Int, attachToRoot: Boolean = false): View {
return LayoutInflater.from(context).inflate(layoutRes, this, attachToRoot)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BodyViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.my_layout, parent, false)
return BodyViewHolder(view)
}
override fun onBindViewHolder(holder: BodyViewHolder, position: Int) {
holder.bindValue(data?.get(position), activity)
}
class BodyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun bindValue(record: MyModel?, activity: MyActivity) {
record?.let {
itemView.mTextView.text = ....
}
}
}
}
Worth mentioning that the height of my recycler view is wrap_content
<android.support.v7.widget.RecyclerView
android:id="#+id/mRecyclerView"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
It's totally ok if you don't have data in onCreate. What you need to do is to define the adapter and bind it with your RecyclerView. Once you have data ready, add data to the list in adapter and notify it. Example as below
class MyAdapter: RecyclerView.Adapter<MyViewHolder>() {
private val data = mutableListOf<MyModel>()
override fun getItemCount() = data.count()
fun addData(data : MyModel) {
// add single data the list or call addAll() to add a group of data.
// just remember not to replace the variable
}
fun clearData() {
}
fun deleteData(id: Int) {
}
}
class adpCoba(val context: Context, val datalist:ArrayList<dataCoba>):RecyclerView.Adapter<adpCoba.MyViewHolder>(){
class MyViewHolder (itemView:View):RecyclerView.ViewHolder(itemView){
val text :TextView=itemView.findViewById(R.id.textcoba)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val itemView = LayoutInflater.from(context).inflate(R.layout.fetch_coba,parent,false)
return MyViewHolder(itemView)
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
val currntItem = datalist [position]
holder.text.text= currntItem.nama
}
override fun getItemCount(): Int {
return datalist.size
}
}