NotifyDataSetChanged method does not refresh my RecyclerView Kotlin - android

I`m trying to understand why adapter method notifyDataSetChanged() not refresh my recyclerview. I find a solution when create method in adapter like this:
fun setData(list: List<DownloadModel>){
resumeList = list
notifyDataSetChanged()
}
This solution works but, i want to know why i can't do something like this:
private lateinit var downloadAdapter: DownloadRecyclerAdapter
private fun setupAdapter() {
downloadAdapter = DownloadRecyclerAdapter(
this#DownloadActivity,
downloadList,
{ id -> onViewClick(id) },
{ id -> onEditClick(id) },
{ id, position -> onDeleteClick(id, position) }
)
savedResumeRv.apply {
layoutManager = LinearLayoutManager(context)
layoutAnimation = AnimationUtils.loadLayoutAnimation(
this#DownloadActivity,
R.anim.layout_animation_down_to_up
)
adapter = downloadAdapter
}
}
private fun observers() {
downloadViewModel.getDownloadList().observe(this, Observer { list ->
downloadList = list
list?.let {
downloadAdapter.notifyDataSetChanged()
}
})
}
downloadAdapter hold same instance of list downloadList, and i wonder why when i notify adapter in activity not work properly.

downloadList = list
because of this line it is not same reference to download list anymore
try
private fun observers() {
downloadViewModel.getDownloadList().observe(this, Observer { list ->
with(downloadList){
clear()
addAll(list)
}
downloadList?.let {
downloadAdapter.notifyDataSetChanged()
}
})
}

You have to do:
val resumeList: MutableList<DownloadModel>
fun setData(list: List<DownloadModel>){
resumeList.clear()
resumeList.addAll(list)
notifyDataSetChanged()
}
I can agree that the previous reply will work for you, but it breaks the encapsulation of the data that the Adapter is holding. That is why in the first place you've hit that bug.
No client of the Adapter should be able to modify directly it's data without any protection.

Related

notifyDataSetChanged not updating view

I am using viewModel in recycler view, but its not updating only if I enter and exist screen not in screen itself:
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
viewModel = ViewModelProvider(requireActivity()).get(RideDataViewModel::class.java)
rideList.adapter = MyRideListItemRecyclerViewAdapter(viewModel.rides.value!!, object : OnItemClickListener {
override fun onItemClicked(ride: RideEntity) {
viewModel.setRide(ride)
findNavController().navigate(R.id.rideDataFragment)
}
})
viewModel.rides.observe(viewLifecycleOwner, {
if (it.isNotEmpty())
{
no_rides.visibility = View.GONE
rideList.adapter!!.notifyDataSetChanged() //Function called but no visible data
}
else
{
no_rides.visibility = View.VISIBLE
}
})
lifecycleScope.launch {
viewModel.setRides(DataBaseHelper.getAllRidesFromDB())
}
}
ViewModel:
class RideDataViewModel : ViewModel() {
var rides= MutableLiveData<List<RideEntity>>(listOf())
fun setRides(item: List<RideEntity>) {
rides.postValue(item)
}
}
You don't show your adapters code, but you need to set the adapters underlying data/list using the it reference before calling notifyDataSetChanged
if (it.isNotEmpty()) {
no_rides.visibility = View.GONE
rideList.adapter!!.data = it // something similar to set the list in your adapter to the new values
rideList.adapter!!.notifyDataSetChanged() //Function called but no visible data
}
Where are you setting the list on your adapter? In your LiveData observer you're only calling notifyDataSetChanged(), which will only make the RecyclerView redraw the items, but you don't set the items first so it has nothing to redraw.
I don't know how your adapter looks, but something along the lines of the code below should make it update
if (it.isNotEmpty()) {
no_rides.visibility = View.GONE
rideList.adapter!!.list = it // Update the list in the adapter
rideList.adapter!!.notifyDataSetChanged() // Now this will have something to draw
}

Sorting a DataSource does not sort the visible list on the screen

I have the following extension function -
fun DataSource.Factory<Int, CountryEntity>.sortBy(comparator: Comparator<in CountryEntity>): DataSource.Factory<Int, CountryEntity> {
return mapByPage { list ->
list.sortedWith(comparator)
}
}
And the following implementation -
class CountriesRepository(val viewmodel: ViewModel) {
.
.
.
fun getAllCountries(comparator: Comparator<in CountryEntity>?): LiveData<PagedList<CountryEntity>> {
if (comparator == null)
return countryDao.getAllCountries().toLiveData(10)
return countryDao.getAllCountries().sortBy(comparator).toLiveData(10)
}
}
//Fragment
override fun onOptionsItemSelected(item: MenuItem): Boolean {
var comparator: Comparator<CountryEntity>? = null
when (item.itemId) {
R.id.country_list_menu_order_by_country_name_ascending -> {
comparator = compareBy { country -> country.nativeName }
}
R.id.country_list_menu_order_by_country_name_descending -> {
comparator = compareByDescending { country -> country.nativeName }
}
R.id.country_list_menu_order_by_area_ascending -> {
comparator = compareBy { country -> country.area }
}
R.id.country_list_menu_order_by_area_descending -> {
comparator = compareByDescending { country -> country.area }
}
}
countriesViewModel.getAllCountries(comparator).observe(this, Observer { list ->
countriesAdapter.submitList(list)
})
return super.onOptionsItemSelected(item)
}
The issue I am facing is the fact that the list does indeed get sorted but the visible portion of the list does not, meaning I need to scroll down in order for the list to invaidate itself and than scroll back up to see the updated data.
Also, it seems like it always ignored the first item in the list and does not show it
What am I missing?
Another thing - how do I get rid of the animations of ListAdapter?
If your RecyclerView or ListView contains only countries, then you should call adapter.notifyDataSetChanged() to refresh changed dataset.
Clear the RecyclerView item animator to remove item animation.
recyclerView.setItemAnimator(null);
You can re-enable your animation after if needed.
recyclerView.setItemAnimator(null);
adapter.notifyDataSetChanged();
recyclerView.setItemAnimator(new DefaultItemAnimator());

How change MutableLiveData value inside ViewModel

I need to change the value of MutableLiveData in my ViewModel, but I can't make it because the value is equal to null, I think need to establish an observer change it inside that, but I don't know how to do it and whether it's a good idea.
AudioRecordersListViewModel
class AudioRecordersListViewModel() : ViewModel() {
var audioRecordsLiveData: MutableLiveData<MutableList<AudioRecordUI>> = MutableLiveData();
private var audioRecordDao: AudioRecordDao? = null
#Inject
constructor(audioRecordDao: AudioRecordDao) : this() {
this.audioRecordDao = audioRecordDao
viewModelScope.launch {
val liveDataItems = audioRecordDao
.getAll().value!!.map { item -> AudioRecordUI(item) }
.toMutableList()
if (liveDataItems.size > 0) {
liveDataItems[0].isActive = true
}
audioRecordsLiveData.postValue(liveDataItems)
}
}
}
AudioRecordDao
#Dao
interface AudioRecordDao {
#Query("SELECT * FROM AudioRecordEmpty")
fun getAll(): LiveData<MutableList<AudioRecordEmpty>>
}
First of all, using !! is not a good idea it can easily lead to NullPointer Exception, us ? instead.
You can set an empty list on your LiveData and add new data to that List:
var audioRecordsLiveData: MutableLiveData<MutableList<AudioRecordUI>> = MutableLiveData();
init {
audioRecordsLiveData.value = mutableListOf()
}
If you need to observe that LiveData:
mViewModel.mLiveData.observe(this, Observer { list ->
if (list.isNotEmpty()) {
//Update UI Stuff
}
})
never set your LiveData inside Fragment/Activity
If you need to update your LiveData:
mViewModel.onSomethingHappened()
Inside ViewModel:
fun onSomethingHappened() {
...
...
...
mLiveData.value = NEW_VALUE
}
If you want to update your LiveData from another thread use:
mLiveData.postValue()

RecyclerView, DiffUtil and weird animation

I have a chat app working with websockets.
Via websocket I receive message, which I save into db, messages table, and update last message id in conversation table. Now, both saves will notify cursor. So I call updateDate twice. They will run sequentially, in correct order and on correct threads. Yet, I guess, the animations are overlapping and making weird effect visible in youtube video attached (visible from message "i"). Can anybody pinpoint my problem or give me solution?
I am using rxjava for threading and dequeue for stacking updates, while always getting only last update from dequeue. Inspiration from vlc here
RecyclerView is stacking from end and if user is scrolled to bottom, then animation scroll to bottom is run when new message comes.
Fragment
adapter = ChatMessageAdapter()
recycler.apply {
adapter = this#ChatDetailFragment.adapter
layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false).apply { stackFromEnd = true }
}
ChatMessageAdapter
private val pendingUpdates: ArrayDeque<Single<Pair<MutableList<ChatItemHolder>, DiffUtil.DiffResult>>> = ArrayDeque()
#MainThread
fun updateData(msgs: MutableList<ChatMessage>, onDone: () -> Unit) {
pendingUpdates.add(
internalUpdate(msgs)
.subscribeOn(Schedulers.computation())
.observeOn(AndroidSchedulers.mainThread())
.doOnSuccess {
items.clear()
items.addAll(it.first)
it.second.dispatchUpdatesTo(this#ChatMessageAdapter)
onDone()
}
)
if (pendingUpdates.size == 1) pendingUpdates.peek().subscribe(Consumer {processQueue()})
}
private fun internalUpdate(msgs: MutableList<ChatMessage>) = Single.create<Pair<MutableList<ChatItemHolder>, DiffUtil.DiffResult>> {
val newItems = temp(msgs) // transforming function from ChatMessage[] to ChatItemHolder[]
val result = DiffUtil.calculateDiff(DiffUtilCallback(newItems, items), true)
it.onSuccess(Pair(newItems, result))
}
#MainThread
private fun processQueue() {
pendingUpdates.remove()
if (!pendingUpdates.isEmpty()) {
if (pendingUpdates.size > 1) {
val last = pendingUpdates.peekLast()
pendingUpdates.clear()
pendingUpdates.add(last)
}
pendingUpdates.peek().subscribe(Consumer { processQueue() })
}
}
Thank you!

Check for "no documents" state or empty data-set in FirebaseUI's FirebaseRecyclerAdapter

I wrote a custom FirebaseRecylerAdapter, based on FirebaseUI documentation like this:
class FavoritesAdapter(lifecycleOwner: LifecycleOwner) : FirebaseRecyclerAdapter<Favorite, FavoritesAdapter.FavoritesHolder>(buildOptions(lifecycleOwner)) {
companion object {
private fun buildQuery() = FirebaseDatabase.getInstance()
.reference
.child("favorites")
.limitToLast(50)
private fun buildOptions(lifecycleOwner: LifecycleOwner) = FirebaseRecyclerOptions.Builder<Favorite>()
.setQuery(buildQuery(), Favorite::class.java)
.setLifecycleOwner(lifecycleOwner)
.build()
}
//...
This class overrides an onDataChanged() function:
override fun onDataChanged() {
// Called each time there is a new data snapshot. You may want to use this method
// to hide a loading spinner or check for the "no documents" state and update your UI.
// ...
}
Now, how do I actually check for that "no documents" state in order to update my UI? I can find no way to check for an empty data-set. I am looking for something like getItemCount().
The adapter does have an itemCount property and you can also use snapshots to get a live list of your model objects. Example:
override fun onDataChanged() {
if (itemCount == 0) {
// Do stuff
}
}

Categories

Resources