PagingDataAdapter from Paging3 library internally manages two semantically different lists. adapter.snapshot() returns a ItemSnapshotList which includes placeholder elements. adapter.snapshot().items return a List which excludes placeholder items.
Now I had to update an element using it's id. Should find and pass the index based on ItemSnapshotList or List? Eg:
adapter.notifyItemChanged(
adapter.snapshot().indexOfFirst { it!!.id == id } // is this correct ?
adapter.snapshot().items.indexOfFirst { it.id == id } // or is this ?
, PAYLOAD_STATUS to Status.Active // payloads
)
I think it's impossible. Any change on your item,you need to destroy and rebuild the pagingList.
I think the best practice is change in your room, and the list observe the room.
Related
I have an initial list of elements in common
val commonList = mutableListOf<String>
I have the following object
ClassObject(val title: String, val subjects: List<String>)
Assuming you have such a list with initial data
val list = listOf<ClassObject> /* With a size of 1000 samples */
How can I iterate the initial list "list" and look in the list of "subjects" for the elements that are not in "commonList" and add them to this last list.
I had thought of a nested for loop, but it would require a lot of power and time.
list.forEach { c ->
c.subjects.forEach { s ->
if (s !in commonList)
commonList.add(s)
}
}
You should use MutableSet instead of MutableList for this purpose. Using an in check on a Set does not require iteration so it is far more performant. It would also make your code a lot simpler, because it won't redundantly add items, so you can simply use an addAll call on it to achieve the same thing as if you had first checked if each item is in the set first.
val commonSet = mutableSetOf<String>()
val list = listOf<ClassObject>( /*...*/ )
for (classObject in list) {
commonSet.addAll(classObject.subjects)
}
In Big-O notation, the above is O(n), whereas your original code is O(n^2).
So to paraphrase, it seems you want to accumulate all unique entries from your nested list into a flat list commonList.
In which case, using a MutableSet would be a good idea. I'm not familiar with the Kotlin implementation of sets, but traditionally they are like hashmaps that don't map keys to values but instead hold unique keys. The difference from a simple List being that they are internally more sophisticated than a plain array, and benefit from faster collision look-ups.
In conclusion, you can't really avoid the nested loop, but you can eliminate the wasteful !in check. If you must end up with a list at the end, you can always call toList on the set.
How to update, delete or add item in a LazyColumn in Jetpack Compose ?
Which is easily achieve by adapter.notifydatasetchange or diffutils in recyclerview
Just do exactly that on the collection you are passing to items function. This is simple as that. If your class is stable (just use data class) you are fine, it will just work. Welcome to Compose magic.
If you want the best performance on update/delete/add then expose a stable key in the items function, see here for more details: https://developer.android.com/jetpack/compose/lists#item-keys
Example:
#Composable
fun MessageList(messages: List<Message>) {
LazyColumn {
items(
items = messages,
key = { message ->
// Return a stable + unique key for the item
message.id
}
) { message ->
// Display entry here
MessageRow(message)
}
}
}
Here, if you provide the key lambda, the compose will know that this entry is the same entry - just with different content. If you do not provide this lambda - index in list will be used as key. So any addition other than at the end of the list would trigger a lot of recompositions. So it is more or less like a diff utils. You only have to provide this, because content equality is handled by compose implicitly - via equals of the Message object.
So if you want to remove one message from the list - remove it and pass new list to MessageList. Compose will handle the rest for you
Can someone explain me what's the main purpose of the 'key' parameter inside items/itemsIndexed function of LazyListScope? What do we get or don't get if we specify that parameter? I'm not sure that I understand the official docs related to this parameter:
key - a factory of stable and unique keys representing the item. Using
the same key for multiple items in the list is not allowed. Type of
the key should be saveable via Bundle on Android. If null is passed
the position in the list will represent the key. When you specify the
key the scroll position will be maintained based on the key, which
means if you add/remove items before the current visible item the item
with the given key will be kept as the first visible one.
I think the best answer is provided by the official doc:
By default, each item's state is keyed against the position of the item in the list. However, this can cause issues if the data set changes, since items which change position effectively lose any remembered state. If you imagine the scenario of LazyRow within a LazyColumn, if the row changes item position, the user would then lose their scroll position within the row.
To combat this, you can provide a stable and unique key for each item, providing a block to the key parameter. Providing a stable key enables item state to be consistent across data-set changes:
#Composable
fun MessageList(messages: List<Message>) {
LazyColumn {
items(
items = messages,
key = { message ->
// Return a stable + unique key for the item
message.id
}
) { message ->
MessageRow(message)
}
}
}
you can use this way
#Composable
fun A(list: MutableList<Model>) {
Column {
LazyColumn {
items(
count = list.size,
key = {
list[it].id
}, itemContent = { index ->
Text(text = list[index].text)
}
)
}
}
}
I have one recyclerview where i am appending list with 1 loadmore data
my requirement is to do not allow to add duplicate object data in main arraylist and sort array but it's not working.
i done some thing like this to remove duplicate data but it's removing all data one by one
newElementsList.forEach { first ->
mList.remove(first)
}
newElementsList is get new data every 5 min and mList is main list(it's value assign on start and on loadmore)
in model i added this
override fun equals(other: Any?): Boolean {
if (other !is ChatData) {
return false
}
val that: ChatData = other as ChatData
// Custom equality check here.
return this.id == that.id
}
but it's removing item one by one from the main list
How can i achieve this both main and new arraylist have same data type
Any help would be highly appreciated.
At present the removal for each element one by one creates an overhead, a better alternative for this use-case is a TreeSet (log(n) time for operations like add, remove and contains)
You could use an TreeSet.addAll() to pass a collection and it will maintain the order for you as well as ensure there are no duplicates present.
Make sure to implement Comparable interface on your model for the set to infer the natural ordering.
Please read the documentation for more information on the usage:
TreeSet - https://docs.oracle.com/javase/7/docs/api/java/util/TreeSet.html
Comparable interface - https://docs.oracle.com/javase/7/docs/api/java/lang/Comparable.html
I have been implementing the new Paging Library with a RecyclerView with an app built on top of the Architecture Components.
The data to fill the list is obtained from the Room database. In fact, it is fetched from the network, stored on the local database and provided to the list.
In order to provide the necessary data to build the list, I have implemented my own custom PageKeyedDataSource. Everything works as expected except for one little detail. Once the list is displayed, if any change occurs to the data of a list's row element, it is not automatically updated. So, if for example my list is showing a list of items which have a field name, and suddenly, this field is updated in the local Room database for a certain row item, the list does not update the row UI automatically.
This behaviour only happens when using a custom DataSource unlike when the DataSource is obtained automatically from the DAO, by returning a DataSource Factory directly. However, I need to implement a custom DataSource.
I know it could be updated by calling the invalidate() method on the DataSource to rebuild the updated list. However, if the app is showing 2 lists at a time (half screen each for example), and this item appears in both lists, it would be needed to call invalidate() for both lists separately.
I have thought with a solution in which, instead of using an instance of the item's class to fill each ViewHolder, it uses a LiveData wrapped version of it, to make each row observe for changes on its own item and update that row UI when necessary. Nevertheless, I see some downsides on this approach:
A LifeCycleOwner (such as the Fragment containing the RecyclerView for example) must be passed to the PagedListAdapter and then forward it to the ViewHolder in order to observe the LiveData wrapped item.
A new observer will be registered for each list's new row, so I do not know at all if it has an excessive computational and memory cost, considering it would be done for every list in the app, which has a lot of lists in it.
As the LifeCycleOwner observing the LiveData wrapped item would be, for example, the Fragment containing the RecyclerView, instead of the ViewHolder itself, the observer will be notified every time a change on that item occurs, even if the row containing that item is not even visible at that moment because the list has been scrolled, which seems to me like a waste of resources that could increase the computational cost unnecessarily.
I do not know at all if, even considering those downsides, it could seem like a decent approach or, maybe, if any of you know any other cleaner and better way to manage it.
Thank you in advance.
Quite some time since last checked this question, but for anyone interested, here is the cause of my issue + a library I made to observe LiveData properly from a ViewHolder (to avoid having to use the workaround explained in the question).
My specific issue was due to a bad use of Kotlin's Data Classes. When using them, it is important to note that (as explained in the docs), the toString(), equals(), hashCode() and copy() will only take into account all those properties declared in the class' constructor, ignoring those declared in the class' body. A simple example:
data class MyClass1(val prop: Int, val name: String) {}
data class MyClass2(val prop: Int) {
var name: String = ""
}
fun main() {
val a = MyClass1(1, "a")
val b = MyClass1(1, "b")
println(a == b) //False :) -> a.name != b.name
val c = MyClass2(2)
c.name = "c"
val d = MyClass2(2)
d.name = "d"
println(c == d) //True!! :O -> But c.name != d.name
}
This is specially important when implementing the PagedListAdapter's DiffCallback, as if we are in a example's MyClass2 like scenario, no matter how many times we update the name field in our Room database, as the DiffCallback's areContentsTheSame() method is probably always going to return true, making the list never update on that change.
If the reason explained above is not the reason of your issue, or you just want to be able to observe LiveData instances properly from a ViewHolder, I developed a small library which provides a Lifecycle to any ViewHolder, making it able to observe LiveData instances the proper way (instead of having to use the workaround explained in the question).
https://github.com/Sarquella/LifecycleCells