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
}
}