I have a function that show markers (I call it tasks) on Google Maps. To make it cleaner, I chose an approach that iterates through the already displayed tasks and the new tasks. If a task already exists in maps and does not in the new list, I delete its marker. If the task is in both lists but its version has changed, I modify its marker . And if the task is only in the new list, i add its marker. Each time the user scrolls through the maps, I search for tasks based on the scrolled positon. The user can scroll multiple times, which should stop the previous call of the function.
the problem is that sometimes i get this exception
Fatal Exception: java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.next(ArrayList.java:860)
at com.weryou.android.ui.missions.search.map.SearchMissionMapFragment$displayMissions$1.invokeSuspend(SearchMissionMapFragment.kt:435)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:750)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665)
And this exception
Fatal Exception: java.lang.IndexOutOfBoundsException: Index: 132, Size: 132
at java.util.ArrayList.get(ArrayList.java:437)
at kotlin.collections.CollectionsKt__MutableCollectionsKt.filterInPlace$CollectionsKt__MutableCollectionsKt(CollectionsKt__MutableCollectionsKt.java:284)
at kotlin.collections.CollectionsKt__MutableCollectionsKt.removeAll(CollectionsKt__MutableCollectionsKt.java:269)
at com.weryou.android.ui.missions.search.map.SearchMissionMapFragment$displayMissions$1.invokeSuspend(SearchMissionMapFragment.kt:220)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:750)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665)
This is my function
private val alreadyDisplayedMissions = ArrayList<Pair<MissionHeaderEntity, Marker>>()
private fun displayMissions(newMissions : List<MissionHeaderEntity>)
{
lifecycleScope.launch(Dispatchers.Default) {
val missionsToRemove : List<Pair<MissionHeaderEntity, Marker>> = alreadyDisplayedMissions.filter { oldMission ->
val newVersionOfOldMission : MissionHeaderEntity? = newMissions.find { it.id == oldMission.first.id }
newVersionOfOldMission == null || newVersionOfOldMission != oldMission.first
}
alreadyDisplayedMissions.removeAll { mission -> missionsToRemove.any { mission.first.id == it.first.id } }
// Loop in the new missions to display in map all new missions with a determined location,
// plus the tuto mission of the user if exists.
val missionsToShow : List<Pair<MissionHeaderEntity, MarkerOptions>> = newMissions.filter { newMission ->
(newMission.firstMission == true && newMission.owned == true)
|| alreadyDisplayedMissions.none { newMission.id == it.first.id }
|| (newMission.place?.address?.location?.latitude != null && newMission.place?.address?.location?.longitude != null)
}.map { newMission ->
// The icon resource of the marker to display
val markerIcon : Drawable = when(newMission.state)
{
MissionState.BOOKED -> markerMissionBooked
MissionState.AVAILABLE -> markerMissionAvailable
MissionState.PRE_RELEASED -> markerMissionPreReleased
else -> markerMissionOwned
}
// The icon of the marker to display .
val markerBitmap : Bitmap = mapUtils.createMissionMarkerIcon(markerIcon, newMission.costing?.price.toCurrency(requireContext()))
val markerLatitude : Double = newMission.place?.address?.location?.latitude!!
val markerLongitude : Double = newMission.place?.address?.location?.longitude!!
// The location of the marker to display.
val markerLatLng = LatLng(markerLatitude, markerLongitude)
val markerOptions : MarkerOptions = MarkerOptions()
.position(markerLatLng)
.icon(BitmapDescriptorFactory.fromBitmap(markerBitmap))
Pair(newMission, markerOptions)
}
lifecycleScope.launch(Dispatchers.Main) {
missionsToRemove.forEach { it.second.remove() }
missionsToShow.forEach {
val marker : Marker? = googleMap?.addMarker(it.second)
marker?.tag = it.first
alreadyDisplayedMissions.add(Pair(it.first, marker!!))
}
}
}
}
What's wrong in my function and how to solve those exceptions?
CodePudding user response:
I assume the java.util.ConcurrentModificationException
exception occurs for alreadyDisplayedMissions
list. You can try to use a Job
instance, returned by launch
function. You can cancel the current job when the next one is started. In this case you should check whether the current job is active by using isActive
property or ensureActive
function:
private var job: Job? = null
private fun displayMissions(newMissions : List<MissionHeaderEntity>) {
job?.cancel()
lifecycleScope.launch(Dispatchers.Default) {
// use ensureActive() everywhere before using alreadyDisplayedMissions list
ensureActive()
...
}
}
ensureActive()
throws CancellationException
if the job is no longer active, so the execution will not be continued after this function.