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
}
Related
I've been trying to learn MVVM and I have a question regarding the kind of logic that is acceptable for a fragment to contain in order to follow the pattern.
For example when showing a loader, should it be separated into two different states, or is it acceptable to have one state with a boolean argument and for the fragment to handle that logic?
So in the view model should the code be like this
fun makeCall() {
_state.value = Loading(true)
// make the call
_state.value = Loading(false)
}
and in the fragment have the check
private fun handleLoadingState(loading: Boolean) {
if (loading) {
binding.loader.visibility = View.VISIBLE
} else {
binding.loader.visibility = View.GONE
}
}
or like this?
fun makeCall() {
_state.value = ShowLoader
// make the call
_state.value = HideLoader
}
and this
private fun handleShowLoaderState() {
binding.loader.visibility = View.VISIBLE
}
private fun handleHideLoaderState() {
binding.loader.visibility = View.GONE
}
If both ways are correct is maybe one of them considered better? Or it doesn't really matter which one I use?
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());
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()
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.
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
}
}