I'm trying to create a recyclerView, but when I try to reference my textView I get an error that says Unresolved Reference: textViewTitle, I have tried many ways and I could not, I appreciate your wise help.
class RecyclerAdapter(private val userList: ArrayList<Sitios> = ArrayList()) :
RecyclerView.Adapter<RecyclerAdapter.ViewHolder>() {
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val v = LayoutInflater.from(parent.context).inflate(R.layout.card_view, parent, false)
return ViewHolder(v)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
//Unresolved reference: textViewTitle
holder.itemView.textViewTitle.text = userList[position].description
}
override fun getItemCount(): Int = userList.size
holder.itemView is the View you pass into your ViewHolder class when onCreateViewHolder runs. It's just a normal View, and there's no field called textViewTitle in that class, which is what you're trying to reference with holder.itemView.textViewTitle - that's why it's saying the reference is unresolved
What you probably want to do is find a TextView with that ID within itemView's hierarchy, so you need to do
holder.itemView.findViewById<TextView>(R.id.textViewTitle)
or whatever the ID is. But typically, people will put fields in the ViewHolder class instead, set them by doing the lookup once, and then you can just access them like you're trying to do:
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
lateinit var textViewTitle : TextView
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val v = LayoutInflater.from(parent.context).inflate(R.layout.card_view, parent, false)
// or you could make the TextView a val in the ViewHolder's constructor and just pass it in instead of setting it after creation
val holder = ViewHolder(v)
holder.textViewTitle = v.findViewById<TextView>(R.id.textViewTitle)
return ViewHolder(v)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
// now the reference is a field on the ViewHolder
holder.textViewTitle.text = userList[position].description
}
this is better because onBindViewHolder runs every time a new item scrolls into view, so you don't want to be doing lots of findViewByIds when you can just do it once
If you're doing it the field way, like up there, instead of making the view references constructor parameters (which can get messy when you have a few) I'd write it more like this:
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val v = LayoutInflater.from(parent.context).inflate(R.layout.card_view, parent, false)
return ViewHolder(v).apply {
textViewTitle = v.findViewById(R.id.textViewTitle)
// and any other views you need to set up
}
}
it looks pretty neat, especially with several views!
Related
I have a view model I have added a click event listener. How do I navigate from that ViewModel into a Fragment
The ViewModel Code
class PromoAdapter(var promo: ArrayList<PromoModal>) :
RecyclerView.Adapter<PromoAdapter.UserViewHolder>() {
override fun onBindViewHolder(holder: UserViewHolder, position: Int) {
holder.bind(promo[position])
holder.promoRow.setOnClickListener() {
// Navigate to Fragment with name UserFragment
};
}
}
User Fragment Code
class UserFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?,
): View? {
val view: View = LayoutInflater.from(container!!.context)
.inflate(R.layout.user_page_fragment, container, false)
}
}
I have tried writing the following code still not working
Inside the ViewModel Click Event
val intent = Intent(itemView.context, UserFragment::class.java)
itemView.context?.startActivity(intent)
I cant find something working any help will be appreciated
It's better to move the click listener to a fragment
In Adapter:
class PromoAdapter(
var promo: ArrayList<PromoModal>,
val openFragment: (promoModal: PromoModal) -> Unit
) :
RecyclerView.Adapter<PromoAdapter.UserViewHolder>() {
override fun onBindViewHolder(holder: UserViewHolder, position: Int) {
holder.bind(promo[position])
val currentItem = promo.getOrNull(position) ?: return
holder.promoRow.setOnClickListener {
openFragment(currentItem)
};
}
}
In Fragment:
val mAdapter = PromoAdapter(
promo = promoList,
openFragment = { promoModal ->
// Navigate to Fragment with name UserFragment
}
)
I have an XML layout of the Fragment with RecyclerViewunder the bonnet.
This RecyclerView has an Adapter with 2 viewTypes
First viewType - is a Photo at the first position in the list
Second viewType - the rest Photos in the list
I wanna use LinearLayoutManager for the First photo and StaggeredLayoutManager for the rest.
It's quite easy if you work with GridLayoutManager and spanSizeLookup, but nothing related with Staggered.
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val inflater = LayoutInflater.from(parent.context)
return when (viewType) {
FIRST_PHOTO -> FirstPhotoViewHolder(
ItemPhotoFirstBinding.inflate(inflater, parent, false)
)
else -> HomeViewHolder(
ItemHomeFeedBinding.inflate(inflater, parent, false)
)
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val currentItem = getItem(position)
currentItem?.let {
when (position) {
0 -> (holder as FirstPhotoViewHolder).bind(it)
else -> (holder as HomeViewHolder).bind(it)
}
}
}
Example
Thank you!
Well as the title says, I need a listener that gives me the position of the item which was recycled.
So far I only found how to get the holder of the recycled item:
override fun onViewRecycled(holder: ViewHolder) {
super.onViewRecycled(holder)
}
and I don't get why it doesn't also return the damn position of it
You might want to use either of the methods according to your use case to get the position of the view to be recycled to create the new view.
override fun onViewRecycled(holder: ViewHolder) {
super.onViewRecycled(holder)
holder.layoutPosition
holder.absoluteAdapterPosition
holder.bindingAdapterPosition
}
holder.layoutPosition: Returns the position of the ViewHolder in terms of the latest layout pass.
holder.absoluteAdapterPosition: Returns the Adapter position of the item represented by this ViewHolder with respect to the RecyclerView's Adapter.
holder.bindingAdapterPosition Returns the Adapter position of the item represented by this ViewHolder with respect to the Adapter that bound it.
You can use the tag of the view to complete
override fun onBindViewHolder(holder: HostingAndAdditionalHolder, position: Int)
{
...
holder.binding.cb.tag = position
...
}
Set up the listener when creating the view. In the code below, I set up the listener for cb.
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): HostingAndAdditionalHolder {
return HostingAndAdditionalSettingsItemViewBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
).run {
cb.setOnClickListener(checkOnClickListener)
HostingAndAdditionalHolder(this)
}
}
This is my listener code
private val checkOnClickListener = View.OnClickListener { view ->
view.tag.let {
if (it is Int) it else null
}?.let {
...
}
}
I have a recycleView containing n reviews. Each review has a nickname, body and a button that gives the opportunity to report my review if something is wrong. Problem is I don't know how to retrieve these info on click. Can someone show me some code draft maybe?
On your RecyclerView class override the following:
#Override
public void onViewAttachedToWindow(#NonNull MyViewHolder holder) {
super.onViewAttachedToWindow(holder);
//assuming your 'body' is TextView:
TextView tv = holder.body; //body should have been set in your ViewHolder inner class
Button button = holder.button; //assume button was defined in your ViewHolder inner class
//now you have a reference to your objects!
}
A clean way to do that is making a interface, here i put an example.
class NewsAdapter(private val noticias: List<Noticia>, private val layout: Int, private val activity: Activity, private val listener: OnItemClickListener) : RecyclerView.Adapter<NewsAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(layout, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(noticias[position], listener)
}
override fun getItemCount() = noticias.size
// ViewHolder
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun bind(noticia: Noticia, listener: OnItemClickListener) {
itemView.textViewNewsTitulo.text = noticia.titulo
Glide.with(activity)
.load(noticia.foto_url)
.fitCenter()
.placeholder(R.drawable.placeholder)
.into(itemView.imageViewNews)
itemView.textViewNewsDesc.text = noticia.desc
// bind the click listener with the itemView
itemView.setOnClickListener { listener.onItemClick(noticia, adapterPosition) }
}
}
// Interface onClick define in adapter adapter
interface OnItemClickListener {
fun onItemClick(noticia: Noticia?, posicion: Int)
}
}
For use in your activity you need to implement the interface like this.
class HomeActivity : BaseActivity(), NewsAdapter.OnItemClickListener
The interface force you to implement the methods in the activity.
override fun onItemClick(noticia: Noticia?, posicion: Int) {
// do your logic on click
}
My question is, is the initialization of a new RecyclerView adapter an asynchronous call?
I have an adapter that I am creating:
mRecyclerAdapter = new TestAdapter(mContext, mListImages);
mRecycler.setLayoutManager(mLayoutManager);
mRecycler.setAdapter(mRecyclerAdapter);
After initializing it, I can call .add() directly after these methods without calling .notifyDataSetChanged() and they would still be added to my adapter, and displayed.
mRecyclerAdapter = new TestAdapter(mContext, mListImages);
mRecycler.setLayoutManager(mLayoutManager);
mRecycler.setAdapter(mRecyclerAdapter);
mListImages.add( . . .);
mListImages.add( . . .);
mListImages.add( . . .);
Are RecyclerView adapters automatically initialized on a background thread?
Here is my adapter:
public class SelectBucketAdapter extends RecyclerView.Adapter<SelectBucketAdapter.ViewHolder> {
private static final String TAG = "SelectBucketAdapter";
private Context mContext;
private ArrayList<String> mBucketList;
public SelectBucketAdapter(Context mContext, ArrayList<String> mBucketList,
) {
this.mContext = mContext;
this.mBucketList = mBucketList;
}
#NonNull
#Override
public ViewHolder onCreateViewHolder(#NonNull ViewGroup viewGroup, int viewType) {
View view = LayoutInflater.from(viewGroup.getContext())
.inflate(R.layout.vh_selectbucketmenu_layout, viewGroup, false);
return new ViewHolder(view);
}
#Override
public void onBindViewHolder(#NonNull ViewHolder holder, int i) {
... binding views
}
#Override
public int getItemCount() {
return mBucketList.size();
}
public class ViewHolder extends RecyclerView.ViewHolder{
#BindView(R.id.vh_selectbucketmenu_name)
TextView vhBucketName;
int mPosition;
public ViewHolder(#NonNull View itemView) {
super(itemView);
}
}
}
Are RecyclerView adapters automatically initialized on a background thread?
No, they are not.
Is the initialization of a new RecyclerView adapter an asynchronous call?
No, it is not.
The layout creation and attachment to window is async.
What this means?
Assume we have following code:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
...
val adapter = MyAdapter()
recyclerView.adapter = adapter
adapter.list.add("1")
}
In this case we will see the "1" being displayed on the screen, because at the point when adapter.list.add("1") was executed RecyclerView hasn't yet passed through its measure-layout-draw cycle.
Now let's consider following code:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
...
val adapter = MyAdapter()
recyclerView.adapter = adapter
Handler().postDelayed({ adapter.list.add("AAA") }, 2000)
}
In this case adapter.list.add("AAA") will be executed in roughly 2 seconds. As long as RecyclerView will already be laid out by that time, then mutating the adapter dataset won't make the RecyclerView show the item, because RecyclerView doesn't know if dataset has suffered a change.
Let's consider following case:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
...
val adapter = MyAdapter()
recyclerView.adapter = adapter
recyclerView.doOnPreDraw { adapter.list.add("preDraw") }
recyclerView.doOnLayout { adapter.list.add("layout") }
adapter.list.add("onCreate")
}
In this case still only "onCreate" will be displayed on screen.
To sum up: as soon as RecyclerView has passed its measure step (i.e. View#onMeasure) then mutating adapter won't be reflected unless adapter explicitly notifies RecyclerView.
The term 'intiializing' is quite ambiguous. At what point do you consider the adapter 'intiialized'?. To me, an Adapter can be intiialized with 0 items. So you can't really measure whether the Adapter has been intiialized by looking at the contents of the RecyclerView.
Secondly, you're asking if 'intiializing is an asynchronous call'. The 'initialization' of a RecyclerView Adapter is a whole bunch of calls. What you've observed is that the result of these calls is not always immediately visible - which tells you that at least some of what is happening behind the scenes is asynchronous.
I think what you're trying to ask is 'at what point in the lifecycle of a RecyclerView are you required to notify the Adapter of changes'. And it sounds like the answer is 'once the RecyclerView has reached onMeasure() (based on #azizbekian's answer).
If you want to add items to the Adapter without having to call notifyDataSetChanged(), then I would suggest adding them before calling RecyclerView.setAdapter(). Once you've set the Adapter, any further changes you make to the Adapter's dataset should be followed with a notifyDataSetChanged() call (or preferably, one of the more specific notifyX() calls).