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
Related
First of all, I am Spanish so my english is not good.
I have an app with Kotlin and room, and it has a Recyclerview.
I have 3 tables: coaster, user and favorite.
The user can add coasters to favorite, and this is done succesfully.
The problem that I have is that when the user clicks on the button to add or delete from favorites, the recyclerview resets, it displays again. So it scrolls to the top of the Screen, and also some odd spaces appears after the element.
I also have a function to search, and it happens the same: spaces appears after each element when I am searching.
I have tried everything: notifyItemChanged,
notifyDataSetChanged... it doesnt work! I also tried removing the observer once from the recyclerview...
My main activity:
class CoasterFragment : Fragment() {
lateinit var coasterListener: CoasterListener
lateinit var usuarioCoaster: List\<UsuarioCoaster\>
private lateinit var searchView: SearchView
private lateinit var cAdapter: CoasterRecyclerViewAdapter
private var \_binding: FragmentCoasterBinding? = null
private val binding get() = \_binding!!
private val viewModel: CoastersViewModel by viewModels()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentCoasterBinding.inflate(inflater, container, false)
val root: View = binding.root
/* val livedata = viewModel.coasters()
livedata.observe(viewLifecycleOwner,object: Observer <List<CoasterFavorito>> {
override fun onChanged(it: List<CoasterFavorito>) {
createRecyclerView(it)
livedata.removeObserver(this)
}
})*/
viewModel.coasters().observe(viewLifecycleOwner){createRecyclerView(it)}
coasterListener = CoasterListenerImpl(requireContext(), viewModel)
searchView = binding.search
searchView.clearFocus()
searchView.setOnQueryTextListener(object: SearchView.OnQueryTextListener{
override fun onQueryTextSubmit(query: String?): Boolean {
if(query != null){
searchDatabase(query)
}
return true
}
override fun onQueryTextChange(query: String?): Boolean {
if(query != null){
searchDatabase(query)
}
return true
}
})
return root
}
fun createRecyclerView(coasters: List<CoasterFavorito>) {
cAdapter =
CoasterRecyclerViewAdapter(
coasters as MutableList<CoasterFavorito>,
coasterListener,
requireContext()
)
val recyclerView = binding.recyclerCoaster
recyclerView.apply {
layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, false)
adapter = cAdapter
addItemDecoration(DividerItemDecoration(context, RecyclerView.VERTICAL))
cAdapter.notifyDataSetChanged()
}
}
fun searchDatabase(query: String) {
val searchQuery = "%$query%"
viewModel.searchDatabase(searchQuery).observe(viewLifecycleOwner) { createRecyclerView(it)
}
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
my adapter:
class CoasterRecyclerViewAdapter(val coasters: List<CoasterFavorito>, val listener: CoasterListener,
val context: Context, ) : RecyclerView.Adapter<CoasterRecyclerViewAdapter.ViewHolder>(){
class ViewHolder private constructor(val binding: CoasterItemBinding, private val listener: CoasterListener,
private val context: Context): RecyclerView.ViewHolder(binding.root){
fun relleno(data: CoasterFavorito){
binding.nombre.text = data.coaster.nombre
binding.parque.text = data.coaster.parque
binding.ciudad.text = data.coaster.ciudad
binding.provincia.text = data.coaster.provincia
binding.comunidad.text = data.coaster.comunidadAutonoma
Glide
.with(context)
.load(data.coaster.imagen)
.centerCrop()
.into(binding.imagen)
binding.check.isChecked = data.favorito
binding.check.setOnClickListener{
if (data.favorito) {
listener.delFavorito(data.coaster.id)
binding.check.isChecked = false
} else {
listener.addFavorito(data.coaster.id)
binding.check.isChecked = true
}
}
}
companion object{
fun crearViewHolder(parent: ViewGroup, listener: CoasterListener, adapter: CoasterRecyclerViewAdapter, context: Context):ViewHolder{
val layoutInflater = LayoutInflater.from(parent.context)
val binding = CoasterItemBinding.inflate(layoutInflater, parent, false)
return ViewHolder(binding, listener, context )
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder.crearViewHolder(parent, listener, this, context)
override fun onBindViewHolder(holder: ViewHolder, position: Int) = holder.relleno(coasters[position])
override fun getItemCount() = coasters.size
}
interface CoasterListener {
fun addFavorito(id: Long)
fun delFavorito(id: Long)
}
I have tried everything: notifyItemChanged,
notifyDataSetChanged... it doesnt work! I also tried removing the observer once from the recyclerview...
Your createRecyclerView function should be invoked only once in a whole lifecycle of the Fragment. You should not create any new RecyclerView.Adapter, or set a LayoutManager to the RecyclerView every time your data set changes.
Therefore the Observer used in viewModel.coasters.observe() should only submit a new List to the existing Adapter and call .notifyDataSetChanged(), or other notifying functions.
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.
I've been trying to delete an item from my list so that it updates without the removed item, but the list seems to redraw itself and keeps displaying all the original items as before. For a short bit of time it's possible to see the item as if it's being removed, however, due to this redrawing everything gets back to what it was before the removal.
I've tried several combinations of the following methods but none of them seem to work in this case.
adapter.notifyItemRangeChanged(position, adapter.itemCount)
adapter.notifyItemRemoved(position)
adapter.notifyItemChanged(position)
adapter.notifyDataSetChanged()
These are my files. Please notice I'm using the Groupie library as a replacement for the default RecyclerView.
class RecyclerProductItem(
private val activity: MainActivity,
private val product: Product,
private val onItemClickListener: OnItemClickListener?
) : Item<GroupieViewHolder>() {
override fun bind(viewHolder: GroupieViewHolder, position: Int) {
viewHolder.apply {
with(viewHolder.itemView) {
ivTrash.setOnClickListener {
if (onItemClickListener != null) {
Toast.makeText(context, "delete method to be added here", Toast.LENGTH_SHORT).show()
onItemClickListener.onClick(viewHolder.adapterPosition)
// deleteProduct(product.id)
}
}
}
}
}
interface OnItemClickListener {
fun onClick(position: Int) //pass your object types.
}
override fun getLayout() = R.layout.recyclerview_item_row
}
And here my fragment:
class ProductsListFragment : Fragment() {
private lateinit var adapter: GroupAdapter<GroupieViewHolder>
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_products_list, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val linearLayoutManager = LinearLayoutManager(activity)
recyclerView.layoutManager = linearLayoutManager
adapter = GroupAdapter()
recyclerView.adapter = adapter
loadProducts()
}
/**
* API calls
*/
private fun loadProducts() {
GetProductsAPI.postData(object : GetProductsAPI.ThisCallback,
RecyclerProductItem.OnItemClickListener {
override fun onSuccess(productList: List<JsonObject>) {
Log.i(LOG_TAG, "successful network call")
for (jo in productList) {
val gson = GsonBuilder().setPrettyPrinting().create()
val product: Product =
gson.fromJson(jo, Product::class.java)
adapter.add(
RecyclerProductItem(
activity as MainActivity,
Product(
product.id,
product.title,
product.description,
product.price
), this
)
)
}
}
override fun onClick(position: Int) {
Log.i(LOG_TAG, position.toString())
adapter.notifyItemRangeChanged(position,
adapter.itemCount)
adapter.notifyItemRemoved(position)
}
})
}
}
Many thanks.
Simple sample
class GroupAdapter(private val items: MutableList<Any>) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
fun removeByPosition(position: Int) {
items.removeAt(position)
notifyItemRemoved(position)
}
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
}
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()