I have a list of CustomObject and I wish to remove a value from the list with same id(property of CustomObject).
Now this can be done by using loop. But that seems a very unnecessary and inefficient code to me. Is there any other way to get specific object with id?
If you don't want to use the removeIf method, you can do something like this:
yourList.apply { removeAt( first { it.id == id } ) }
If your project's min sdk version is 24 and up then you can use removeIf
yourList.removeIf(model -> model.id == yourid);
If you check the code inside removeIf, actually it also iterate
throughout the list to remove matching object.
Beside this, if you are using Kotlin or want to, Then you can filter the list with your id and then remove those from your list.
val yourList = arrayListOf<Model>()
val removeList = yourList.filter { model ->
model.id == yourid
}
yourList.removeAll(removeList)
Related
I am updating the old mutableStateOf() object data by modifying it but new data is not reflecting on it.
variable: as
val offTime = mutableStateOf<List<OffTime>>(emptyList())
update code
fun updateOffTime(newOffTime: OffTime, index: Int){
val updatedOffTime = offTime.value.mapIndexed { i, offTime ->
var result = offTime
if(index == i) result = newOffTime
result
}
offTime.value = updatedOffTime
Log.d(TAG, "updateOffTime: $updatedOffTime")
Log.d(TAG, "updateOffTime: ${offTime.value}")
}
Note: It works when the object OffTime is without id
i.e. OffTime(fromTime, toTime) :- works
OffTime(id,fromTime, toTime) :- doesn't works
I don't know exactly your use-case, neither your entire code implementation, but if I may ask why are you using an ordinary collection list instead of a SnapshotStateList or an extension of its new instance, mutableStateListOf when its part of your requirement to perform list operations?
Have you tried converting your offtime as a SnapshotStateList like this?
val offTime = mutableStateListOf<OffTime>(mutableStateListOf())
and performing updates liks this?
offTime.add( ... )
//or
offtime.remove(...)
//or
offTime[index] = offtime copy
//or
var offTimeIterator = offTime.lisIterator() // where you can safely modify indeces
SnapshotStateList is created exactly for such use-cases in compose, where you can perform normal list operations such as (add, remove, update, or batch updates) and guarantees re-composition.
Your call
offTime.value = updatedOffTime // if this is a new instance of a list
will trigger an entire re-composition as the entire list reference had been changed, but with SnapshotStateList, any changes to the structure is guaranteed to match a specific re-composition, say if you modify an item at index 7, and if this is observed by say a LazyColumn only LazyColumn's 7th index will re-compose
Also I don't know if your Offtime is a data class or a standard class, I would recommend it to be a data-class so you can easily copy() a certain instance of it, pass a new value to a certain property of it and re-assign it in a SnapshotStateList.
I'm working on a simple calorie counter app using two fragments and a ViewModel. I'm a beginner and this is a modification of an app I just created for a course (this app is not a homework assignment). It uses ViewModel and has a fragment that collects user input and a fragment that displays the input as a MutableList of MutableLiveData. I would like for the list screen to initially be empty except for a TextView with instructions, and I'd like the instructions to disappear once an entry has been added to the list. My class instructor told me to use an if-else statement in the fragment with the list to achieve this, but it's not working. He didn't tell me exactly where to put it. I tried a bunch of different spots but none of them worked. I don't get errors - just no change to the visibility of the TextView.
Here is the code for the ViewModel with the list:
val entryList: MutableLiveData<MutableList<Entry>>
get() = _entryList
init {
_entry = MutableLiveData<Entry>()
_entryList.value = mutableListOf()
}
fun addEntry(entryInfo: Entry){
_entry.value = entryInfo
_entryList.value?.add(_entry.value!!)
}
}
And this is the code for the observer in the list fragment:
Observer { entryList ->
val entryListView: View = inflater.inflate(R.layout.fragment_entry_list, null, false)
if (entryList.isNullOrEmpty()) {
entryListView.instructions_text_view.visibility = View.VISIBLE
} else {
entryListView.instructions_text_view.visibility = View.GONE
}
entryList.forEach {entry ->
val view: View = inflater.inflate(R.layout.entry_list_item, null, false)
view.date_entry_text_view.text = String.format(getString(R.string.date), entry.date)
view.calories_entry_text_view.text =
view.line_divider
binding.entryList.addView(view)
}
Thanks for any help.
I guess you are expecting your observer to get notified of the event when you are adding entryInfo to your event list (_entryList.value?.add(_entry.value!!).
But this won't happen as you are just adding an element to the same mutable list, and as the list reference hasn't changed, live data won't emit any update.
To solve this, you have two options.
Create a new boolean live data which controls when to show and hide the info text. Set its initial value to false, and update it to true in addEntry() function.
Instead of updating the same mutable list, create of copy of it, add the element and set the entryList.value equal to this new list. This way your observer will be notified of the new list.
Additionally, its generally not a good practice to expose mutable data unless there is no alternative. Here you are exposing a mutable list of Entry and that too in the form of a mutable live data. Ideally, your should be exposing LiveData<List<Entry>>.
This is one possible implementation of all the points that I mentioned:
private val _entryList = MutableLiveData(listOf<Entry>()) // Create a private mutable live data holding an empty entry list, to avoid the initial null value.
val entryList: LiveData<List<Entry>> = _entryList // Expose an immutable version of _entryList
fun addEntry(entryInfo: Entry) {
_entryList.value = entryList.value!! + entryInfo
}
I haven't used the _entry live data here, but you can implement it the same way.
set your viewModel to observe on entry added.
I think you have gotten your visibility toggle in the your if else blocks wrong.
if (entryList.isNullOrEmpty()) {
entryListView.instructions_text_view.visibility = View.GONE // OR View.INVISIBLE
} else {
entryListView.instructions_text_view.visibility = View.VISIBLE
}
Your Observer should get notified of changes to entryList when _entryList has changed. Make sure you are calling addEntry() function to trigger the notification.
Android 4.1.2
Kotlin 1.4.21
I have the following live data that I add to, but when it comes to removing it doesn't remove any elements.
val selectedLiveData by lazy { MutableLiveData<List<Core>>() }
I don't want to trigger the observers so I am not assigning the value as I just want to remove a single element from the liveData list and only trigger when adding.
None of the following work
selectedLiveData.value?.toMutableList()?.apply {
removeAt(0)
}
selectedLiveData.value?.toMutableList()?.apply {
removeFirst()
}
selectedLiveData.value?.toMutableList()?.apply {
remove(Core)
}
I am adding my elements like this and then assigning the value so the observers to this live data get updated:
selectedLiveData.value = selectedLiveData.value?.toMutableList()?.apply {
add(core)
}
What you wanted is
val selectedLiveData = MutableLiveData<List<Core>>(emptyList())
Then
selectedLiveData.value = selectedLiveData.value.toMutableList().apply {
removeAt(0)
}.toList()
So what are you doing exactly:
You create a MutableLiveData with a List of objects. As we know in Kotlin List is immutable, so it's readonly.
If you want to add / remove items from a list, you should use MutableList.
If we look the documentation of toMutableList which you are using:
/**
* Returns a new [MutableList] filled with all elements of this collection.
*/
public fun <T> Collection<T>.toMutableList(): MutableList<T> {
return ArrayList(this)
}
So every time you try to remove an item via:
selectedLiveData.value?.toMutableList()
you actually perform that operation on a new MutableList not the original one.
If you want to add / remove I suggest you to use MutableList in your MutableLiveData so you can create something similar to this:
private val selectedLiveData = MutableLiveData<MutableList<Int>>()
// Init
selectedLiveData.value = mutableListOf(100, 200)
// Add items
selectedLiveData.value?.add(2)
selectedLiveData.value?.add(10)
selectedLiveData.value?.add(50)
// Remove item
selectedLiveData.value?.remove(2)
selectedLiveData.postValue(selectedLiveData.value.toMutableList().apply {
removeAt(0)
}.toList())
It's been a while that I just started to learn how to develop in Kotlin.
There is this thing that I am working on, I am trying to parse a list into another type of list. Basically they are the same thing but with different names. But when I try to populate the new list with the data that I get from the list given as parameter in the function the list only gets populated with the first object.
Here is my function:
fun convertRoomClass(course: List<Course>) : List<Courses> {
lateinit var list : List<Courses>
course.forEach {
val id = it.pathID
val name = it.pathName
val desc = it.pathDescription
val crs : Courses = Courses(id, name!!, desc!!)
list = listOf(crs)
}
return list
}
The error in your code is that you are making a list in every iteration of the loop. You should make the list first and then add every item from the loop to it!
fun convertRoomClass(courses: List<Course>) : List<AnotherCourseClass> {
val newList = mutableListOf<AnotherCourseClass>()
courses.forEach {
newList += AnotherCourseClass(it.pathID, it.pathName, it.pathDescription)
}
return newList
}
A better solution is to use the map function
fun convertRoomClass(courses: List<Course>) = courses.map {
AnotherCourseClass(it.pathID, it. pathName, it.pathDescription)
}
You might be looking for Kotlin Map
Example:
course.map { Courses(it.pathID, it.pathName,it.pathDescription) }
You're getting the list with only on object, cause the function listOf(crs) returns a list of all objects that are passed as a parameters. Saying the same thing in Java you're doing something like this:
for (course: Courses) {
Course course = new Course(...);
List<Course> list = new ArrayList<>();
list.add(course);
return list;
}
As you can see the it created new list with a single object per iteration.
What you're trying to achieve, can be done with operator map{...} which simply transforms every object in the initial list using code passed inside map and returns list of transformed objects
course.map{ Courses(...) }
Also, I've noticed that you're using the !! operator when creating a Courses object. Probably because the Course can have nullable name, while Courses can't. I'm considering this as a bad practice, cause in this case you're saying
Please throw an Exception if the name is null.
I think that a much better approach is to provide an alternative, like:
val name = course.name ?: "default", saying
Please use name or "default" if the name is null.
or skip objects without name, or any other approach that suits your situation.
You could use MutableList instead of List. That enable you to append new element at the end of your list instead of replace the entire list by doing : list = listOf(crs)
So replace the type of your var lateinit var list : List<Courses> by lateinit var list : MutableList<Courses> then replace list = listOf(crs) by list.add(crs)
Hope it helps and have fun :)
I have the following function:
override fun insertUpdatedItems(items: List<AutomobileEntity>) {
if (!items.isEmpty()) {
items.forEachIndexed { index, automobileEntity ->
if (automobileEntity.id == items[index].id) {
automobileCollection[index] = items[index]
notifyItemInserted(index)
}
}
}
}
I'm using to provide data for a recyclerview, I'm trying to insert updated/edited items that are already in automobileCollection which size always returns 10 items but the items list might differ it can be 1 to 10.
It's supposed to compare items by id but what I'm getting currently with this function is the edited items are just inserted to recyclerview's adapter and not treated as an already existing item.
On the contrary, if I iterate using automobileCollection I get IndexOutOfBoundsException since most of the time the items list is smaller than automobileCollection.
To update a list with items from another one, you can use several ways.
First starting with a direct replacement (which preserves the order, but that's just a detail):
val sourceList = TODO()
val targetList = TODO()
targetList.replaceAll { targetItem ->
sourceList.firstOrNull { targetItem.id == it.id }
?: targetItem
}
Alternatively removing all the items and adding them again:
targetList.removeIf { targetItem ->
sourceList.any { it.id == targetItem.id }
}
targetList.addAll(sourceList)
Using listIterator (note! that's actually also happening under the hood when you call replaceAll... not in the same way, but similar ;-)):
val iterator = targetList.listIterator()
while (iterator.hasNext()) {
iterator.next().apply {
sourceList.firstOrNull { id == it.id }?.also(iterator::set)
}
}
Probably not so readable... For your forEachIndexed I do not really see any use-case. For other problems there definitely are, but I would suggest you try to omit indices (and also forEach) as often as you can. If nothing better comes to your mind, then forEach is also ok, but many times, forEach (and even more so forEachIndexed) isn't the best approach to solve an issue.