onCreateViewHolder not called in PagedListAdapter - android

I have PagedListAdapater:
class TrackedActivityAdapter constructor(diffUtilCallback: DiffUtil.ItemCallback<TrackedActivity>) :
PagedListAdapter<TrackedActivity, TrackedActivityAdapter.TrackedActivityHolder>(diffUtilCallback) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TrackedActivityHolder {
val inflater = LayoutInflater.from(parent.context)
val view = DataBindingUtil.inflate<ActivityItemBinding>(inflater, R.layout.activity_item, parent, false)
return TrackedActivityHolder(view)
}
override fun onBindViewHolder(holder: TrackedActivityHolder, position: Int) {
getItem(position)?.let { holder.bind(it) }
}
class TrackedActivityHolder(var binding: ActivityItemBinding) : RecyclerView.ViewHolder(binding.root) {
fun bind(trackedActivity: TrackedActivity) {
binding.activity = trackedActivity
binding.executePendingBindings()
}
}
}
And fragment in which this adapter created:
class HistoryFragment : DaggerFragment() {
#Inject
lateinit var viewModelFactory: ViewModelProvider.Factory
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val binding = DataBindingUtil.inflate<HistoryFragmentViewBinding>(
inflater,
R.layout.history_fragment_view,
container,
false
)
val historyViewModel = ViewModelProviders.of(this, viewModelFactory).get(HistoryViewModel::class.java)
val adapter = TrackedActivityAdapter(TrackedActivity.DIFF_CALLBACK)
binding.trackedActivityRv.adapter = adapter
binding.trackedActivityRv.layoutManager = LinearLayoutManager(binding.root.context)
historyViewModel.getTrackedActivities().observe(this,
Observer<PagedList<TrackedActivity>> { t ->
adapter.submitList(t)
})
binding.executePendingBindings()
return binding.root
}
}
After the adapter.submitList (t) method has been called. Nothing happens, and the onCreateViewHolder and onBindViewHolder methods are not called, please tell me there could be a reason, I just can't understand (

Have you tried using HistoryFragmentViewBinding.inflate(inflater) instead of DataBindingUtil.inflate()?
I had the same problem and got stuck for days. Turns out I just returned the wrong view at the end. It wasn't like yours, but probably worth a try.

I had the same issue. It is only one line different from you, and the code is exactly the same. In my case
recyclerview.setHasFixedSize(true)
Was added. When I removed this, onCreateViewHolder() was called.
So, I don't know exactly which part of your code is the problem, but try to handle this part as well.
val adapter = TrackedActivityAdapter(TrackedActivity.DIFF_CALLBACK)
binding.trackedActivityRv.adapter = adapter
binding.trackedActivityRv.layoutManager = LinearLayoutManager(binding.root.context)
binding.executePendingBindings()

Related

Recycler View not showing results in fragment

I'm trying to implement recycler view inside some of my fragments, and i tried to do so on the first one. No issues are displayed in the IDE on compilation time, but on runtime I get this message on the console: E/RecyclerView: No layout manager attached; skipping layout. Also, data is not showing in my application.
Here is my Fragment:
var sandwiches = listOf<Sandwich>()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val binding = DataBindingUtil.inflate<FragmentSandwichesBinding>(
inflater,
R.layout.fragment_sandwiches, container, false
)
val application = requireNotNull(this.activity).application
val dataSource = NbaCafeDB.getInstance(application).sandwichDao
val viewModelFactory = SandwichViewModelFactory(dataSource, application)
val sandwichViewModel =
ViewModelProvider(this, viewModelFactory).get(SandwichViewModel::class.java)
sandwiches = sandwichViewModel.getAll()
val adapter = SandwichAdapter(sandwiches)
binding.sandwichRecycler.adapter = adapter
binding.setLifecycleOwner(this)
return binding.root
}
}
And here is my Adapter:
class SandwichAdapter (val sandwich: List<Sandwich>) : RecyclerView.Adapter<SandwichAdapter.SandwichHolder>() {
override fun getItemCount() = sandwich.size
class SandwichHolder(val view: View) : RecyclerView.ViewHolder(view) {
fun bind(sandwich: Sandwich) {
view.findViewById<TextView>(R.id.sandwichNom).text = sandwich.nomSandwich
view.findViewById<TextView>(R.id.sandwichDesc).text = sandwich.descSandwich
view.findViewById<TextView>(R.id.sandwichPreu).text = (sandwich.preuSandwich.toString()+" €")
}
companion object {
fun from(parent: ViewGroup): SandwichHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val view = layoutInflater
.inflate(R.layout.sandwich_cell_layout, parent, false)
return SandwichHolder(view)
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SandwichHolder {
return SandwichHolder.from(parent)
}
override fun onBindViewHolder(holder: SandwichHolder, position: Int) {
holder.bind(sandwich[position])
}
}
Also, I'm retrieving data from a room database and using viewModel and viewModelFactory, in case that changes anything.
Thanks!
You don't need to call RecyclerView.setLayoutManager(layoutManager).
Just add app:layoutManager to RecyclerView in your xml.
The default orientation of LinearLayoutManager is VERTICAL, but if you want to change orientation as HORIZONTAL, you can just add android:orientation="horizontal".
<androidx.recyclerview.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical|horizontal"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />

recyclerview not visible at run time

I implemented a simple recyclerview using databinding using kotlin language.
In xml, recyclerview seems to be the default,
but when I run it, it doesn't show up as default.
Other buttons and views included in the fragment appear normally when executed, but only the recyclerview is not visible. Which part is wrong?
class WorkFragment : Fragment() {
private var _binding: FragmentWorkBinding? = null
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentWorkBinding.inflate(inflater, container, false)
val view = binding.root
return view
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val data = mutableListOf<WorkList>()
val adapter = WorkAdapter()
adapter.data = data
binding.recycler1.adapter = adapter
binding.recycler1.layoutManager = LinearLayoutManager(requireContext())
}
}
class WorkAdapter : RecyclerView.Adapter<WorkAdapter.ViewHolder>() {
var data = mutableListOf<WorkList>()
class ViewHolder(val binding: RecyclerviewWorkItemBinding) : RecyclerView.ViewHolder(binding.root) {
fun bind(workList: WorkList) {
binding.tvStarttime.text = workList.Starttime
binding.tvAdmin.text = workList.Admin
binding.tvPart.text = workList.Part
binding.tvStoptime.text = workList.Stoptime
binding.tvWorkers.text = workList.Workers.toString()
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): WorkAdapter.ViewHolder {
val binding = RecyclerviewWorkItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return ViewHolder(binding)
}
override fun onBindViewHolder(holder: WorkAdapter.ViewHolder, position: Int) {
holder.bind(data[position])
}
override fun getItemCount(): Int {
return data.size
}
// fun replaceList(newList: MutableList<WorkList>) {
// data = newList.toMutableList()
// notifyDataSetChanged()
// }
}
Is there something wrong with my code?
For reference, I just used a fragment, not an activity.
its typically for the RecyclerView to behave like its hidden but actually its not. the reason is you are passing an empty list so the recycler will not be extended or show because there is no items in the list.

How to provide an implementation of the click listener interface for the recycler adapter?

Suppose we have a recycler view in a fragment, and we need to add a click handler to the adapter. Which one of the following approaches are better? Please tell me the pros and cons each one:
Approach One: implementing OnItemClickListener in the fragment:
In this approach we implement the OnItemClickListener in the fragment class MyFragment : Fragment() , MyAdapter.OnItemClickListener and pass it to the adapter using this keyword:
class MyFragment : Fragment() , MyAdapter.OnItemClickListener {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
myRecyclerView.adapter = MyAdapter(it , this) // here we pass the fragment
return view
}
override fun onCLick(item: MyItem) {
Log.d("test","item name = " + item.Name)
}
}
class MyAdapter(val data: List<MyItem> , val onItemClickListener: OnItemClickListener) :
RecyclerView.Adapter<MyFragment.MyViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.support_simple_spinner_dropdown_item, parent, false)
return MyViewHolder(view)
}
override fun getItemCount() = data.size
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
holder.itemView.setOnClickListener{
onItemClickListener.onCLick(data[position])
}
with(holder) {
textView1?.let {
it.text = data[position].Name
}
}
}
interface OnItemClickListener{
fun onCLick(item: MyItem)
}
}
Approach Two: implementing OnItemClickListener using a anonymous object:
In this approach we use object: syntax to create an anonymous object, like inline interface implementation in java:
class MyFragment : Fragment(){
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
myRecyclerView.adapter = MyAdapter(it ,object : MyAdapter.OnItemClickListener{
override fun onCLick(item: MyItem) {
Log.d("test","name = " + item.Name)
}
})
return view
}
class MyAdapter(val data: List<MyItem> , val onItemClickListener: OnItemClickListener) :
RecyclerView.Adapter<MyViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.support_simple_spinner_dropdown_item, parent, false)
return MyViewHolder(view)
}
override fun getItemCount() = data.size
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
holder.itemView.setOnClickListener{
onItemClickListener.onCLick(data[position])
}
with(holder) {
textView1?.let {
it.text = data[position].Name
}
}
}
interface OnItemClickListener{
fun onCLick(item: MyItem)
}
}
Which one is more modern?
Which one gives a better performance? (If there is any difference)
Why in most tutorials and video lessons they use the first approach, while the second one needs fewer lines of codes?
Simple answer: it doesn't matter. Approach 2 simply allocates one extra object on the heap which is not a big deal. I would prefer 2 over 1 because 1 exposes that MyFragment implements MyAdapter.OnItemClickListener: that should remain an implementation detail, not a part of your fragment's contract.
However, there is something in your code that can affect performance. Your setOnClickListener() call allocates new listener whenever onBindViewHolder() is called, which is pretty often if you scroll fast enough. It adds to the work of your garbage collector.
You can always get ViewHolder's position in adapter with getBindingAdapterPosition() (or its deprecated counterpart getAdapterPosition() if you're using an older version of RecyclerView). As you can see from the docs, in marginal cases it may not be consistent with the position you use and is a more correct approach to handling user actions.
With that said, it is sufficient to set your listener only once, in onCreateViewHolder():
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): MyViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.support_simple_spinner_dropdown_item, parent, false)
val holder = MyViewHolder(view)
holder.itemView.setOnClickListener {
holder
.bindingAdapterPosition
.takeUnless { it == RecyclerView.NO_POSITION }
?.let { position ->
onItemClickListener.onClick(data[position])
}
}
return holder
}
Here you can use Callback, it is the recommended choice of Google's Android team. You implement that as:
class MyFragment : Fragment(){
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
myRecyclerView.adapter = MyAdapter(it) { item ->
// You can use your item here.
}
return view
}
class MyAdapter(val data: List<MyItem> ,
private val callback: (MyItem) -> Unit) :
RecyclerView.Adapter<MyViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.support_simple_spinner_dropdown_item, parent, false)
return MyViewHolder(view)
}
override fun getItemCount() = data.size
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
holder.itemView.setOnClickListener{
callback.invoke(data[position])
}
with(holder) {
textView1?.let {
it.text = data[position].Name
}
}
}
}

notifyDataSetChanged not updating RecyclerView although ArrayList reference is not lost

I appreciate that this question has been asked MANY times in SO but all the solutions refer to a missing reference in my arraylist which currently I (believe) am preserving.
I have an adapter as follows:
/**
* [RecyclerView.Adapter] that can display a [Note].
*/
class MyNoteRecyclerViewAdapter(
private var notes: ArrayList<Note>,
private val onNoteSelectedListener: MainActivityContract.OnNoteSelectedListener
) : RecyclerView.Adapter<MyNoteRecyclerViewAdapter.ViewHolder>() {
private val mNotes: ArrayList<Note> = notes
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.fragment_notes_list, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = mNotes[position]
holder.bind(item, onNoteSelectedListener)
}
override fun getItemCount(): Int = mNotes.size
fun setItems(notes: ArrayList<Note>) {
mNotes.clear()
mNotes.addAll(notes)
notifyDataSetChanged()
}
inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
private val idView: TextView = view.findViewById(R.id.item_number)
private val contentView: TextView = view.findViewById(R.id.content)
fun bind(item: Note, onNoteSelectedListener: MainActivityContract.OnNoteSelectedListener) {
idView.text = item.id
contentView.text = item.title
itemView.setOnClickListener(View.OnClickListener { onNoteSelectedListener.onNoteSelected(item) })
}
override fun toString(): String {
return super.toString() + " '" + contentView.text + "'"
}
}
}
as you can see above, the function setItems() is setting clearing all notes in the list and readding them followed by notifyDataSetChanged()
The view that calls this is here:
class NotesListFragment() : BaseFragment(), MainActivityContract.OnNoteSelectedListener {
var mAdapter: MyNoteRecyclerViewAdapter? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.fragment_notes_list_list, container, false)
val lvNotes = view.findViewById<RecyclerView>(R.id.lv_notes);
mAdapter = MyNoteRecyclerViewAdapter(ArrayList<Note>(), this)
lvNotes.adapter = mAdapter
return view
}
override fun onNoteSelected(note: Note) {
(activity as MainActivity).onNoteSelected(Note());
}
fun onNotesLoaded(notes: ArrayList<Note>) {
mAdapter?.setItems(notes)
view?.findViewById<RecyclerView>(R.id.lv_notes)?.adapter?.notifyDataSetChanged()
}
}
The function onNotesLoaded is called by an external class that fetches notes. As you can see, I am setting items using mAdapter.setItems() which should notify the list that data has changed but without luck. I tried to also add the second line to see if it's something I'm missing but again, no luck.
I'm not sure if this is a kotlin issue on my part when assigning the list variable but any assistance would be greatly appreciated.
It seem you forgot add LayoutManager for RecyclerView
mAdapter = MyNoteRecyclerViewAdapter(ArrayList<Note>(), this)
lvNotes.adapter = mAdapter
lvNotes.layoutManager = LinearLayoutManager(context)
It seems like the issue lied in other areas of my codebase so I will close this question. Thanks for anyone who tried to help

how to fix No Adapter Attatch skipping layout

i have problem when try to display recyvlerview using kotlin , warn no adapter attach skipping layout and nothing happen in my app, ive tried many way but nothing solve it
how could i do ? please review my code, i will very thankfull to anyone can help
i have problem when try to display recyvlerview using kotlin , warn no adapter attach skipping layout and nothing happen in my app, ive tried many way but nothing solve it
how could i do ? please review my code, i will very thankfull to anyone can help
class ArticleFragment : Fragment() {
private lateinit var mPeopleRVAdapter: FirebaseRecyclerAdapter<News, NewsViewHolder>
//function oncreate
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.activity_news, container, false)
}
//viewholder
class NewsViewHolder internal constructor(var mView: View) : RecyclerView.ViewHolder(mView) {
fun setTitle(title: String?) {
val post_title = mView.findViewById<View>(R.id.post_title) as TextView
post_title.text = title
}
fun setDesc(desc: String?) {
val post_desc = mView.findViewById<View>(R.id.post_desc) as TextView
post_desc.text = desc
}
fun setImage(ctx: Context?, image: String?) {
val post_image = mView.findViewById<View>(R.id.post_image) as ImageView
Picasso.with(ctx).load(image).into(post_image)
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
//"News" here will reflect what you have called your database in Firebase.
//"News" here will reflect what you have called your database in Firebase.
val mDatabase = FirebaseDatabase.getInstance().reference.child("news")
mDatabase.keepSynced(true)
val mPeopleRV = view.findViewById<View>(R.id.myRecycleView) as RecyclerView
val personsRef =
FirebaseDatabase.getInstance().reference.child("news")
val personsQuery = personsRef.orderByKey()
val personsOptions: FirebaseRecyclerOptions<News> = FirebaseRecyclerOptions.Builder<News>().setQuery(
personsQuery, News::class.java).build()
mPeopleRVAdapter = object : FirebaseRecyclerAdapter<News, NewsViewHolder>(personsOptions) {
override fun onBindViewHolder(holder: NewsViewHolder, position: Int, model: News) {
holder.setTitle(model.title)
holder.setDesc(model.desc)
holder.setImage(activity ,model.image)
holder.mView.setOnClickListener {
val url: String? = model.url
val intent = Intent(activity, NewsWebView::class.java)
intent.putExtra("id", url)
startActivity(intent)
}
mPeopleRV.hasFixedSize()
mPeopleRV.layoutManager = LinearLayoutManager(context)
mPeopleRV.apply {
mPeopleRV.adapter = mPeopleRVAdapter
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NewsViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.news_row, parent, false)
return NewsViewHolder(view)
}
}
}
override fun onStart() {
super.onStart()
mPeopleRVAdapter.startListening()
}
override fun onStop() {
super.onStop()
this.mPeopleRVAdapter.stopListening()
}
}
Move this code out of adapter:
mPeopleRV.hasFixedSize()
mPeopleRV.layoutManager = LinearLayoutManager(context)
mPeopleRV.apply {
mPeopleRV.adapter = mPeopleRVAdapter
}

Categories

Resources