I am creating a GenericAdapter for handling single row|item layouts. Everything working fine only view-binding is not updating data..
I want to get RecyclerView.ViewHolder binding in callback ,I know I can bind it in adapter using BR.item and executePending
I want viewDataBinding context in a Callback
holder.binding.name.text = mutableList[pos]
Above line in TestActivity not working properly
GenericAdapter.kt
class GenericAdapter<T,VB:ViewDataBinding>(
var items:MutableList<T>,
#LayoutRes val resLayoutID:Int,
val onBind:(holder:GenericViewHolder<T,VB>,pos:Int) -> Unit
): RecyclerView.Adapter<GenericViewHolder<T,VB>>() {
lateinit var mItemBinding:VB
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): GenericViewHolder<T,VB> {
val layoutInflater = LayoutInflater.from(parent.context)
mItemBinding = DataBindingUtil.inflate(layoutInflater, resLayoutID, parent, false)
return GenericViewHolder(mItemBinding)
}
override fun onBindViewHolder(holder: GenericViewHolder<T,VB>, position: Int) {
onBind(holder,position)
}
override fun getItemCount(): Int = items.size
}
GenericViewHolder.kt
class GenericViewHolder<T,VB: ViewDataBinding>(val binding: VB)
:RecyclerView.ViewHolder(binding.root){
val mItemBinding:VB = binding
}
TestActivity.kt
class TestActivity:AppCompatActivity() {
lateinit var recyclerView: RecyclerView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_test)
recyclerView = findViewById(R.id.recyclerView)
recyclerView.layoutManager = LinearLayoutManager(this)
recyclerView.setHasFixedSize(true)
populateList()
}
private fun populateList(){
val mutableList = mutableListOf<String>("apple","mango","tutti fruti","apricot",
"apple","mango","tutti fruti","apricot",
"apple","mango","tutti fruti","apricot",
"apple","mango","tutti fruti","apricot")
val mAdapter = GenericAdapter<String,ItemCountryBinding>(mutableList,R.layout.item_country){ holder,pos ->
//val nameTv = holder.itemView.findViewById<TextView>(R.id.name)
//nameTv.text = mutableList[pos]
holder.binding.name.text = mutableList[pos]
}
recyclerView.adapter = mAdapter
}
}
Where as below code working fine
val nameTv = holder.itemView.findViewById<TextView>(R.id.name)
nameTv.text = mutableList[pos]
I would recommend do onBind inside view holder.
I don't see reason why you need this callback.
You already passed mutableList to the adapter.
For example here is my ViewHolder
class NewsViewHolder(private val binding: ListItemNewsBinding) : RecyclerView.ViewHolder(binding.root) {
fun bind(news: RedditPost, itemClick: (RedditPost, View) -> Unit) {
with(binding) {
root.transitionName = news.thumbnail
root.setOnClickListener { itemClick.invoke(news, binding.root) }
title.text = news.title
image.setImageUrl(news.thumbnail)
author.text = news.author
xHoursAgo.text = news.created_utc
numComments.text = news.numComments
}
}
}
And
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
if (isLoadingMore && position == (itemCount - 1)) return
(holder as? NewsViewHolder)?.bind(news[position], itemClick)
}
Related
I'm new to Kotlin. I have a MainActivity and a list view inside.
When an item is clicked in the list view, I want to update a textView inside MainActivity.
However because data should be passed from ListView to MainActivity, I did something weird:
MainActivity.kt:
data class Country(val imgRes:Int, val name:String)
class MainActivity : AppCompatActivity() {
private lateinit var binding : ActivityMainBinding
private val data = arrayListOf<Country>()
val imgRes = intArrayOf()
val data1 = arrayOf()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
for(i in imgRes.indices) {
val country = Country(imgRes[i], data1[i])
data.add(country)
}
val adapter = RecyclerAdapter(data, object:RecyclerAdapter.OnItemClickListener {
override fun onItemClick(v: View, pos: Int) {
binding.textView.text = (v as TextView).text
}
})
binding.recycler1.adapter = adapter
binding.recycler1.layoutManager = StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL)
}
}
ListView.kt:
class RecyclerAdapter(private val dataSet:List<Country>, private val listener:OnItemClickListener) : RecyclerView.Adapter<RecyclerAdapter.ViewHolder>() {
public interface OnItemClickListener {
fun onItemClick(v:View, pos:Int)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val binding = RowBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return ViewHolder(binding)
}
override fun getItemCount(): Int {
return dataSet.size
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(position)
}
inner class ViewHolder(val binding: RowBinding) : RecyclerView.ViewHolder(binding.root) {
val rowImageView = binding.rowImageView
val rowTextView = binding.rowTextView
init {
binding.root.setOnClickListener {
listener.onItemClick(rowTextView, adapterPosition)
}
}
fun bind(pos: Int) {
with(binding) {
rowImageView.setImageResource(dataSet[pos].imgRes)
rowTextView.text = dataSet[pos].name
}
}
}
}
I declared interface(OnItemClickListener) at ListView.kt and redefine it at MainActivity.kt.
It works fine but is there any better way to do this?
You can use higher order functions instead of interface.
Define this function top of your Adapter:
var onClick: ((text: String) -> Unit)? = null
in ViewHolder:
binding.root.setOnClickListener {
onClick?.invoke(dataSet[position].text)
}
in MainActivity:
adapter.onClick = { name ->
binding.textView.text = name
}
You can also find more information about higher orders in here
<include android:layout_width="match_parent"
android:layout_height="wrap_content"
layout="#layout/yourlayout" />
Try this in your Adapter:
var itemClickListener: ((position: Int, name: String) -> Unit)? = null
Bindviewholder:
itemClickListner.invoke(1,"anyvalue")
in Activity:
adapter.itemClickListener = {
position, name ->
Toast.makeText(requireContext(),"position is $position name is $name ",Toast.LENGTH_SHORT).show()
}
in Adapter:
private var mListener: OnItemClickListener,
interface OnItemClickListener {
fun onClick(position: Int, routerArray: ArrayList<Router>)
}
binding.yourView.setOnClickListener {
mListener.onClick(adapterPosition, routerArray)
}
in Activity:
override fun onClick(position: Int, yourArray: ArrayList<Model>) {
binding.textView.text = yourArray[position].text
}
im facing errors in recyclerView here is //
data class categories(
val Name: String,
val Image:Int
)
here is model /////
object CategoriesModel {
fun getImages(): ArrayList<categories> {
val ImagesList = ArrayList<categories>()
val categoryFirst = categories(
"Memmals",R.drawable.memmals
)
ImagesList.add(categoryFirst)
val categorySecond = categories(
"Memmals",R.drawable.fish
)
ImagesList.add(categorySecond)
return ImagesList
}
}
//// adapter///
class CategoriesAdapter(private val itemList: ArrayList) :
RecyclerView.Adapter<CategoriesAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CategoriesAdapter.ViewHolder {
val view =LayoutInflater.from(parent.context)
.inflate(R.layout.memmals, parent,false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: CategoriesAdapter.ViewHolder, position: Int) {
holder.bind(categories("Memmals",2))
}
override fun getItemCount(): Int {
return itemList.size
}
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun bind(category: categories) {
val textViewName = itemView.findViewById<TextView>(R.id.txtCategory)
textViewName.text = category.Name
val txtImage=itemView.findViewById<View>(R.id.image)
txtImage.id=category.Image
}
}
}
/////main activity///
class MainActivity : AppCompatActivity() {
private lateinit var mAdapter: CategoriesAdapter
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
binding= ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.recyclerView.setOnClickListener{
val intent=Intent(this,Level1::class.java)
startActivity(intent)
}
val recyclerView = findViewById<RecyclerView>(R.id.recyclerView)
recyclerView.layoutManager = GridLayoutManager(this,2)
val itemList = ArrayList<CategoriesModel>()
// from here
itemList.add(categories("Memmals",R.drawable.memmals))
itemList.add(categories("Fishes",R.drawable.fish))
itemList.add(categories("Arthropods",R.drawable.dear))
itemList.add(categories("Yalk",R.drawable.yalk))
//to here errors are there
//Adapter setting
mAdapter = CategoriesAdapter(itemList)
recyclerView.adapter = mAdapter
}
}
/// here is the error///
MainActivity.kt: (34, 22): Type mismatch: inferred type is categories but CategoriesModel was expected
MainActivity.kt
val itemList = ArrayList<CategoriesModel>()
should be changed to
val itemList = ArrayList<categories>()
because you are using CategoriesModel object and expected is categories.
I am fetching JSON data from API and passing it in recycler view but if I want to fetch new data and display it in recycler view then I have to clear the list and then add new data in that list and notify the adapter that the data is changed but it is not updated what should I do?
MainActivity.kt
class MainActivity : AppCompatActivity() {
private lateinit var recipeViewModel: RecipeViewModel
private lateinit var mainBinding: ActivityMainBinding
private lateinit var recipeAdapter: RecipeAdapter
private lateinit var recipeItemList: ArrayList<Hit>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mainBinding = ActivityMainBinding.inflate(layoutInflater)
setContentView(mainBinding.root)
recipeViewModel =
ViewModelProvider(
this,
ViewModelProvider.AndroidViewModelFactory
.getInstance(application)
)[RecipeViewModel::class.java]
recipeItemList = arrayListOf()
mainBinding.recyclerView.layoutManager = LinearLayoutManager(this)
mainBinding.recyclerView.hasFixedSize()
recipeAdapter = RecipeAdapter(this)
mainBinding.recyclerView.adapter = recipeAdapter
recipeViewModel.recipeLiveData.observe(this, Observer { recipeItems ->
recipeItemList.addAll(recipeItems.hits)
recipeAdapter.updateRecipes(recipeItemList)
Log.d("RESPONSE", recipeItems.toString())
Log.d("List size", recipeAdapter.itemCount.toString())
})
searchRecipeName()
}
private fun searchRecipeName() {
mainBinding.searchRecipeFabBtn.setOnClickListener {
val view = layoutInflater.inflate(R.layout.recipe_search_layout, null)
val searchRecipeET = view.findViewById<EditText>(R.id.searchRecipeET)
val searchRecipeBtn = view.findViewById<Button>(R.id.searchRecipeBtn)
val bottomSheetDialog = BottomSheetDialog(this)
bottomSheetDialog.apply {
this.setContentView(view)
this.show()
}
searchRecipeBtn.setOnClickListener {
val recipeName = searchRecipeET.text.toString()
searchRecipeName(recipeName, searchRecipeET, bottomSheetDialog)
}
}
}
private fun searchRecipeName(
recipeName: String,
searchRecipeET: EditText,
bottomSheetDialog: BottomSheetDialog
) {
if (recipeName.isEmpty()) {
searchRecipeET.error = "Please enter recipe name"
} else {
recipeViewModel.getRecipes(recipeName)
bottomSheetDialog.dismiss()
}
}
}
RecipeAdapter.kt
class RecipeAdapter(val context: Context) : RecyclerView.Adapter<RecipeAdapter.RecipeViewHolder>() {
private val recipesList: ArrayList<Hit> = arrayListOf()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecipeViewHolder {
val layoutInflater = LayoutInflater.from(context)
val view = layoutInflater.inflate(R.layout.recipe_items_layout, null, false)
return RecipeViewHolder(view)
}
override fun onBindViewHolder(holder: RecipeViewHolder, position: Int) {
val currentItem = recipesList[position]
holder.recipeImageView.load(currentItem.recipe.image)
holder.recipeNameText.text = currentItem.recipe.label
}
override fun getItemCount(): Int {
return recipesList.size
}
class RecipeViewHolder(itemView: View) :RecyclerView.ViewHolder(itemView) {
val recipeImageView: ImageView = itemView.findViewById(R.id.recipeImageView)
val recipeNameText: TextView = itemView.findViewById(R.id.recipeNameText)
}
fun updateRecipes(newRecipesList: ArrayList<Hit>){
recipesList.clear()
Log.d("RECIPE SIZE", "${recipesList.size}")
recipesList.addAll(newRecipesList)
notifyDataSetChanged()
}
}
This may be helpful.
Be careful of this :
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mainBinding = ActivityMainBinding.inflate(layoutInflater)
setContentView(mainBinding.root)
recipeViewModel =
ViewModelProvider(
this,
ViewModelProvider.AndroidViewModelFactory
.getInstance(application)
)[RecipeViewModel::class.java]
recipeItemList = arrayListOf()
mainBinding.recyclerView.layoutManager = LinearLayoutManager(this)
mainBinding.recyclerView.hasFixedSize()
recipeAdapter = RecipeAdapter(this)
mainBinding.recyclerView.adapter = recipeAdapter
recipeViewModel.recipeLiveData.observe(this, Observer { recipeItems ->
// You're adding items here but never clear the list
// list will be bigger every time you'll be notified
// recipeItemList.addAll(recipeItems.hits
// recipeAdapter.updateRecipes(recipeItemList)
// Do this instead
recipeItemList = recipeItems.hits
recipeAdapter.updateRecipes(recipeItemList)
Log.d("RESPONSE", recipeItems.toString())
Log.d("List size", recipeAdapter.itemCount.toString())
})
searchRecipeName()
}
Also, here: It's a little better to do this (https://stackoverflow.com/a/10298038/4221943)
fun updateRecipes(newRecipesList: ArrayList<Hit>){
recipesList = newRecipesList
Log.d("RECIPE SIZE", "${recipesList.size}")
notifyDataSetChanged()
}
BTW it will always be more efficient to use the more specific change events if you can. Rely on notifyDataSetChanged() as a last resort. It is also good practice to use notifyItemInserted(mItems.size() - 1) for "easier" solution.
You could convert the RecyclerView.Adapter into a ListAdapter:
class RecipeAdapter(val context: Context) : ListAdapter<Hit, RecipeAdapter.RecipeViewHolder>(RecipeDiffCallback()) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecipeViewHolder {
val layoutInflater = LayoutInflater.from(context)
val view = layoutInflater.inflate(R.layout.recipe_items_layout, null, false)
return RecipeViewHolder(view)
}
override fun onBindViewHolder(holder: RecipeViewHolder, position: Int) {
val currentItem = getItem(position)
holder.recipeImageView.load(currentItem.recipe.image)
holder.recipeNameText.text = currentItem.recipe.label
}
class RecipeViewHolder(itemView: View) :RecyclerView.ViewHolder(itemView) {
val recipeImageView: ImageView = itemView.findViewById(R.id.recipeImageView)
val recipeNameText: TextView = itemView.findViewById(R.id.recipeNameText)
}
}
class RecipeDiffCallback : DiffUtil.ItemCallback<Hit>() {
// Change this condition based on the attribute of `Hit` that will change
override fun areItemsTheSame(oldItem: Hit, newItem: Hit): Boolean = oldItem.id == newItem.id
override fun areContentsTheSame(oldItem: Hit, newItem: Hit): Boolean = oldItem == newItem
}
Then update its content with the submitList method.
Every item not satisfying the RecipeDiffCallback conditions will be automatically updated:
recipeViewModel.recipeLiveData.observe(this, Observer { recipeItems ->
recipeAdapter.submitList(recipeItems.hits)
})
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.recyclerviewShoes.apply {
adapter = ShoeAdapter(ShoeRepository.getShoes())
{ shoeModel ->
val intent = Intent(this#MainActivity, ActivityProductDetail::class.java)
intent.putExtra(ActivityProductDetail.CATEGORY, shoeModel.product_name)
startActivity(intent)
}
layoutManager = LinearLayoutManager(this#MainActivity, LinearLayoutManager.HORIZONTAL, false)
}
}
}
My Adapter
class ShoeAdapter (
private val shoeList: List<ShoeModel>,
private val onClick: (ShoeModel) -> Unit
) : RecyclerView.Adapter<ShoeViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ShoeViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val binding = ItemProductBinding.inflate(layoutInflater, parent, false)
return ShoeViewHolder(binding)
}
override fun getItemCount() = shoeList.size
override fun onBindViewHolder(holder: ShoeViewHolder, position: Int) {
val shoe = shoeList[position]
holder.bind(shoe)
holder.itemView.setOnClickListener {
onClick(shoe)
}
}
}
class ShoeViewHolder(
private val binding: ItemProductBinding): RecyclerView.ViewHolder(binding.root) {
fun bind(shoe: ShoeModel) {
binding.apply {
textProductName.text = shoe.product_manufacturer
textShoeName.text = shoe.product_name
textPrice.text = shoe.product_price
imageShoe.setImageResource(shoe.image)
}
}
}
When I click the item in my RecyclerView, nothing happens.
Inside your onBindViewHolder function, replace this:
holder.itemView.setOnClickListener {
onClick(shoe)
}
with this:
holder.itemView.setOnClickListener {
onClick(shoe).invoke()
}
I've created a RecyclerView with Horizontal Scrolling and PageSnapHelper. Now I think to replace RecyclerView with ViewPager2.? Can I simply set RecyclerView Adapter I've created earlier for new ViewPager2.?
Adapter Class goes here
class QuoteAdapter(
val context: Context,
val list: ArrayList<ResultsItem>
) : RecyclerView.Adapter<QuoteAdapter.QuoteViewHolder>() {
var i = 0;
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int):
QuoteViewHolder {
val view = LayoutInflater.from(context)
.inflate(R.layout.item_quote, parent, false)
return QuoteViewHolder(view)
}
override fun getItemCount() = list.size
override fun onBindViewHolder(holder: QuoteViewHolder, position: Int) {
holder.quote.text = list[position].quoteText
holder.quote_by.text = "- ${list[position].quoteAuthor}"
ColorStore()
if (position == 0) {
holder.quote_bg.setBackgroundColor(ContextCompat.getColor(context, R.color.md_blue_400))
} else {
holder.quote_bg.setBackgroundColor(ContextCompat.getColor(context, colorList.random()))
}
holder.quote_bg.setOnClickListener {
holder.quote_bg.setBackgroundColor(ContextCompat.getColor(context, colorList.random()))
}
}
class QuoteViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val quote = itemView.quote_text
val quote_by = itemView.by_text
val quote_bg = itemView.quote_bg
}
}
I just set recyclerview adapter to viewpager2. It works.