How to correctly delete item from recycler view and refresh the adapter when using PagingDataAdapter

75 views Asked by At

I want to delete an Item from the recycler view that uses PagingDataAdapter. The problem is after I removed the data from the Room database, I had to refresh the adapter. After this action, the recycler view behaves strangely and my list starts shuffling and sometimes shows false entities. I did all the answers but nothing worked...

This is the DataSource:

class SentMessagesPagingSource @Inject constructor(private val repository: SentMessagesRepository): PagingSource<Int, MessageEntity>(){
    override fun getRefreshKey(state: PagingState<Int, MessageEntity>): Int? {
        return null
    }

    override suspend fun load(params: LoadParams<Int>): LoadResult<Int, MessageEntity> {
        return try {
            //THE OFFSET IN DATABASE WILL BE STARTED FROM 0
            val currentPage = params.key ?: 0
            val limit = MAX_DATA_REQUESTED_FROM_DATABASE
            val offset = currentPage * MAX_DATA_REQUESTED_FROM_DATABASE
            Log.i("LOG:", "currentPage = $currentPage")
            val response = repository.sentMessagesPaging(limit, offset)
            val data = response ?: emptyList<MessageEntity>()
            val responseData = mutableListOf<MessageEntity>()
            Log.i("LOG:", "SentMessagesPagingSource -->$response")
            responseData.addAll(data)

            LoadResult.Page(
                data = responseData,
                prevKey = if (currentPage == 0) null else -1 ,
                nextKey = if (responseData.isEmpty()) null else currentPage.plus(1)
            )
        }catch (e:Exception){
            LoadResult.Error(e)
        }
    }
}

This is the ViewModel:

@HiltViewModel
class SentMessageViewModel @Inject constructor(private val repository: SentMessagesRepository): ViewModel() {

    //SAVE RECYCLER VIEW STATE
    var recyclerviewState: Parcelable? = null

    private var _messagesLiveData: MutableLiveData<DataStatus<List<MessageEntity>>> = MutableLiveData()
    val messagesLiveData: LiveData<DataStatus<List<MessageEntity>>> = _messagesLiveData

    private var _isSearch: MutableLiveData<Boolean> = MutableLiveData()
    val isSearch = _isSearch

    private var _isLoading: MutableLiveData<Boolean> = MutableLiveData()
    val isLoading: LiveData<Boolean> = _isLoading

    //GET ALL SENT MESSAGES FROM THE DATABASE
    fun getAllMessages() = viewModelScope.launch(Dispatchers.IO) {
        Log.e("LOG:","SentMessageViewModel-getAllMessages")
        repository.allSentMessages().collect{
            _messagesLiveData.postValue(DataStatus.success(it, it.isEmpty()))
        }
    }

    //GET SENT MESSAGES USING PAGE INITIATION FROM THE DATABASE
    fun getSentMessages(page: Int) = viewModelScope.launch(Dispatchers.IO) {
        _isLoading.postValue(true)
        Log.e("LOG:","SentMessageViewModel-getSentMessages")
        val limit = MAX_DATA_REQUESTED_FROM_DATABASE
        val offset = page * MAX_DATA_REQUESTED_FROM_DATABASE
        repository.sentMessages(limit, offset).collect{
            _messagesLiveData.postValue(DataStatus.success(it, it.isEmpty()))
            _isLoading.postValue(false)
        }
    }

    /** REMOVE ALL SENT MESSAGES*/
    fun deleteAllSentMessages() = viewModelScope.launch(Dispatchers.IO) {
        Log.e("LOG:","SentMessageViewModel-deleteAllSentMessages")
        repository.removeAllSentMessages(0)
    }

    //UPDATE SENT MESSAGE STATUS2
    fun updateSentMessageStatus2(entity: MessageEntity) = viewModelScope.launch(Dispatchers.IO) {
        Log.e("LOG:","SentMessageViewModel-deleteSentMessage")
        repository.updateMessageStatus2ById(0, entity.id)
    }

    //SEARCH SENT MESSAGES FROM THE DATABASE AND GETTING THE SEARCH RESULT
    fun getSearchSentMessages(search: String, status: String) = viewModelScope.launch(Dispatchers.IO) {
        Log.e("LOG:","SentMessageViewModel-getSearchSentMessages")
        _isSearch.postValue(true)
        repository.searchSentMessage(search, status).collect{
            Log.e("LOG:","$it")
            _messagesLiveData.postValue(DataStatus.success(it, it.isEmpty()))
            _isSearch.postValue(false)
        }
    }


    //SENT MESSAGES LIST USING PAGING 3
    val sentMessageList = Pager(PagingConfig(pageSize = MAX_DATA_REQUESTED_FROM_DATABASE,
        enablePlaceholders = false,
        initialLoadSize = MAX_DATA_REQUESTED_FROM_DATABASE)){
        SentMessagesPagingSource(repository)
    }.flow.map {
        val messagesMap = mutableSetOf<Int> ()
        it.filter { message ->
            if (messagesMap.contains (message.id)) {
                false
            } else {
                messagesMap.add(message.id)
            }
        }
    }.cachedIn (viewModelScope)

    private var _pagedMessagesLiveData: MutableLiveData<PagingData<MessageEntity>> = MutableLiveData()
    val pagedMessagesLiveData: LiveData<PagingData<MessageEntity>> = _pagedMessagesLiveData

    fun loadPagedData() = viewModelScope.launch(Dispatchers.IO) {
        sentMessageList.collectLatest {
            Log.i("LOG","LOAD DATA")
            _pagedMessagesLiveData.postValue(it)
        }
    }
}

This is the Adapter:

class SentMessageAdapterPaging @Inject constructor() : PagingDataAdapter<MessageEntity, SentMessageAdapterPaging.ViewHolder>(differCallback) {
    private lateinit var binding: ListSentMessagesNew1Binding
    private lateinit var context: Context
    private var addedIdList: MutableList<Int> = mutableListOf()

    override fun onCreateViewHolder(
        parent: ViewGroup,
        viewType: Int
    ): SentMessageAdapterPaging.ViewHolder {
        binding =
            ListSentMessagesNew1Binding.inflate(LayoutInflater.from(parent.context), parent, false)
        context = parent.context
        return ViewHolder()
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        holder.bind(getItem(position)!!)
        //Not duplicate items
        //USING BELOW CODE IS A BAD THING !!!
        //holder.setIsRecyclable(false)
    }

    override fun getItemViewType(position: Int): Int {
        return position
    }

    inner class ViewHolder : RecyclerView.ViewHolder(binding.root) {
        @SuppressLint("SetTextI18n")
        fun bind(item: MessageEntity) {
            binding.apply {
                //txtName.text = item.name
                if(!addedIdList.contains(item.id)){
                    addedIdList.add(item.id)
                    txtID.text = item.id.toString()
                    txtName.text = item.name
                    txtPhone.text = ": " + item.phone
                    //txtDate.text = "${item.date} ساعت ${item.hour}"
                    txtDate.text = "${item.date} - ${item.hour}"
                    //txtDate.text = item.date
                    //txtHour.text = item.hour
                    txtMessage.text = item.message
                    Log.i("LOG:", item.message)
                    //Menu
                    imgMenu.setOnClickListener {
                        val popupMenu = PopupMenu(context, it)
                        popupMenu.menuInflater.inflate(R.menu.menu_item_delete, popupMenu.menu)
                        popupMenu.show()
                        //Click
                        popupMenu.setOnMenuItemClickListener { menuItem ->
                            when (menuItem.itemId) {
                                R.id.itemDelete -> {
                                    onItemClickListener?.let {
                                        it(item, DELETE, position)
                                    }
                                }
                            }
                            return@setOnMenuItemClickListener true
                        }
                    }
                    root.setOnClickListener {
                        Log.i("LOG:", "${item.id}")
                    }
                }

            }
        }
    }

    private var onItemClickListener: ((MessageEntity, String, Int) -> Unit)? = null

    fun setOnItemClickListener(listener: (MessageEntity, String, Int) -> Unit) {
        onItemClickListener = listener
    }

    companion object {
        val differCallback = object : DiffUtil.ItemCallback<MessageEntity>() {
            override fun areItemsTheSame(oldItem: MessageEntity, newItem: MessageEntity): Boolean {
                return oldItem.id == newItem.id
            }

            override fun areContentsTheSame(oldItem: MessageEntity, newItem: MessageEntity): Boolean {
                return oldItem == newItem
            }
        }
    }
}

and This is the fragment:


@AndroidEntryPoint
class SentMessageFragment : Fragment() {
    //Binding
    private var _binding: FragmentSentMessageBinding? = null
    private val binding get() = _binding!!

    @Inject
    lateinit var sentMessageAdapter: SentMessageAdapterPaging

    @Inject
    lateinit var messageEntity: MessageEntity

    @Inject
    lateinit var myPagingSource: SentMessagesPagingSource

    //ViewModel
    private val viewModel: SentMessageViewModel by viewModels()

    //Other
    private var allSentMessageJob: Job? = null
    private var searchSentMessageJob: Job? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        setHasOptionsMenu(true)
        super.onCreate(savedInstanceState)
    }

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
        _binding = FragmentSentMessageBinding.inflate(layoutInflater)
        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        //INITIALIZING THE VIEWS
        binding.apply {
            //SUPPORTING THE TOOLBAR
            val activity = activity as AppCompatActivity
            activity.setSupportActionBar(sentMessageToolbar)
            activity.supportActionBar?.setDisplayShowTitleEnabled(false)
            //INIT RECYCLERVIEW
            recyclerSentMessage.setupRecyclerview(LinearLayoutManager(requireContext(), LinearLayoutManager.VERTICAL, false), sentMessageAdapter)
            //GETTING DATA CONSIDERING PAGING
            viewModel.loadPagedData()
            viewModel.pagedMessagesLiveData.observe(viewLifecycleOwner){ pagingData ->
                Log.i("LOG","-->>Data Observed!")
                lifecycleScope.launch(Dispatchers.Main) {
                    sentMessageAdapter.submitData(pagingData)
                }
            }
            //SHOW LOADING DATA
            showLoading()
            //MESSAGES MENU CLICKED
            sentMessageAdapter.setOnItemClickListener { entity, type, position ->
                when (type) {
                    DELETE -> {
                        messageEntity.id = entity.id
                        messageEntity.name = entity.name
                        messageEntity.phone = entity.phone
                        messageEntity.date = entity.date
                        messageEntity.time = entity.time
                        messageEntity.message = entity.message
                        messageEntity.status = entity.status
                        //WE NEED TO REMOVE DELETED ITEM FROM THE PAGED LIST ADAPTER TOO
                        viewModel.updateSentMessageStatus2(messageEntity)
                        //sentMessageAdapter.removeItem(position)
                        lifecycleScope.launch(Dispatchers.IO) {
                            delay(100)
                            //myPagingSource.invalidate()
                            sentMessageAdapter.refresh()
                        }
                    }
                }
            }
            //DELETE ALL THE SENT MESSAGES
            //Filter
            sentMessageToolbar.setOnMenuItemClickListener {
                when (it.itemId) {
                    R.id.deleteAllSentMessages -> {
                        if (emptyLay.visibility != View.VISIBLE) {
                            showDialog()
                        }
                        return@setOnMenuItemClickListener true
                    }
                    R.id.actionSearch -> {
                        SearchSentMessagesFragment().show(requireActivity().supportFragmentManager, SearchSentMessagesFragment().tag)
                        return@setOnMenuItemClickListener true
                    }

                    else -> {
                        return@setOnMenuItemClickListener false
                    }
                }
            }
        }

        //MANAGE THE STACK
        val callback = object : OnBackPressedCallback(true) {
            override fun handleOnBackPressed() {
                requireActivity().finish()
            }
        }
        requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, callback)
    }

    /** SHOW LOADING IN CASE OF GET OR RELOAD DATA*/
    private fun showLoading(){
        lifecycleScope.launch {
            lifecycle.repeatOnLifecycle(Lifecycle.State.CREATED) {
                sentMessageAdapter.loadStateFlow.collect {
                    val state = it.append
                    binding.progressBar.isVisible = state is LoadState.Loading
                    //CHECK IF THE LIST IS EMPTY OR NOT
                    delay(200)
                    if(sentMessageAdapter.itemCount < 1){
                        showEmpty(true)
                    }else{
                        showEmpty(false)
                    }
                }
            }
        }
    }

    /** DIALOG IN ORDER TO DELETE ALL SENT MESSAGES*/
    private fun showDialog() {
        val dialog = Dialog(requireContext())
        dialog.requestWindowFeature(Window.FEATURE_NO_TITLE)
        dialog.setCancelable(true)
        dialog.setContentView(R.layout.dialog_delete_all_sent_messages)
        val yesBtn = dialog.findViewById(R.id.positiveButton) as TextView
        val noBtn = dialog.findViewById(R.id.negativeButton) as TextView
        dialog.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))

        yesBtn.setOnClickListener {
            //Delete All Messages
            viewModel.deleteAllSentMessages()
            dialog.dismiss()
        }
        noBtn.setOnClickListener {
            dialog.dismiss()
        }
        dialog.show()
        val window: Window? = dialog.window
        window?.setLayout(
            ConstraintLayout.LayoutParams.MATCH_PARENT,
            ConstraintLayout.LayoutParams.WRAP_CONTENT
        )

    }

    private fun showEmpty(isShown: Boolean) {
        binding.apply {
                if (isShown) {
                    emptyLay.isVisible(true, recyclerSentMessage)
                } else {
                    emptyLay.isVisible(false, recyclerSentMessage)
                }
        }
    }

    @Deprecated("Deprecated in Java", ReplaceWith("super.onCreateOptionsMenu(menu, inflater)", "androidx.fragment.app.Fragment"))
    override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
        inflater.inflate(R.menu.menu_toolbar_search_sent_message, menu)
        super.onCreateOptionsMenu(menu, inflater)
    }

    override fun onPause() {
        super.onPause()
        //Save RecyclerView State
        viewModel.recyclerviewState =
            binding.recyclerSentMessage.layoutManager?.onSaveInstanceState()
    }

    override fun onDestroy() {
        super.onDestroy()
        _binding = null
    }

}
0

There are 0 answers