PagingDataAdapter class not executed after called adapter.submitData method - android

i want to try Paging 3 in my android apps. It successed until i get my PagingData<*> list from class PagingDataSource. but every time i called adapter.submitData(it), my adapter class not execute. Please help, where is my mistake? Thank you so much.
this is my code homeFragment.kt to call
private lateinit var webinarListAdapter: myAdapter
fun initAdapter() {
webinarAdapter = myAdapter()
with(recyclerView) {
layoutManager = LinearLayoutManager(requireContext())
adapter = webinarAdapter
adapter = webinarAdapter.withLoadStateHeaderAndFooter(
header = WebinarLoadStateAdapter { webinarAdapter.retry() },
footer = WebinarLoadStateAdapter { webinarAdapter.retry() }
)
}
lifecycleScope.launch {
viewModel.user.collectLatest {
webinarAdapter.submitData(it)
}
}
}```
MyAdapter.kt class
```class myAdapter: PagingDataAdapter<User, UserHolder>(DIFF_CALLBACK) {
override fun onBindViewHolder(holder: UserHolder, position: Int) {
Log.e(TAG, "onBindViewHolder")
holder.onBind(getItem(position)!!)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserHolder {
return UserHolder.create(parent)
}
companion object {
private val DIFF_CALLBACK = object : DiffUtil.ItemCallback<User>() {
override fun areItemsTheSame(oldItem: User, newItem: User) =
oldItem.id == newItem.id
override fun areContentsTheSame(oldItem: Taklim, newItem: Taklim) = oldItem == newItem
}
}
}```

Add this to you adapter
fun submitList(list: List<T>) {
yourlist.submitList(list)
notifyDataSetChanged()
}

after debug more and more i know where is my mistake. my mistake is on my paging data source executed catch exception. so my adapter not executed and my viewmodel not receive any data. Thanks!

Related

ListAdapter submitlist not update after deleting the data instead its giving me duplicate data

when i try to delete first row i get first row back and with duplicate second row
enter image description here
after delete any row from the list gives me duplicate data like this
enter image description here
This is my Adapter class. On my deleteItem function position is passed from fragment class and
it is supposed to delete an item from the given position and update the list but its rendering the duplicate data.
class MyListAdapter :ListAdapter<Article,MyListAdapter.MyViewHolder>(MyDiffUtil()) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.recycler_item,parent,false)
return MyViewHolder(view)
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
val item = getItem(position)
holder.onBind(item)
// to click each item of list
holder.itemView.setOnClickListener {
onItemClickListener?.let {it(item) }
}
}
class MyViewHolder(itemView :View) : RecyclerView.ViewHolder(itemView)
{
fun onBind(article: Article) = with(itemView)
{
Glide.with(this)
.load(article.urlToImage)
.error(R.drawable.notfound)
.into(imageView)
webSource.text = article.source?.name
newsDate.text = article.publishedAt
newsTitle.text = article.title
newsDescription.text = article.description
}
}
// lamda function for handling web view
private var onItemClickListener : ((Article) -> Unit)? = null
fun setOnItemClickListener(listener : (Article) ->Unit)
{
onItemClickListener = listener
}
// implementing diffutil class
class MyDiffUtil : DiffUtil.ItemCallback<Article>()
{
override fun areItemsTheSame(oldItem: Article, newItem: Article): Boolean {
return oldItem.url == newItem.url
}
override fun areContentsTheSame(oldItem: Article, newItem: Article): Boolean {
return oldItem == newItem
}
}
fun deleteItem(pos : Int)
{
var myl = ArrayList<Article>()
val currentList= currentList.toMutableList()
currentList.removeAt(pos)
myl.addAll(currentList)
submitList(myl)
}
}
`
I tried to delete the saved news from the ListAdapter and update the recycler view with animation of DiffUtill class but its not updating the recycler view and
giving duplicate data . How can i delete data with diffutil animation as its normal way. Thanks in advance.
this is my fragment
package com.example.news.Fragments
class SavedNews : Fragment() {
lateinit var mymainViewModel: MainViewModel
lateinit var databaseObject: MyDataBase
lateinit var myAdapter: MyListAdapter
lateinit var convertedArticleList : ArrayList<Article>
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
myAdapter = MyListAdapter()
convertedArticleList = ArrayList()
return inflater.inflate(R.layout.fragment_saved_news, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
savedNewsRecyclerView.layoutManager = LinearLayoutManager(activity,LinearLayoutManager.VERTICAL,
false)
savedNewsRecyclerView.adapter = myAdapter
ItemTouchHelper(object :ItemTouchHelper.SimpleCallback(0,ItemTouchHelper.RIGHT){
override fun onMove(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder
): Boolean {
// this method is called
// when the item is moved.
return true
}
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
// this method is called when we swipe our item to right direction.
// on below line we are getting the item at a particular position.
val deletedCourse: Article =
convertedArticleList.get(viewHolder.adapterPosition)
myAdapter.deleteItem(viewHolder.adapterPosition)
mymainViewModel.deleteSavedNews(deletedCourse.id!!)
// myAdapter.notifyItemRemoved(viewHolder.adapterPosition)
// myAdapter.submitList(myCurrentList)
// Snackbar.make(savedNewsRecyclerView,"Deleted" + deletedCourse.title,
// Snackbar.LENGTH_LONG).setAction(
// "Undo",
// View.OnClickListener {
//
// convertedArticleList.add(position,deletedCourse)
// myAdapter.notifyItemInserted(position)
// }
// ).show()
}
}).attachToRecyclerView(savedNewsRecyclerView)
}
override fun onResume() {
super.onResume()
Log.d("LIFE","ON Resume")
val myInterfaceObject = ApiInterface.MyObject.getInstance()
databaseObject = MyDataBase.MyObjectDB.getDBInstance(activity as MainActivity)
val myRepository = Repository(myInterfaceObject, databaseObject)
mymainViewModel = ViewModelProvider(
this,
MainViewModelFactory(myRepository)
).get(MainViewModel::class.java)
//listAdapter things
// show saved news in savednews fragment
lifecycleScope.launch(Dispatchers.Main) {
//abstract data from saved room database and converting likedartcile datacass object to
// Artticle data class article
mymainViewModel.abstractSavedNews().observe(viewLifecycleOwner, Observer {
it.forEach { eachLikedArticle ->
val obj = toArticle(eachLikedArticle)
convertedArticleList.add(obj)
}
myAdapter.submitList(convertedArticleList)
})
//clicking the item of save news
myAdapter.setOnItemClickListener {
val bundle = Bundle().apply {
putSerializable("article", it)
}
convertedArticleList.clear()
findNavController().navigate(R.id.action_savedNews_to_article, bundle)
}
}
}
private fun toArticle(rawObject: LikedArticle) = Article(
rawObject.author, rawObject.content,
rawObject.description, rawObject.publishedAt, rawObject.source, rawObject.title,
rawObject.url, rawObject.urlToImage, rawObject.id
)
}
There is not enough information to actually replicate how you're trying to use this in your Fragment/Activity but without changing your code too much, this works (note I used view binding):
class MyListAdapter :
ListAdapter<Article, MyViewHolder>(MyDiffUtil()) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = MyViewHolder(
RecyclerItemBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
)
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
val item = getItem(position)
holder.onBind(item)
}
fun deleteItem(pos: Int) {
var myl = ArrayList<Article>()
val currentList = currentList.toMutableList()
currentList.removeAt(pos)
myl.addAll(currentList)
submitList(myl)
}
}
class MyViewHolder(private val binding: RecyclerItemBinding) : RecyclerView.ViewHolder(binding.root) {
fun onBind(article: Article) = with(binding) {
textView.text = article.url
}
}
class MyDiffUtil : DiffUtil.ItemCallback<Article>() {
override fun areItemsTheSame(oldItem: Article, newItem: Article): Boolean {
return oldItem.url == newItem.url
}
override fun areContentsTheSame(oldItem: Article, newItem: Article): Boolean {
return oldItem == newItem
}
}
Overall this should work.
You can also refactor deleteItem to something more meaningful:
fun deleteItem(pos: Int) {
val oldList = currentList.toMutableList()
oldList.removeAt(pos)
val updatedList = oldList
submitList(updatedList)
}

RecyclerView only displaying data after restarting Activity

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

Delete Item in RecyclerView and Dao with buttonclick

I have a RecyclerView displaying data from a Dao.
In the data that is shown in the recyclerView is a button with which the item should get deleted.
I know the function for that is :
fun deleteReceipt(receipts: Receipts) = viewModelScope.launch {
receiptDao.delete(receipts)
}
But I am not quite sure where to put it and call it. Because if I want to call it in the Adapter where i set the other displayed Item values I have no access to the Dao
Here is the 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)
}
}
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)
}
}
}
The ViewModel:
#HiltViewModel
class PurchaseViewmodel #Inject constructor(
private val receiptDao: ReceiptDao
): ViewModel() {
private val tasksEventChannel = Channel<TasksEvent>()
val addTaskEvent = tasksEventChannel.receiveAsFlow()
val receipts = receiptDao.getAllReceipts().asLiveData()
fun onAddResult(result: Int){
when (result){
ADD_RECEIPT_RESULT_OK ->showReceiptSavedConfirmation("Receipt is saved")
}
}
private fun showReceiptSavedConfirmation (text: String) = viewModelScope.launch {
tasksEventChannel.send(TasksEvent.ShowReceiptSavedConfirmation(text))
}
fun deleteReceipt(receipts: Receipts) = viewModelScope.launch {
receiptDao.delete(receipts)
}
sealed class TasksEvent {
data class ShowReceiptSavedConfirmation(val msg: String) : TasksEvent()
}
}
And the Adapter:
class ExampleAdapter : ListAdapter<Receipts,ExampleAdapter.ExampleViewHolder>(DiffCallback()) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ExampleViewHolder { // Basically how to get a new Item from the List and display it
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
}
}
Create a listener using Interface
Implement the listener in Fragment
Create listener variable in Adapter
pass the listener from fragment to adapter
pass the listener to viewHolder
on Delete button click of that item call the listener method, which will trigger the implementation in Fragment where you have access to your ViewModel
delete the receipt using viewModel.deleteReceipt

How can I get the position of a item in ListAdapter of Android?

I use ListAdapter as the source of a RecyclerView, it will display a list of MVoice. You can see Code B.
I think I can get the position of a MVoice in ListAdapter, so I can scroll to the position of the item in RecyclerView, just like Code A
Is there a way to get the position of a Movice?
Code A
binding.recyclerViewVoice.adapter = myAdapter
mHomeViewModel.listVoiceBySort.observe(this.viewLifecycleOwner) {
myAdapter.submitList(it)
}
//val position=myAdapter.getPostionByItem(aMovice)
//binding.recyclerViewVoice.scrollToPosition(position)
Code B
class VoiceAdapters (private val aHomeViewModel: HomeViewModel, private val mPlay: PlayInterface):
ListAdapter<MVoice, VoiceAdapters.VoiceViewHolder>(MVoiceDiffCallback()) {
private lateinit var mContext: Context
private lateinit var mLifecycleOwner:LifecycleOwner
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VoiceViewHolder {
mContext = parent.context
mLifecycleOwner = mContext as LifecycleOwner
return VoiceViewHolder(
LayoutVoiceItemBinding.inflate(LayoutInflater.from(parent.context), parent, false).also {
it.lifecycleOwner = mLifecycleOwner
it.aHomeViewModel = aHomeViewModel
}
)
}
override fun onBindViewHolder(holder: VoiceViewHolder, position: Int) {
val inputMVoice = getItem(position)
holder.bind(inputMVoice)
}
inner class VoiceViewHolder (private val binding: LayoutVoiceItemBinding):
RecyclerView.ViewHolder(binding.root) {
fun bind(inputMVoice: MVoice) {
binding.amVoice = inputMVoice
binding.executePendingBindings()
}
}
}
class MVoiceDiffCallback : DiffUtil.ItemCallback<MVoice>() {
override fun areItemsTheSame(oldItem: MVoice, newItem: MVoice): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: MVoice, newItem: MVoice): Boolean {
return oldItem == newItem
}
}
You can get a reference to the list currently displayed with currentList, and use indexOf() to get the position
fun getPositionByItem(aMovice: MVoice) = currentList.indexOf(aMovice)
Add a ItemClick Listener in fun bind(), then pass the MVoice.getPosition in Toast.
Thus you would able to see the position of item in your toast.

RecyclerView views updated even when there is no difference based on DiffUtil?

I'm using ListAdapter with a DiffUtil.ItemCallback and I have made the DiffUtil callback methods return true for all items to inspect its behavior. And it seems that the RecyclerView views are still getting updated and showing the new result ('D', 'E', 'F'). I did not expect this, Shouldn't the RecyclerView only update views/items that have changed?
Adapter:
class ItemsAdapter : ListAdapter<Item, ItemViewHolder>(ItemDiff) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder {
return ItemViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.item_item, parent, false))
}
override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
holder.bind(getItem(position))
}
}
object ItemDiff : DiffUtil.ItemCallback<Item>() {
override fun areItemsTheSame(oldItem: Item, newItem: Item): Boolean {
return true
}
override fun areContentsTheSame(oldItem: Item, newItem: Item): Boolean {
return true
}
}
class ItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val tvName = itemView.findViewById<TextView>(R.id.tvName)
fun bind(item: Item) {
tvName.text = item.name
}
}
data class Item(val name: String)
MainActivity:
class MainActivity : AppCompatActivity() {
private val adapter = ItemsAdapter()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
rvItems.layoutManager = LinearLayoutManager(this)
rvItems.adapter = adapter
adapter.submitList(listOf(Item("A"), Item("B"), Item("C")))
adapter.submitList(listOf(Item("D"), Item("E"), Item("F")))
}
}
I figured out the problem and it seems like it was a concurrency issue, Since I was calling the submitList method twice and ListAdapter calculates the diff on the background thread.
To test this I changed my activity code to the following and the list was not updated.
class MainActivity : AppCompatActivity() {
private val adapter = ItemsAdapter()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
rvItems.layoutManager = LinearLayoutManager(this)
rvItems.adapter = adapter
adapter.submitList(listOf(Item("A"), Item("B"), Item("C")))
thread {
Thread.sleep(1000)
runOnUiThread {
adapter.submitList(listOf(Item("D"), Item("E"), Item("F")))
Toast.makeText(this, "second submitList", Toast.LENGTH_LONG).show()
}
}
}
}

Categories

Resources