RecyclerView only displaying data after restarting Activity - android

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

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 displays first item

So, I am creating a cocktail app, based on the https://www.thecocktaildb.com/ api. Thus far, I have only created a screen to display options based on the ingredient I put in the search bar (search bar is not done yet). Yet when I run the app, only the first entry is displayed
By putting Log.e("TAG", "$position") inside of my onBindViewHolder, of the adapter, I saw that the position variable never increases from 0
class CocktailsAdapter: RecyclerView.Adapter<CocktailsAdapter.CocktailsViewHolder>() {
inner class CocktailsViewHolder(val binding: ItemCocktailPreviewBinding) :
RecyclerView.ViewHolder(binding.root)
private val differCallback = object : DiffUtil.ItemCallback<CocktailsByBaseDto>() {
override fun areItemsTheSame( oldItem: CocktailsByBaseDto, newItem: CocktailsByBaseDto): Boolean {
return oldItem.drinks[0].idDrink == newItem.drinks[0].idDrink
}
override fun areContentsTheSame(oldItem: CocktailsByBaseDto, newItem: CocktailsByBaseDto): Boolean {
return oldItem.drinks[0] == newItem.drinks[0]
}
}
val differ = AsyncListDiffer(this, differCallback)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CocktailsViewHolder {
return CocktailsViewHolder(
ItemCocktailPreviewBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
)
}
override fun onBindViewHolder(holder: CocktailsViewHolder, position: Int) {
val binding = holder.binding
val cocktail = differ.currentList[position]
holder.itemView.apply {
Glide.with(this).load(cocktail.drinks[position].strDrinkThumb).into(binding.imgCocktailsMainRecyclerViewImage)
binding.tvCocktailsMainRecyclerViewTitle.text = cocktail.drinks[position].strDrink
Log.e("TAG", "$position")
setOnClickListener {
onItemClickListener?.let { it(cocktail) }
}
}
}
override fun getItemCount(): Int {
return differ.currentList.size
}
private var onItemClickListener: ((CocktailsByBaseDto) -> Unit)? = null
fun setOnItemClickListener(listener: (CocktailsByBaseDto) -> Unit) {
onItemClickListener = listener
}
I have tried both position and 0 (which makes more sense) inside val cocktail = differ.currentList[position], but neither gave me a different result
Fixed it by changing the class I had passed to DiffUtil.ItemCallback

RecyclerView items not displaying

The title says, I'm trying to load items into a recyclerview but the items doesn't displaying.
I'm getting the items from the api, using retrofit and mutablelivedata, I'm getting the items right, and the adapter gets the items (there are 3 items, and the adapter gets the 3 items), but the items doesn't display on the UI.
Here is my code:
Adapter.java
class EstablecimientosAdapter : ListAdapter<EstablecimientoModel, EstablecimientosViewHolder>(
DIFF_CALLBACK
) {
companion object {
val DIFF_CALLBACK: DiffUtil.ItemCallback<EstablecimientoModel> =
object : DiffUtil.ItemCallback<EstablecimientoModel>() {
override fun areItemsTheSame(
oldItem: EstablecimientoModel,
newItem: EstablecimientoModel
): Boolean {
return oldItem.hash == newItem.hash
}
override fun areContentsTheSame(
oldItem: EstablecimientoModel,
newItem: EstablecimientoModel
): Boolean {
return oldItem.nombre == newItem.nombre
}
}
}
private val mEstablecimientos: MutableList<EstablecimientoModel> = mutableListOf()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): EstablecimientosViewHolder {
val binding = RowEstablecimientoBinding.inflate(
LayoutInflater.from(parent.context), parent, false
)
return EstablecimientosViewHolder(binding)
}
override fun onBindViewHolder(holder: EstablecimientosViewHolder, position: Int) {
val establecimiento = getItem(position)
holder.bindItem(establecimiento!!)
}
override fun getItemCount(): Int {
return mEstablecimientos.size
}
fun addMoreEstablecimientos(newEstablecimientos: List<EstablecimientoModel>) {
mEstablecimientos.addAll(newEstablecimientos)
submitList(mEstablecimientos)
}
class EstablecimientosViewHolder(val binding: RowEstablecimientoBinding) :
RecyclerView.ViewHolder(
binding.root
) {
fun bindItem(establecimiento: EstablecimientoModel) {
binding.setVariable(BR.establecimiento, establecimiento)
}
}
}
recycler_view.xml
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/rvEstablecimientos"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:listitem="#layout/row_establecimiento" />
Fragment.java
val cvGoBackEstablecimientos = binding!!.cvGoBackEstablecimientos
val rvEstablecimientos = binding!!.rvEstablecimientos
val adapter = EstablecimientosAdapter()
rvEstablecimientos.setHasFixedSize(true)
rvEstablecimientos.adapter = adapter
cvGoBackEstablecimientos.setOnClickListener { requireActivity().onBackPressed() }
viewModel?.getNearFreeEstablecimientos(2.0, 2.0)
?.observe(viewLifecycleOwner) { t -> adapter.addMoreEstablecimientos(t!!) }
It seems that I had some problems with the layout, I removed a CoordinatorLayout and it works now.

PagingDataAdapter class not executed after called adapter.submitData method

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!

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.

Categories

Resources