Problems with Room on Android Studio - android

So, i want to create a button on Android Studio that updates my list in a sorting order, Ascending, etc., but i've been running in to some problems with the code and i can wrap my head around it. When i click the button nothing happends, it doesn't sort my list at all
Using Room Database FrameWork from Andriod Studio.
This is what i using to do the sorting:
//'Produto' is the list, 'nome' is a element on that list that i want to sort
#Entity
#Parcelize
data class Produto(
#PrimaryKey(autoGenerate = true)
val id: Long,
val nome: String)
#Query("SELECT * FROM Produto")
fun buscaTodos() : List<Produto>
//This is the code that i use to do the sorting
#Query("SELECT * FROM Produto ORDER BY nome ASC")
fun getAllSortedByName(): List<Produto>
This is the code to i'm using to do the sorting after a press the button
class ListaProdutosAdapter(
private val context: Context,
produtos: List<Produto> = emptyList(),
var quandoClicaNoItem: (produto: Produto) -> Unit = {}
) : RecyclerView.Adapter<ListaProdutosAdapter.ViewHolder>() {
override fun onOptionsItemSelected(item: MenuItem): Boolean {
private val adapter = ListaProdutosAdapter(context = this)
val db = AppDatabase.instancia(this)
val produtoDao = db.produtoDao()
//menu_ordem_asc_id being the button id
when (item.itemId) {
R.id.menu_ordem_asc_id -> {
produto?.let { produtoDao.getAllSortedByName()}
adapter.atualiza(produtoDao.buscaTodos())
//This is in another class, but i put it here so it's easier to understand
fun atualiza(produtos: List<Produto>) {
this.produtos.clear()
this.produtos.addAll(produtos)
notifyDataSetChanged()
}
}
return super.onOptionsItemSelected(item)
}

Well, produtoDao.getAllSortedByName() doesn't sort the items in place, it returns a list of sorted items. So when you do produto?.let { produtoDao.getAllSortedByName()} you don't do anything with the result which is the sorted list.
On the next line you call adapter.atualiza(produtoDao.buscaTodos()) and as you mentioned in your comments produtoDao.buscaTodos() returns an unsorted list of products. And this is what you populate your adapter with.
In order to populate the adapter with the sorted list you should call adapter.atualiza(produtoDao.getAllSortedByName()) instead.

Related

LiveData list of objects from Room query not showing up in the view

I'm currently trying to use a SQLite database via the Room library on my Jetpack Compose project to create a view that does the following:
display a list of entries from the database that are filtered to only records with the current user's ID
allow the user to create new records and insert those into the database
update the list to include any newly created records
My issue is that I cannot get the list to show when the view is loaded even though the database has data in it and I am able to insert records into it successfully. I've seen a lot of examples that show how do this if you are just loading all the records, but I cannot seem to figure out how to do this if I only want the list to include records with the user's ID.
After following a few of tutorials and posts it is my understanding that I should have the following:
A DAO, which returns a LiveData object
A repository which calls the DAO method and returns the same LiveData object
A viewholder class, which will contain two objects: one private MutableLiveData variable and one public LiveData variable (this one is the one we observe from the view)
My view, a Composable function, that observes the changes
However, with this setup, the list still will not load and I do not see any calls to the database to load the list from the "App Inspection" tab. The code is as follows:
TrainingSet.kt
#Entity(tableName = "training_sets")
data class TrainingSet (
#PrimaryKey() val id: String,
#ColumnInfo(name = "user_id") val userId: String,
TrainingSetDao.kt
#Dao
interface TrainingSetDao {
#Insert(onConflict = OnConflictStrategy.ABORT)
suspend fun insert(trainingSet: TrainingSet)
#Query("SELECT * FROM training_sets WHERE user_id = :userId")
fun getUserTrainingSets(userId: String): LiveData<List<TrainingSet>>
}
TrainingSetRepository.kt
class TrainingSetRepository(private val trainingSetDao: TrainingSetDao) {
fun getUserTrainingSets(userId: String): LiveData<List<TrainingSet>> {
return trainingSetDao.getUserTrainingSets(userId)
}
suspend fun insert(trainingSet: TrainingSet) {
trainingSetDao.insert(trainingSet)
}
}
TrainingSetsViewModel.kt
class TrainingSetsViewModel(application: Application): ViewModel() {
private val repository: TrainingSetRepository
private val _userTrainingSets = MutableLiveData<List<TrainingSet>>(emptyList())
val userTrainingSets: LiveData<List<TrainingSet>> get() = _userTrainingSets
init {
val trainingSetDao = AppDatabase.getDatabase(application.applicationContext).getTrainingSetDao()
repository = TrainingSetRepository(trainingSetDao)
}
fun getUserTrainingSets(userId: String) {
viewModelScope.launch {
_userTrainingSets.value = repository.getUserTrainingSets(userId).value
}
}
fun insertTrainingSet(trainingSet: TrainingSet) {
viewModelScope.launch(Dispatchers.IO) {
try {
repository.insert(trainingSet)
} catch (err: Exception) {
println("Error!!!!: ${err.message}")
}
}
}
}
RecordScreen.kt
#Composable
fun RecordScreen(navController: NavController, trainingSetsViewModel: TrainingSetsViewModel) {
// observe the list
val trainingSets by trainingSetsViewModel.userTrainingSets.observeAsState()
// trigger loading of the list using the userID
// note: hardcoding this ID for now
trainingSetsViewModel.getUserTrainingSets("20c1256d-0bdb-4241-8781-10f7353e5a3b")
// ... some code here
Button(onClick = {
trainingSetsViewModel.insertTrainingSet(TrainingSet(// dummy test data here //))
}) {
Text(text = "Add Record")
}
// ... some more code here
LazyColumn() {
itemsIndexed(trainingSets) { key, item ->
// ... list row components here
}
}
NavGraph.kt** **(including this in case it's relevant)
#Composable
fun NavGraph(navController: NavHostController) {
NavHost(
navController = navController,
startDestination = Screens.Record.route,
) {
composable(route = Screens.Record.route) {
val owner = LocalViewModelStoreOwner.current
owner?.let {
val trainingSetsViewModel: TrainingSetsViewModel = viewModel(
it,
"TrainingSetsViewModel",
MainViewModelFactory(LocalContext.current.applicationContext as Application)
)
// note: I attempted to load the user training sets here in case it needed to be done before entering the RecordScreen, but that did not affect it (commenting this line out for now)
// trainingSetsViewModel.getUserTrainingSets("20c1256d-0bdb-4241-8781-10f7353e5a3b")
RecordScreen(
navController = navController,
trainingSetsViewModel= TrainingSetsViewModel,
)
}
}
}
}
What somewhat worked...
I was able to get the list to eventually load by making the following two changes (see comments in code), but it still did not load in the expected sequence and this change did not seem to align from all the examples I've seen. I will note that with this change, once the list showed up, the newly created records would be properly displayed as well.
*TrainingSetsViewModel.kt *(modified)
private val _userTrainingSets = MutableLiveData<List<TrainingSet>>(emptyList())
/ ***************
// change #1 (change this variable from a val to a var)
/ ***************
var userTrainingSets: LiveData<List<TrainingSet>> = _userTrainingSets
... // same code as above example
fun getUserTrainingSets(userId: String) {
viewModelScope.launch {
// ***************
// change #2 (did this instead of: _userTrainingSets.value = repository.getUserTrainingSets(userId).value)
// ***************
userTrainingSets = repository.getUserTrainingSets(userId)
}
}
... // same code as above example

What's the recommended way to update Jetpack Compose UI on Room database update?

Right now, my method of updating my jetpack compose UI on database update is like this:
My Room database holds Player instances (or whatever they're called). This is my PlayerDao:
#Dao
interface PlayerDao {
#Query("SELECT * FROM player")
fun getAll(): Flow<List<Player>>
#Insert
fun insert(player: Player)
#Insert
fun insertAll(vararg players: Player)
#Delete
fun delete(player: Player)
#Query("DELETE FROM player WHERE uid = :uid")
fun delete(uid: Int)
#Query("UPDATE player SET name=:newName where uid=:uid")
fun editName(uid: Int, newName: String)
}
And this is my Player Entity:
#Entity
data class Player(
#PrimaryKey(autoGenerate = true) val uid: Int = 0,
#ColumnInfo(name = "name") val name: String,
)
Lastly, this is my ViewModel:
class MainViewModel(application: Application) : AndroidViewModel(application) {
private val db = AppDatabase.getDatabase(application)
val playerNames = mutableStateListOf<MutableState<String>>()
val playerIds = mutableStateListOf<MutableState<Int>>()
init {
CoroutineScope(Dispatchers.IO).launch {
db.playerDao().getAll().collect {
playerNames.clear()
playerIds.clear()
it.forEach { player ->
playerNames.add(mutableStateOf(player.name))
playerIds.add(mutableStateOf(player.uid))
}
}
}
}
fun addPlayer(name: String) {
CoroutineScope(Dispatchers.IO).launch {
db.playerDao().insert(Player(name = name))
}
}
fun editPlayer(uid: Int, newName: String) {
CoroutineScope(Dispatchers.IO).launch {
db.playerDao().editName(uid, newName)
}
}
}
As you can see, in my ViewHolder init block, I 'attach' a 'collector' (sorry for my lack of proper terminology) and basically whenever the database emits a new List<Player> from the Flow, I re-populate this playerNames list with new MutableStates of Strings and the playerIds list with MutableStates of Ints. I do this because then Jetpack Compose gets notified immediately when something changes. Is this really the only good way to go? What I'm trying to achieve is that whenever a change in the player table occurs, the list of players in the UI of the app gets updated immediately. And also, I would like to access the data about the players without always making new requests to the database. I would like to have a list of Players at my disposal at all times that I know is updated as soon as the database gets updated. How is this achieved in Android app production?
you can instead use live data. for eg -
val playerNames:Livedata<ListOf<Player>> = db.playerDao.getAll().asliveData
then you can set an observer like -
viewModel.playerNames.observe(this.viewLifecycleOwner){
//do stuff when value changes. the 'it' will be the changed list.
}
and if you have to have seperate lists, you could add a dao method for that and have two observers too. That might be way more efficient than having a single function and then seperating them into two different lists.
First of all, place a LiveData inside your data layer (usually ViewModel) like this
val playerNamesLiveData: LiveData<List<Player>>
get() = playerNamesMutableLiveData
private val playerNamesMutableLiveData = MutableLiveData<List<Player>>
So, now you can put your list of players to an observable place by using playerNamesLiveData.postValue(...).
The next step is to create an observer in your UI layer(fragment). The observer determines whether the information is posted to LiveData object and reacts the way you describe it.
private fun observeData() {
viewModel.playerNamesLiveData.observe(
viewLifecycleOwner,
{ // action you want your UI to perform }
)
}
And the last step is to call the observeData function before the actual data posting happens. I prefer doing this inside onViewCreated() callback.

DiffUtil not refreshing view in Observer call android kotlin

Hey I am using diff util with ListAdapter. The updating of list works but I can only see those new values by scrolling the list, I need to view the updates even without recycling the view (when scrolling) just like notifyItemChanged(). I tried everything inside this answer ListAdapter not updating item in RecyclerView only working for me is notifyItemChanged or setting adapter again. I am adding some code. Please someone know how to fix this problem?
Data and Enum class
data class GroupKey(
val type: Type,
val abc: Abc? = null,
val closeAt: String? = null
)
data class Group(
val key: GroupKey,
val value: MutableList<Item?> = ArrayDeque()
)
enum class Type{
ONE,
TWO
}
data class Abc(
val qq: String? = null,
val bb: String? = null,
val rr: RType? = null,
val id: String? = null
)
data class RType(
val id: String? = null,
val name: String? = null
)
data class Item(
val text: String? = null,
var abc: Abc? = null,
val rr: rType? = null,
val id: String? = null
)
viewmodel.kt
var list: MutableLiveData<MutableList<Group>?> = MutableLiveData(ArrayDeque())
fun populateList(){
// logic to call api
list.postValue(data)
}
fun addItemTop(){
// logic to add item on top
list.postValue(data)
}
inside view model I am filling data by api call inside viewmodel function and return value to list. Also another function which item is inserting at top of list so that's why is used ArrayDeque
Now I am adding nested reyclerview diff util callback.
FirstAdapter.kt
class FirstAdapter :
ListAdapter<Group, RecyclerView.ViewHolder>(comp) {
companion object {
private val comp = object : DiffUtil.ItemCallback<Group>() {
override fun areItemsTheSame(oldItem: Group, newItem: Group): Boolean {
return oldItem == newItem
}
override fun areContentsTheSame(oldItem: Group, newItem: Group): Boolean {
return ((oldItem.value == newItem.value) && (oldItem.key == newItem.key))
}
}
}
......... more function of adapter
}
FirstViewHolder
val adapter = SecondAdapter()
binding.recyclerView.adapter = adapter
adapter.submitList(item.value)
SecondAdapter.kt
class SecondAdapter : ListAdapter<Item, OutgoingMessagesViewHolder>(comp) {
companion object {
private val comp = object : DiffUtil.ItemCallback<Item>() {
override fun areItemsTheSame(oldItem: Item, newItem: Item): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: Item, newItem: Item): Boolean {
return ((oldItem.rr == newItem.rr) &&
(oldItem.text == oldItem.text) && (oldItem.abc == newItem.abc))
}
}
}
..... more function
}
Activity.kt
viewModel.list.observe(this, { value ->
submitList(value)
})
private fun submitList(list: MutableList<Group>?) {
adapter?.submitList(list)
// adapter?.notifyDataSetChanged()
}
I am 100% sure that my list is updating and my observer is calling when my new list is added. I debug that through debug view. But problem is I can only see those new values by scrolling the list, I need to view the updates even without recycling the view (when scrolling) just like notifyItemChanged()
UPDATE
viewmodel.kt
class viewModel : BaseViewModel(){
var list: MutableLiveData<MutableList<Group>?> = MutableLiveData()
//... more variables...
fun fetchData(context: Context) {
viewModelScope.launch {
val response = retroitApiCall()
response.handleResult(
onSuccess = { response ->
list.postValue(GroupData(response?.items, context))
},
onError = { error ->
Log.e("error" ,"$error")
}
)
}
}
}
internal fun GroupData(items: List<CItem>?, context: Context): MutableList<Group> {
val result: MutableList<Group> = MutableList()
items?.iterator()?.forEach { item ->
// adding item in list by add function and then return list.
return result
}
private fun addItemOnTop(text: String) {
list.value?.let { oldlist ->
// logic to add items on top of oldlist variable
if(top != null){
oldlist.add(0,item)
}else{
val firstGroup = oldlist[0]
firstGroup.value.add(item)
}
list.postValue(oldlist)
}
}
}
I am using sealed class something like this but not this one Example. And Something similar to these when call api Retrofit Example. Both link I am giving you example. What I am using in my viewmodel.
I don't know what's going on, but I can tell you two things that caught my attention.
First Adapter:
override fun areItemsTheSame(oldItem: Group, newItem: Group): Boolean {
return oldItem == newItem
}
You're not comparing if the items are the same, you're comparing the items and their contents are the same. Don't you have an Id like you did in your second adapter?
I'd probably check oldItem.key == newItem.key.
Submitting the List
As indicated in the answer you linked, submitList has a very strange logic where it compares if the reference of the actual list is the same, and if it is, it does nothing.
In your question, you didn't show where the list comes from (it's observed through what appears to be liveData or RXJava), but the souce of where the list is constructed is not visible.
In other words:
// P S E U D O C O D E
val item1 = ...
val item2 = ...
val list1 = mutableListOf(item1, item2)
adapter.submitList(list1) // works fine
item1.xxx = ""
adapter.submitList(list1) // doesn't work well.
WHY?
Unfortunately, submitList's source code shows us that if the reference to the list is the same, the diff is not calculated. This is really not on the adapter, but rather on AsyncListDiffer, used by ListAdapter internally. It is this differ's responsibility to trigger the calculation(s). But if the list references are the same, it doesn't, and it silently ignores it.
My suspicion is that you're not creating a new list. This rather undocumented and silent behavior hurts more than it helps, because more often than not, developers aren't expecting to duplicate a list supplied to an object whose purpose and promise is to offer the ability to "magically" (and more importantly, automatically) calculate its differences between the previous.
I understand why they did it, but I would have at the very least emitted a log WARNING, indicating you're supplying the same list. Or, if you want to avoid polluting the already polluted logCat, then at least be much more explicit about it in its official documentation.
The only hint is this simple phrase:
you can use submitList(List) when new lists are available.
The key here being the word new lists. So not the same list with new items, but simply a new List reference (regardless of whether the items are the same or not).
What should you try?
I'd start by modifying your submitList method:
private fun submitList(list: MutableList<Group>?) {
adapter?.submitList(list.toMutableList())
}
For Java users out there:
adapter.submitList(new ArrayList(oldList));
The change is to create a copy of the list you receive: list.ToMutableList(). This way the AsyncListDiffer's check for list equality will return false and the code will continue.
UPDATE / DEBUG
Unfortunately, I don't know what is going on with your code; I assure you that ListAdapter works, as I use it myself on a daily basis; If you think you've found a case where there are problems with it, I suggest you create a small prototype and publish it on github or similar so we can reproduce it.
I would start by using debug/breakpoints in key areas:
ViewModel; write down the reference fromthe list you "return".
DiffUtil methods, is diffUtil being called?
Your submitList() method, is the list reference the same as the one you had in your ViewModel?
etc.
You need to dig a bit deeper until you find out who is not doing what.
On Deep vs Shallow copy and Java and whatever...
Please keep in mind, ListAdapter (through AsyncDiff) checks if the reference to the list is the same. In other words, if you have a list val x = mutableListOf(...) and you give this to the adapter, it will work the 1st time.
If you then modify the list...
val x = mutableListOf(...)
adapter.submitList(x)
x.clear()
adapter.submitList(x)
This will NOT WORK correctly, because to the eyes of the Adapter both lists are the same (they actually are the same list).
The fact that the list is mutable is irrelevant. (I still frown upon the mutable list; why does submitList accept a mutable list if you cannot mutate it and submit it again, escapes my knowledge but I would not have approved that Pull Request like so) It would have avoided most problems if they only took a non-mutable list, therefore implying you must supply a new list every time if you mutate it. Anyway...
as I was saying, duplicating a list is simple, in either Kotlin or Java there are multiple variations:
val newListWithSameContents = list1.toList()
List newListWithSameContents = ArrayList(list1);
now if list1 has an item...
list1.add("hello")
When you copy list1 into newList... The reference to "Hello" (the string) is the same. If String were mutable (it's not, but assume it is), and you modified that string somehow... you would be modifying both strings at the same time or rather, the same string, referenced in both lists.
data class Thing(var id: Int)
val thing = Thing(1)
val list1: MutableList<Thing> = mutableListOf(thing)
val list2: MutableList<Thing> = list1.toMutableList()
println(list1)
println(list2)
// This prints
[Thing(id=1)]
[Thing(id=1)]
Now modify the thing...
thing.id = 2
println(list1)
println(list2)
As expected, both lists, pointing to the same object:
[Thing(id=2)]
[Thing(id=2)]
This was a shallow copy because the items were not copied. They still point to the same thing in memory.
ListAdapter/DiffUtil do not care if the objects are the same in that regard (depending how you implemented your diffutil that is); but they certainly care if the lists are the same. As in the above example.
I hope this clarifies what is needed for ListAdapter to dispatch updates. If it fails to do so, then check if you're effectively doing the right thing.

ListAdapter fetches the right list but does not update the values

I have the following fuction -
private fun fetchGroupData(callback: (groupModelList: List<GroupModel>) -> Unit) {
val groupModelList = mutableListOf<GroupModel>()
groupViewmodel.getAllGroupEntities().observeOnce(requireActivity(), Observer { groupEntityList ->
groupEntityList.forEach { groupEntity ->
/*
We iterate though all of the available groups,
for each group we get all of it's groupMembers models
*/
val groupName = groupEntity.groupName
val groupId = groupEntity.id
taskViewmodel.getGroupTaskCounter(groupId).observeOnce(requireActivity(), Observer { groupTaskCount ->
/*
For each group we observe it's task counter
*/
groupViewmodel.getGroupMembersForGroupId(groupId).observeOnce(requireActivity(), Observer { groupMembers ->
/*
For each group, we iterate through all of the groupMembers and for each of them we use it's userId
to fetch the user model, getting it's full name and adding it to a list of group users full name.
*/
val groupUsersFullNames = mutableListOf<String>()
groupMembers.forEach { groupMember ->
val memberId = groupMember.userId
groupViewmodel.getGroupParticipantForUserId(memberId).observeOnce(requireActivity(), Observer { groupUser ->
groupUsersFullNames.add(groupUser.fullName)
/*
When the groupUsersFullNames size matches the groupMembers size, we can add a model to our list.
*/
if (groupUsersFullNames.size == groupMembers.size)
groupModelList.add(GroupModel(groupId, groupName, groupTaskCount, groupUsersFullNames))
/*
When the new list matches the size of the group list in the DB we call the callback.
*/
if (groupModelList.size == groupEntityList.size)
callback(groupModelList)
})
}
})
})
}
})
}
That is being used by the following function -
private fun initAdapter() {
fetchGroupData { groupModelList ->
if (groupModelList.isEmpty()) {
binding.groupsListNoGroupsMessageTitle.setAsVisible()
binding.groupsListNoGroupsMessageDescription.setAsVisible()
return#fetchGroupData
}
binding.groupsListNoGroupsMessageTitle.setAsGone()
binding.groupsListNoGroupsMessageDescription.setAsGone()
val newList = mutableListOf<GroupModel>()
newList.addAll(groupModelList)
adapter.submitList(groupModelList)
Log.d("submitList", "submitList")
binding.groupsListRecyclerview.setAdapterWithItemDecoration(requireContext(), adapter)
}
}
These 2 functions represent group list fetch from my local DB into a RecyclerView.
In order to be notified when a new group has been created, I am holding a shared ViewModel object with a boolean indicating if a new group has been created.
In the same Fragment that these 2 functions ^ are written, I am observing this Boolean, and if the value is true I trigger a re-fetch for the entire list -
private fun observeSharedInformation() {
sharedInformationViewModel.value.groupCreatedFlag.observe(requireActivity(), Observer { hasGroupBeenCreated ->
if (!hasGroupBeenCreated) return#Observer
sharedInformationViewModel.value.groupCreatedFlag.value = false
Log.d("submitList", "groupCreatedFlag")
initAdapter()
})
}
At some point in my code in a different Fragment that also has an instance of my shared ViewModel, I trigger a value change for my Boolean LiveData -
sharedInformationViewModel.value.groupCreatedFlag.value = true
Which in turn triggers the observer, and does a re-fetch for my group list.
The issue I am facing is that when re-fetching for a new list (because a new group has been added) I do get the current information and everything should work 100% fine, but the new data - the newly created group - does not appear.
The newly added data appears in the list under 2 circumstances -
I restart the app
The function is triggered again - what happens now is that I see the list with the previous newly added group, but the newest group to be added does not appear.
There is one exception to this issue - if the group list is empty, the first group to be added does indeed appear when I submit the list with one group.
What is it that I am missing?
Edit -
Here is my adapter.
I am using a custom call called DefaultAdapterDiffUtilCallback, which expects a model that implements an interface that defines the unique ID for each model so I can compare new and old models.
class GroupsListAdapter(
private val context: Context,
private val onClick: (model : GroupModel) -> Unit
) : ListAdapter<GroupModel, GroupsListViewHolder>(DefaultAdapterDiffUtilCallback<GroupModel>()) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): GroupsListViewHolder {
val binding = GroupsListViewHolderBinding.inflate(LayoutInflater.from(context), parent, false)
return GroupsListViewHolder(binding)
}
override fun onBindViewHolder(holder: GroupsListViewHolder, position: Int) {
holder.bind(getItem(position), onClick)
}
override fun submitList(list: List<GroupModel>?) {
super.submitList(list?.let { ArrayList(it) })
}
}
/**
* Default DiffUtil callback for lists adapters.
* The adapter utilizes the fact that all models in the app implement the "ModelWithId" interfaces, so
* it uses it in order to compare the unique ID of each model for `areItemsTheSame` function.
* As for areContentsTheSame we utilize the fact that Kotlin Data Class implements for us the equals between
* all fields, so use the equals() method to compare one object to another.
*/
class DefaultAdapterDiffUtilCallback<T : ModelWithId> : DiffUtil.ItemCallback<T>() {
override fun areItemsTheSame(oldItem: T, newItem: T) =
oldItem.fetchId() == newItem.fetchId()
#SuppressLint("DiffUtilEquals")
override fun areContentsTheSame(oldItem: T, newItem: T) =
oldItem == newItem
}
/**
* An interface to determine for each model in the app what is the unique ID for it.
* This is used for comparing the unique ID for each model for abstracting the DiffUtil Callback
* and creating a default general one rather than a new class for each new adapter.
*/
interface ModelWithId {
fun fetchId(): String
}
data class GroupModel(val id: String, val groupName: String, var tasksCounter: Int, val usersFullNames: List<String>) : ModelWithId {
override fun fetchId(): String = id
}
edit 2.0 -
my observeOnce() extension -
fun <T> LiveData<T>.observeOnce(lifecycleOwner: LifecycleOwner, observer: Observer<T>) {
observe(lifecycleOwner, object : Observer<T> {
override fun onChanged(t: T?) {
observer.onChanged(t)
removeObserver(this)
}
})
}
Are you using the "new" ListAdapter?
import androidx.recyclerview.widget.ListAdapter
In this case, I can think of an answer to your problem. But since I do not know more about your exact implementation it is based on my assumptions and you have to verify if it applies or not.
For this ListAdapter you have to implement areItemsTheSame and areContentsTheSame methods.
I've once had a similar problem. I was submitting the list but it just didn't update the list in the view.
I could resolve this issue by checking carefully how I was comparing if the contents are the same or not.
For your comparison function, consider the following:
override fun areContentsTheSame(oldItem: GroupModel, newItem: GroupModel): Boolean {
// assuming GroupModel is a class
// this comparison is most likely not getting the result you want
val groupModelsAreMatching = oldItem == newItem // don't do this
// for data classes it usually gives the expected result
val exampleDataClassesMatch = oldItem.dataClass == newItem.dataClass
// But: the properties that need to be compared need to be declared in the primary constructor
// and not in the function body
// compare all relevant custom properties
val groupIdMatches = oldItem.groupId == newItem.groupId
val groupNameMatches = oldItem.groupName == newItem.groupName
val groupTaskCountMatches = oldItem.groupTaskCount == newItem.groupTaskCount
val groupUsersFullNamesMatches = oldItem.groupUsersFullNames == newItem.groupUsersFullNames
return groupIdMatches && groupNameMatches && groupTaskCountMatches && groupUsersFullNamesMatches
}
And of course you need to make sure that areItemsTheSame. Here you only need to compare the groupIds.
Did you do it like this already?
I figured out the problem.
And it has nothing to do with my fetch logics.
The issue is the following -
When creating a group, I am adding a new Fragment to the backstack and popping it off when completed.
When deleting a group, I am navigating forward to the main Fragment of mine while using popUpTo and popUpToInclusive - that works fine.
I needed to use the navigation rather than popping backwards the stack in order to see the new list.
This took me 3 days of work to figure out. jeez

One-shot request single result in Room Android

I am practicing my android skills (beginner) by coding a grocery list app. I have two tables in my db, a shopping_item table (The items I want to buy) and a reference_item table (The items I know the category and the unit price). Each time I add a shopping item, there is an refId field referencing to the reference item id corresponding. It is a default value to a default reference item if the shopping item is not referenced yet.
I use a MVVM model. I then have a DAO, a repository, a viewModel and my fragments that display data.
When I add a new shopping item, I want to know if there is a corresponding reference item. I want to do the following Query:
#Query(value = "SELECT refId FROM reference_items WHERE reference_item_name = :refName")
suspend fun getRefItem(refName : String) : Int
It returns the id of the reference item corresponding as an Int or is null if it is not referenced yet. In my repository, I have a function like that:
suspend fun getRefItem(refName : String) = db.getShoppingDao().getRefItem(refName)
For now, I think I am doing alright. No mistake in sight I guess.
The problem begin when I try to implement my viewModel. What should I do? What about my fragment?
I have a addNewItem(name: String, amount: Int) function in my fragment to add the new item. I can find the reference item corresponding with the name provided.
I tried multiple things, using LiveData, suspend functions, mutableLiveData/LiveData, but I am getting lost right now. Every tutorials or examples use LiveData or Query all data from the db. I just want one Integer, one Time, no need of LiveData I think.
here is the complete solution. Hope this is useful for you.
DAO
#Query(value = "SELECT refId FROM reference_items WHERE reference_item_name = :refName")
suspend fun getRefItem(refName : String) : Int
Repository
// Specify return datatype as Int
suspend fun getRefItem(refName : String): Int = db.getShoppingDao().getRefItem(refName)
ViewModel
fun getRefItem(name: String): LiveData<Int> {
val result : MutableLiveData<Int>() <-- setup livedata to return as value
viewModelScope.lanuch {
result.postValue(repository.getRefItem(name))
}
return result <-- return livedata
}
Fragment
fun addNewItem(name: String, amount: Int) {
// setup viewModel observer
viewModel.getRefItem(name).observer { viewLifecycleOwner, { value ->
// GET YOUR INT VALUE HERE
Log.i("VALUE", value)
}
}
}

Categories

Resources