class testAdapter(
private val list: ArrayList<Objects>
) : RecyclerView.Adapter<testViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): testViewHolder {
*BREAKPOINT HERE*
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.X, parent, false)
val viewHolder = testViewHolder(view)
return viewHolder
}
override fun onBindViewHolder(holder: testViewHolder, position: Int) {
holder.bind()
}
override fun getItemCount(): Int { return contentList.size }
}
I can see that program was here, because breakpoint is check-marked. I'm pretty sure, that it's caused by async (by retrofit) call, which gathers item list, but how can I fight with this? I can write code, if I can't debug none of Adapters functions.
I set up adapter like this:
onCreate() {
recyclerView =rootView.findViewById(R.id.test)
gridLayoutManager = /* custom grid layout manager */ (this is fine)
recyclerView.layoutManager = gridLayoutManager
}
asyncFunctionResult(list: List) {
contentAdapter = testAdapter()
recyclerView.adapter = contentAdapter
}
Sorry for pseudo code, but this should be enough. Obviously, there are alot of things wrong in adapter ect., but it should atleast get a hit [stop] from debugger. Any ideas?
For anybody having this problem: try checking out if you forgot to add the recyclerview.layoutManager.
It looks like your list is not filled, onCreateViewHolder is only called when it needs to inflate an item which is when a listitem needs to be shown.
You could try to add an item to contentlist or list (as your code shows 2 lists, I assume this is also pseudocode).
Related
I try to make a small file manager, I want that when holding a file or folder I get a contextual menu, and I tried to use registerForContextMenu(newRecyclerView) but it does not work for me, nothing happens at all, instead if I do it with another element like a button or an ImageView, the menu comes out perfect, and I've been googling for hours, but most of the solutions I've found are in java, and I don't know how to implement them in kotlin, one thing to keep in mind is that when handling files , the recylerview is going to be constantly changing as we navigate through the directories, what I want is a menu where I can copy, cut the file and stuff, I know there are other solutions for this, but I want to implement them with a context menu , here is MyAdapter, I don't put the Main Activity,because it's a riot
import...
class MyAdapter(private val newsList:
ArrayList<News>):RecyclerView.Adapter<MyAdapter.MyViewHolder>()
{
private lateinit var mListener:OnItemClickListener
interface OnItemClickListener{
fun onItemClick(position: Int)
}
fun setOnItemClicKListener(listener:OnItemClickListener){
mListener = listener
}
override fun onCreateViewHolder(parent: ViewGroup, viewType:
Int): MyViewHolder {
val itemView =
LayoutInflater.from(parent.context).inflate(R.layout.list_items,
parent, false)
return MyViewHolder(itemView, mListener)
}
override fun onBindViewHolder(holder: MyViewHolder, position:
Int) {
val currentItem = newsList[position]
holder.titleImage.setImageResource(currentItem.titleImage)
holder.tvHeading.text = currentItem.heading
}
override fun getItemCount(): Int {
return newsList.size
}
class MyViewHolder(itemView: View, listener:
OnItemClickListener):RecyclerView.ViewHolder(itemView){
val titleImage: ShapeableImageView =
itemView.findViewById(R.id.title_image)
val tvHeading:TextView = itemView.findViewById(R.id.tvHeading)
init{
itemView.setOnClickListener {
listener.onItemClick(adapterPosition)
}
}
}
}
Instead of registering the entire RecyclerView with a context menu, you should register each child view. This is because you are holding the child view, not the RecyclerView itself.
I have a RecyclerView which has an EditText in its items.
For each EditText I have added a textChangeListener.
Whenever the user types a character I want to do an input validation and so I can change the item, for example show red border in case of wrong input etc...
After I do the validation I call:
adapter.notifyItemChanged(itemPosition)
to update the view.
But the problem is that the focus is lost from the EditText and I have to click again inside the box in order to continue typing.
Is there a solution for this case? How can I continue typing while the view is updated after calling notifyItemChanged ?
ADAPTER
class ItemAdapter() : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val inflater = LayoutInflater.from(parent.context)
val binding = LayoutItemBinding.inflate(inflater, parent, false)
binding.editText.addTextChangedListener {
// Do layout changes....
if (isInvalidInput) {
item.setError = true
....
...
}
adapter.notifyItemChanged(itemPosition)
}
return ItemViewHolder(binding)
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
holder.bind(items[position])
}
inner class ItemViewHolder(val binding: LayoutItemBinding)
: RecyclerView.ViewHolder(binding.root) {
fun bind(model: ItemModel) {
// Setup view for item
}
}
}
Try to move your error setting code in the bind function of the ViewHolder and update the error status outside of the TextWatcher:
class ItemAdapter() : RecyclerView.Adapter<RecyclerView.ViewHolder>()
{
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val inflater = LayoutInflater.from(parent.context)
val binding = LayoutItemBinding.inflate(inflater, parent, false)
return ItemViewHolder(binding)
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
holder.bind(items[position], position)
}
inner class ItemViewHolder(val binding: LayoutItemBinding) : RecyclerView.ViewHolder(binding.root) {
fun bind(model: ItemModel, position: Int) {
binding.editText.addTextChangedListener {
// Update the ItemModel on text change
adapter.notifyItemChanged(position)
}
// Update the view error status everytime it is updated
val isInvalidInput = ... // Add your logic
if (isInvalidInput) {
model.setError = true
....
....
}
}
}
}
First, Please post your code.
If you called notifyDatasetChanged(), all views of recyclerview will refreshed (it means that the views could be recreated.).
So if you want remain focus in edittext, you not call notifyDatasetChanged() or set focus to your edittext after notifyDataChanged().
But setFocus is bit more difficult.
So I suggest method that update views of recyclerview without call notifyDatasetChanged().
I think you can directly update view of recyclerview without call notifyDataChanged().
You should save the id of item that contains the EditText that is editing. And in the bind() method of ViewHolder, you will check if the item is currently being edit, you will call requestFocus() method on EditText.
And please take note that when you call notifyDataChanged(), the RecycleView maybe check the data of each item to know whether it was changed or not. If the data of an item was changed, RecycleView will redraw that item.
I am researching on how to add an onClick event on my recyclerview properly,
currently I am using the interface inside my customAdapter
class CategoryAdapter(val categoryList : List<CategoryObject>, val context: Context, val mItemClickListener: MainInterface) : RecyclerView.Adapter<CategoryAdapter.ViewHolder>() {
interface MainInterface {
fun onCategoryItemClick(categoryKey: Int)
}
override fun getItemCount(): Int {
return categoryList.size
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(LayoutInflater.from(context).inflate(R.layout.listview_category, parent, false))
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder?.categoryName?.text = categoryList[position].categoryName
// holder?.categoryName.setOnClickListener{
// mItemClickListener.onCategoryItemClick(position)
// }
// holder?.categoryName?.setOnClickListener { listener(categoryList[position]) }
}
inner class ViewHolder (view: View) : RecyclerView.ViewHolder(view) {
val categoryName = view.lv_txt_category_name
init {
view.setOnClickListener {
mItemClickListener.onCategoryItemClick(adapterPosition)
}
}
}
}
viewHolder's onClick does not register on my activity ovveride function.
but putting the onClick inside onBindViewHolder works perfectly,
I'm not sure which is more efficient, if the onBindViewHolder onClick is the right answer, then I'll stick with it, but if the viewHolder is the right one, why it does not work properly?
Thanks in advance!
Update
This is the stackoverflow post I'm using to research things
RecyclerView itemClickListener in Kotlin
tried android:clickable="true" in your xml?
As the documentation suggest for:
RecyclerView.Adapter.html#onBindViewHolder
Called by RecyclerView to display the data at the specified position.
This method should update the contents of the itemView to reflect the
item at the given position.
And for the RecyclerView.Adapter.html#onCreateViewHolder
Called when RecyclerView needs a new RecyclerView.ViewHolder of the
given type to represent an item.
This new ViewHolder should be constructed with a new View that can
represent the items of the given type. You can either create a new
View manually or inflate it from an XML layout file.
As onCreateViewHolder is a method where we return the type of ViewHolder only while in onBindViewHolder we update or initialise the data to display.
It means no data is binded in onCreateViewHolder and binding anything in this method will have no effect.
I want to display new list of items retrieved from ViewModel in custom RecyclerView.Adapter. To do so I pass the retrieved list to adapter and invoke notifyDataSetChanged(), but nothing changes on the UI.
I've debugged code and adapter's list had 1 element (UI displayed 1 element), new retrieved list had 2 elements - after setting new list in adapter and invoking notifyDataSetChanged() UI didn't change, but should append that 1 element.
Fragment's code:
trip_details_participantsList.layoutManager = LinearLayoutManager(activity, LinearLayoutManager.HORIZONTAL, false)
trip_details_participantsList.adapter = userBriefAdapter
tripsDetailsViewModel.getTripParticipants(tripId).observe(this, Observer {participants ->
tripsDetailsViewModel.getUsersBriefs(participants.map { x -> x.userId }).observe(this, Observer{ users ->
userBriefAdapter.setData(users)
})
})
Custom Adapter and ViewHolder:
class UserBriefAdapter : RecyclerView.Adapter<UserBriefViewHolder>() {
private var users: List<UserBrief> = mutableListOf()
fun setData(items: List<UserBrief>){
this.users = items
notifyDataSetChanged()
}
override fun onBindViewHolder(holder: UserBriefViewHolder, position: Int) =
holder.bind(users[position])
override fun getItemCount() = users.size
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserBriefViewHolder {
val inflater = LayoutInflater.from(parent.context)
return UserBriefViewHolder(inflater, parent)
}
}
class UserBriefViewHolder(inflater: LayoutInflater, parent: ViewGroup) : RecyclerView.ViewHolder(inflater.inflate(
R.layout.trip_details_list_item, parent, false)) {
private var profilePictureImageView: ImageView = itemView.findViewById(R.id.trips_details_profile_image)
fun bind(userBrief: UserBrief){
Picasso.get()
.load(userBrief.profilePictureUrl)
.into(profilePictureImageView)
}
}
I've read existing topics about it, but every accepted solution is something like my adapter's setData() function with notifyDataSetChanged(). I've also tried to set new instance of UserBriefAdapter to RecyclerView each time observed values changed, but results were the same. When I go back and forth the view, then it correctly displays the elements, but I want to achieve this without changing the view.
It could be an issue of threading. Try to post a runnable to the main thread in your observer callback:
tripsDetailsViewModel.getUsersBriefs(participants.map { x -> x.userId }).observe(this, Observer{ users ->
view?.post { userBriefAdapter.setData(users) }
})
Done some more debugging and figured out that notifyDataSetChanged() wasn't the problem. I was passing wrong profilePictureUrl to UserBriefViewHolder, so Picasso library doesn't fetch an image and in consequence UI wasn't updated.
I'm having a problem when call the notifyItemRangeInserted of the adapter. When I call this method, nothing happens, simple as that. I've tried to set some println() in the ViewHolderAdapter, but he isn't called, so I can't view the prints.
I've tried all of the "notify" commands of the adapter, and none of these work. Simply nothing happens.
That's my MainActivity. All the objects and arrays I've tested, all of them are working like a charm. I can't understand why the notify doesn't work.
class MainActivity:AppCompatActivity(){
//Declarations of the variables
var pageNumber = 1
var limitPerPage = 5
lateinit var product: Product
var productList = ArrayList<EachProduct>()
var myAdapter =ViewHolderAdapter(productList, productList.size)
override fun onCreate(savedInstanceState:Bundle?){
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
recyclerView.layoutManager = LinearLayoutManager(this#MainActivity)
recyclerView.adapter = myAdapter
The code to add items on the list and notify the ViewHolderAdapter is
//update the product list
fun updateProductList(product:Product){
for(i in 0 until 5 step 1){
productList.add(product.produtos[i])
}
showData(productList,pageNumber*limitPerPage)//then notify
}
fun showData(productList:List<EachProduct>,productsListSize:Int){
myAdapter.notifyItemRangeInserted(0,productList.size)
}
That's my ViewHolderAdapter class
class ViewHolderAdapter(private var products: List<EachProduct>, private val productsListSize: Int): RecyclerView.Adapter<ViewHolderAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent:ViewGroup,viewType:Int): ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.recyclerview_layout, parent, false)
returnViewHolder(view)
}
override fun getItemCount() = productsListSize
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.productName.text=products[position].nome
Picasso.get().load(products[position].fabricante.img).into((holder.productLogo))
}
class ViewHolder(itemView: View): RecyclerView.ViewHolder(itemView) {
val productName:TextView=itemView.ProductName
var productLogo:ImageView=itemView.ProductLogo
}
}
I expect the ViewHolderAdapter to be called, but this is not occurring. Why is that happens? I can't understand. I'll be very grateful if someone could help me.
Because initial value of the variable productsListSize is zero. Remove it from the constructor and change adapter like this:
class ViewHolderAdapter(private var products: List<EachProduct>): RecyclerView.Adapter<ViewHolderAdapter.ViewHolder>() {
override fun getItemCount() = products.size
}
A reason can be that the initial size of the item list you want to show is 0 and the recycler view height is set to wrap content. At the moment, for this case I see 2 options:
Keep wrap content for recycler view and make sure the initial list size > 0.
Set the height of the recycler view to match_parent or a fixed size and notifyItemRangeInserted will work without issues.