45

I am new to kotlin programming. What I want is that I want to remove a particular data from a list while iterating through it, but when I am doing that my app is crashing.

for ((pos, i) in listTotal!!.withIndex()) {

            if (pos != 0 && pos != listTotal!!.size - 1) {

                if (paymentsAndTagsModel.tagName == i.header) {
                    //listTotal!!.removeAt(pos)
                    listTotal!!.remove(i)
                }



            }
        }

OR

 for ((pos,i) in listTotal!!.listIterator().withIndex()){
            if (i.header == paymentsAndTagsModel.tagName){
                listTotal!!.listIterator(pos).remove()
            }

        }

The exception which I am getting

java.lang.IllegalStateException
Anuj Mody
  • 505
  • 1
  • 5
  • 6

9 Answers9

39
val numbers = mutableListOf(1,2,3,4,5,6)
val numberIterator = numbers.iterator()
while (numberIterator.hasNext()) {
    val integer = numberIterator.next()
    if (integer < 3) {
        numberIterator.remove()
    }
}
murgupluoglu
  • 6,524
  • 4
  • 33
  • 43
  • 1
    This should be the accepted answer as the Kotlin docs clearly state the following: "For iterating mutable collections, there is MutableIterator that extends Iterator with the element removal function remove(). So, you can remove elements from a collection while iterating it." – Johann Jun 19 '20 at 16:41
37

use removeAll

pushList?.removeAll {  TimeUnit.MILLISECONDS.toMinutes(
      System.currentTimeMillis() - it.date) > THRESHOLD }
Mustafa Güven
  • 15,526
  • 11
  • 63
  • 83
24

It's forbidden to modify a collection through its interface while iterating over it. The only way to mutate the collection contents is to use Iterator.remove.

However using Iterators can be unwieldy and in vast majority of cases it's better to treat the collections as immutable which Kotlin encourages. You can use a filter to create a new collections like so:

listTotal = listTotal.filterIndexed { ix, element ->
    ix != 0 && ix != listTotal.lastIndex && element.header == paymentsAndTagsModel.tagName
}
miensol
  • 39,733
  • 7
  • 116
  • 112
  • Ok thanks, Sir I used your solution but the thing is I have to notify my adapter for data change but it's not working – Anuj Mody Feb 02 '18 at 07:38
  • @AnujMody Please provide more details about what adapter you are using. Have you called [notifyDataSetChanged](https://developer.android.com/reference/android/support/v7/widget/RecyclerView.Adapter.html#notifyDataSetChanged()) ? – miensol Feb 02 '18 at 07:42
  • There is a much better answer from @murgupluoglu now. – Khantahr Dec 30 '21 at 20:18
23

The answer by miensol seems perfect.

However, I don't understand the context for using the withIndex function or filteredIndex. You can use the filter function just by itself.

You don't need access to the index the list is at, if you're using lists.

Also, I'd strongly recommend working with a data class if you already aren't. Your code would look something like this

Data Class

data class Event(
        var eventCode : String,
        var header : String
)

Filtering Logic

fun main(args:Array<String>){

    val eventList : MutableList<Event> = mutableListOf(
            Event(eventCode = "123",header = "One"),
            Event(eventCode = "456",header = "Two"),
            Event(eventCode = "789",header = "Three")
    )


    val filteredList = eventList.filter { !it.header.equals("Two") }

}
Satej S
  • 2,113
  • 1
  • 16
  • 22
9

The following code works for me:

val iterator = listTotal.iterator()
for(i in iterator){
    if(i.haer== paymentsAndTagsModel.tagName){
        iterator.remove()
    }
}

You can also read this article.

double-beep
  • 5,031
  • 17
  • 33
  • 41
sadhu
  • 115
  • 1
  • 5
1

People didn't break iteration in previous posts dont know why. It can be simple but also with extensions and also for Map:

fun <T> MutableCollection<T>.removeFirst(filter: (T) -> Boolean) =
    iterator().removeIf(filter)

fun <K, V> MutableMap<K, V>.removeFirst(filter: (K, V) -> Boolean) =
    iterator().removeIf { filter(it.key, it.value) }

fun <T> MutableIterator<T>.removeFirst(filter: (T) -> Boolean): Boolean {
    for (item in this) if (filter.invoke(item)) {
        remove()
        return true
    }
    return false
}
Renetik
  • 5,887
  • 1
  • 47
  • 66
0

Use a while loop, here is the kotlin extension function:

fun <E> MutableList<E>.removeIfMatch(isMatchConsumer: (existingItem: E) -> Boolean) {
    var index = 0
    var lastIndex = this.size -1

    while(index <= lastIndex && lastIndex >= 0){
        when {
            isMatchConsumer.invoke(this[index]) -> {
                this.removeAt(index)
                lastIndex-- // max is decreased by 1
            }
            else -> index++ // only increment if we do not remove
        }
    }
}
Derwrecked
  • 771
  • 1
  • 6
  • 17
0

Typically you can use:

yourMutableCollection.removeIf { someLogic == true }

However, I'm working with an Android app that must support APIs older than 24. In this case removeIf can't be used.

Here's a solution that is nearly identical to that implemented in Kotlin Collections that doesn't rely on Predicate.test - which is why API 24+ is required in the first place

 //This function is in Kotlin Collections but only for Android API 24+
fun <E> MutableCollection<E>.removeIff(filter: (E) -> Boolean): Boolean {
    var removed = false
    val iterator: MutableIterator<E> = this.iterator()
    while (iterator.hasNext()) {
        val value = iterator.next()
        if (filter.invoke(value)) {
            iterator.remove()
            removed = true
        }
    }
    return removed
}
Chris Sprague
  • 3,158
  • 33
  • 24
0

Another solution that will suit small collections. For example set of listeners in some controller.

inline fun <T> MutableCollection<T>.forEachSafe(action: (T) -> Unit) {
    val listCopy = ArrayList<T>(this)
    for (element: T in listCopy) {
        if (this.contains(element)) {
            action(element)
        }
    }
}

It makes sure that elements of collection can be removed safely even from outside code.

Artem_Iens
  • 182
  • 2
  • 11