The problem is that when I scroll up my recycler view some items gets disappear
Here is MyAdapter Code of RecyclerView
class MessageAdapter:androidx.recyclerview.widget.ListAdapter<MessageEntity,MessageAdapter.MessageViewHolder>(MessageDiffCallBack()){
inner class MessageViewHolder(val view: View):RecyclerView.ViewHolder(view)
class MessageDiffCallBack:DiffUtil.ItemCallback<MessageEntity>(){
override fun areItemsTheSame(oldItem: MessageEntity, newItem: MessageEntity): Boolean {
return oldItem.timeStamp==newItem.timeStamp
}
override fun areContentsTheSame(oldItem: MessageEntity, newItem: MessageEntity): Boolean {
return oldItem==newItem
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MessageViewHolder {
val view=LayoutInflater.from(parent.context).inflate(R.layout.chat_item,parent,false)
return MessageViewHolder(view)
}
override fun onBindViewHolder(holder: MessageViewHolder, position: Int) {
val messageEntity=getItem(position)
val ourTextView=holder.view.findViewById<TextView>(R.id.ourUserMessage)
val otherTextView=holder.view.findViewById<TextView>(R.id.otherUserMessage)
val ourCardView=holder.view.findViewById<CardView>(R.id.ourCardView)
val otherCardView=holder.view.findViewById<CardView>(R.id.otherCardView)
if(messageEntity.senderId==FirebaseAuth.getInstance().currentUser?.uid){
otherCardView.visibility=View.GONE
ourTextView.text=messageEntity.message
}
else{
ourCardView.visibility=View.GONE
otherTextView.text=messageEntity.message
}
}
}
Can anyone help me in this problem
Related
I was following along the Android Room With a View tutorial.
The tutorial makes use of DiffUtil to compute changes in the list and update the RecyclerView accordingly.
However, when removing or adding items to the RecyclerView, DiffUtil always causes the entire RecyclerView to reload, instead of calling the correct notifyItemRemoved or notifyItemInserted.
My Adapter:
class MarksAdapter(private val context: Context) :
ListAdapter<Mark, MarksAdapter.MarkViewHolder>(MarksComparator()) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MarkViewHolder {
return MarkViewHolder.create(parent)
}
override fun onBindViewHolder(holder: MarkViewHolder, position: Int) {
val mark = getItem(position)
holder.bind(context, mark)
}
class MarkViewHolder(view: View) : RecyclerView.ViewHolder(view) {
private var mark: Mark? = null
fun bind(context: Context, mark: Mark) {
this.mark = mark
// Removed for brevity...
}
companion object {
fun create(parent: ViewGroup): MarkViewHolder {
val view: View =
LayoutInflater.from(parent.context).inflate(R.layout.card_view, parent, false)
return MarkViewHolder(view)
}
}
}
class MarksComparator : DiffUtil.ItemCallback<Mark>() {
override fun areItemsTheSame(oldItem: Mark, newItem: Mark): Boolean {
return oldItem === newItem
}
override fun areContentsTheSame(oldItem: Mark, newItem: Mark): Boolean {
return oldItem == newItem
}
}
}
From the activity/fragment:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.recyclerViewMarks.apply {
marksAdapter = MarksAdapter(context)
adapter = marksAdapter
layoutManager = LinearLayoutManager(this#MarksOverviewFragment.requireContext())
}
marksViewModel.allMarks.observe(viewLifecycleOwner) { marks ->
marks?.let { marksAdapter.submitList(it) }
}
}
After thinking about it some more, the only place where I can actually influence what DiffUtil does, is within the DiffUtil.Callback.
So after changing:
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
return newList[newItemPosition] === oldList[oldItemPosition]
}
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
return newList[newItemPosition] == oldList[oldItemPosition]
}
to:
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
return newList[newItemPosition].Uid == oldList[oldItemPosition].Uid
}
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
return newList[newItemPosition] == oldList[oldItemPosition]
}
(notice the comparison of Uid instead of identity)
...the DiffUtil works as expected.
What I'm not sure however, is why it worked in the tutorial, but not for me.
I am building an app to be able to drag an item from one recycle view to another and I still have to keep the option to re-order inside a single recycler view.
So I have defined a Reorder Callback already
class ReorderHelperCallback(val adapter : ItemTouchHelperAdapter): ItemTouchHelper.Callback() {
override fun getMovementFlags(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder
): Int {
val dragFlags = ItemTouchHelper.UP or ItemTouchHelper.DOWN or
ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT
return makeMovementFlags( dragFlags, 0)//swipeFlags )
}
override fun onMove(
recyclerView: RecyclerView,
source: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder
): Boolean {
adapter.onItemMove(source.getAdapterPosition(),
target.adapterPosition)
return true
}
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
//Not use for Drag N Drop
}
}
also an interface:
interface OnStartDragListener {
fun onStartDrag(viewHolder: RecyclerView.ViewHolder?)
}
and a touchhelper:
interface ItemTouchHelperAdapter {
fun onItemMove(fromPosition: Int, toPosition: Int): Boolean
fun onItemDismiss(position: Int)
}
to allow the reorder to work, I had to update the Recycler view adapter as below:
class Adapter(
private var context: Context?,
val dragStartListener : OnStartDragListener
): RecyclerView.Adapter<Adapter.ViewHolder>(), ItemTouchHelperAdapter {
var arrayItems : ArrayList<Data?> = ArrayList()
fun setData(array : MutableList<Data?>){
array.toCollection(arrayItems)
notifyDataSetChanged()
}
override fun getItemCount(): Int {
return arrayItems.size
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Adapter.ViewHolder {
val binding = DashboardTileLayoutBinding
.inflate(LayoutInflater.from(parent.context), parent, false)
return ViewHolder(binding, dragStartListener)
}
override fun onBindViewHolder(holder: Adapter.ViewHolder, position: Int) {
holder.setData(arrayItems[position])
}
inner class ViewHolder(val binding: LayoutBinding,
val dragStartListener : OnStartDragListener? = null)
: RecyclerView.ViewHolder(binding.root) {
val tileLayout = binding.tileLayout
fun setData(data: Data?) {
....
tileLayout.setOnDragListener { view, dragEvent ->
when(dragEvent.action) {
ACTION_DRAG_STARTED -> {
dragStartListener?.onStartDrag(this)
true
}
else -> false
}
}
}
}
override fun onItemMove(fromPosition: Int, toPosition: Int): Boolean {
Collections.swap(arrayItems, fromPosition, toPosition)
notifyItemMoved(fromPosition, toPosition)
return true
}
override fun onItemDismiss(position: Int) {
TODO("Not yet implemented")
}
}
and the fragment which contain the rv, I have updated the adapter init:
list1adapter?.let { adapter ->
adapter.setData(list)
val callback: ItemTouchHelper.Callback = ReorderHelperCallback(adapter)
mItemTouchHelperSelected = ItemTouchHelper(callback)
mItemTouchHelperSelected?.attachToRecyclerView(selectedLayout)
}
---
override fun onStartDrag(viewHolder: RecyclerView.ViewHolder?) {
viewHolder?.let {
mItemTouchHelperSelected?.startDrag(it)
}
}
But my fragment contain 2 recycler views. list1 is working fine to user drag and drop to re-order the item but now, I would like to also be able to move an item from my rv list1 to the list2 and vice versa
Any idea, how to make it Kotlin ? I tried an sample code, byt I am losing the re-ordering.
Thanks
i am making an app in which i have to insert data into reyclerview , the recyclerview is working fine but the problem is that when i scroll it up adapter reupdate the data , so to solve this issue the code looks fine but still getting this issue ....................................................
BaseClass
abstract class MultiViewModelBaseAdapter<M : Model, VDB : ViewDataBinding>(private var diffCallback: DiffUtil.ItemCallback<M>) : ListAdapter<M ,BaseViewHolder<VDB>>(diffCallback) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder<VDB> {
val inflator = LayoutInflater.from(parent.context)
val binding = createBinding(viewType, inflator, parent)
return BaseViewHolder(binding)
}
abstract fun createBinding(viewType: Int, inflater: LayoutInflater, parent: ViewGroup) : VDB
override fun onBindViewHolder(holder: BaseViewHolder<VDB>, position: Int) {
bind(holder.mBinding, getItem(position), position)
holder.mBinding.executePendingBindings()
}
abstract fun bind(binding: VDB, item: M, position: Int)
abstract fun onDataChanged(values: Boolean)}
}
Adapter
class LanguageAdapter(
private val context: Context,
private val mViewModel: LanguageListViewModel,
private val onClickListener: OnItemClickListener<String>
) : MultiViewModelBaseAdapter<LanguageSupportModel, ViewDataBinding>(diffCallback) {
companion object {
private val ADS = 1
private val LANGUAGES = 2
val diffCallback = object : DiffUtil.ItemCallback<LanguageSupportModel>() {
override fun areItemsTheSame(
oldItem: LanguageSupportModel,
newItem: LanguageSupportModel
): Boolean = oldItem.dataId == newItem.dataId
/**
* Note that in kotlin, == checking on data classes compares all contents, but in Java,
* typically you'll implement Object#equals, and use it to compare object contents.
*/
override fun areContentsTheSame(
oldItem: LanguageSupportModel,
newItem: LanguageSupportModel
): Boolean = oldItem == newItem
}
}
override fun getItemId(position: Int): Long {
return position.toLong()
}
override fun getItemViewType(position: Int): Int {
return position
}
override fun createBinding(viewType: Int, inflater: LayoutInflater, parent: ViewGroup): ViewDataBinding {
return DataBindingUtil.inflate(inflater, R.layout.language_view, parent, false)
}
override fun bind(binding: ViewDataBinding, item: LanguageSupportModel, position: Int) {
binding as LanguageViewDataBinding
binding.apply {
language = item
//click
}
}
override fun onDataChanged(values: Boolean) {}
}
Fragment
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val languageAdapter = LanguageAdapter(requireContext(), mViewModel, this ,lifecycleScope)
languageAdapter.submitList(LanguageArray.arrayValues())
reyclerview.layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
adapter = languageAdapter
}
If You will Use Paging concept in Scrolling then it will Solve.
In the JAVA or KOTLIN We can implement.. like..
import androidx.viewpager.widget.PagerAdapter;
Then extends it into Adapter Class.
public void addList(List<ClsList> list) {
this.mResources = list;
notifyDataSetChanged(); // also this main line
}
Problem Solve.
☻♥ Have Fun..
I have found an issue when dragging an item that doesn't trigger a move on a GridLayoutManager with 2 columns.
When dragging one item from the first column to the top of the list (which scrolls the list upwards) and then back to its original position, the recyclerView creates an empty space next to the original position, pushing the item that was next to the dragged item to the next row.
I have coded the simplest app to show you what I mean. On the first screenshot I start dragging one item towards the top of the list. On the second screenshot I reach the top of the screen. Then I move it back to its position and you can already see how the items have not been laid down properly.
Has anyone encountered this as well? I have not found any solution to this issue.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val myAdapter = MyAdapter()
with(findViewById<RecyclerView>(R.id.recyclerView)) {
adapter = myAdapter
layoutManager = GridLayoutManager(this#MainActivity, 2)
ItemTouchHelper(object : ItemTouchHelper.Callback() {
override fun getMovementFlags(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder): Int {
return makeFlag(ItemTouchHelper.ACTION_STATE_DRAG, DOWN or UP or START or END)
}
override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder): Boolean {
return false
}
override fun canDropOver(recyclerView: RecyclerView, current: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder): Boolean {
return false
}
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
TODO("Not yet implemented")
}
}).attachToRecyclerView(this)
}
myAdapter.submitList(data)
}
data class ListItem(val id: String, val name: String)
class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
}
class MyAdapter : ListAdapter<ListItem, MyViewHolder>(MyAdapterDiffCallback()) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.list_item, parent, false)
return MyViewHolder(view)
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
}
class MyAdapterDiffCallback : DiffUtil.ItemCallback<ListItem>() {
override fun areItemsTheSame(oldItem: ListItem, newItem: ListItem) = oldItem.id == newItem.id
override fun areContentsTheSame(oldItem: ListItem, newItem: ListItem) = oldItem == newItem
}
}
So i have this strange behaviour where my RecyclerView only Displays data when i start the App and then restart the activity via Android Studio. Or make a change in XML, then undo it and restart.
The then displayed data is correct and also updates when i add things, but it is not visible after starting the app.
Can anyone help me out? I am really clueless why that would be.
Fragment:
#AndroidEntryPoint
class HistoryFragment : Fragment(R.layout.fragment_history) {
private val viewModel: PurchaseViewmodel by viewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val binding = FragmentHistoryBinding.bind(view)
val exampleAdapter = ExampleAdapter()
binding.apply{
recyclerView.apply{
layoutManager = LinearLayoutManager(requireContext())
adapter = exampleAdapter
setHasFixedSize(true)
}
ItemTouchHelper(object : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT) {
override fun onMove(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder
): Boolean {
return false /// We dont need any interaction with Drag&Drop we only want swipe left&right
}
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
val receipt = exampleAdapter.currentList[viewHolder.adapterPosition]
viewModel.onSwipe(receipt)
}
}).attachToRecyclerView(recyclerView)
}
setFragmentResultListener("add_receipt_request"){_,bundle ->
val result = bundle.getInt("add_receipt_request")
viewModel.onAddResult(result)
}
viewModel.receipts.observe(viewLifecycleOwner){ /// New Items get passed to the List
exampleAdapter.submitList(it)
}
viewLifecycleOwner.lifecycleScope.launchWhenStarted { //as soon as we close our app the events will be suspended, but not deleted and will remain after restart
viewModel.addTaskEvent.collect { event->
when(event){
is PurchaseViewmodel.TasksEvent.ShowUndoDelete -> {
Snackbar.make(requireView(),"Tasks deleted", Snackbar.LENGTH_LONG)
.setAction("UNDO"){
viewModel.unDoDeleteClick(event.receipts)
}.show()
}
}
}
}
}
}
Adapter:
class ExampleAdapter : ListAdapter<Receipts,ExampleAdapter.ExampleViewHolder>(DiffCallback()) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ExampleViewHolder {
val binding = ReceiptsBinding.inflate(LayoutInflater.from(parent.context),parent,false)
return ExampleViewHolder(binding)
}
override fun onBindViewHolder(holder: ExampleViewHolder, position: Int) {
val currentItem = getItem(position)
holder.bind(currentItem)
}
override fun getItemCount(): Int {
return super.getItemCount()
}
class ExampleViewHolder(private val binding: ReceiptsBinding) : RecyclerView.ViewHolder(binding.root){ //Examples One Row in our list
fun bind (receipts: Receipts) {
binding.apply {
storeHistory.text = receipts.store
amountHistory.text = receipts.total
dateHistory.text = receipts.date
}
}
}
class DiffCallback : DiffUtil.ItemCallback<Receipts>() {
override fun areItemsTheSame(oldItem: Receipts, newItem: Receipts) =
oldItem.id == newItem.id
override fun areContentsTheSame(oldItem: Receipts, newItem: Receipts) =
oldItem == newItem
}
}
You should compare corresponding fields of old/new Receipts in areContentsTheSame() instead of comparing the entire old/new objects
override fun areContentsTheSame(oldItem: Receipts, newItem: Receipts) =
oldItem.store == newItem.store
&& oldItem.total == newItem.total
&& oldItem.date == newItem.date