Fix ConcurrentModificationException

This commit is contained in:
Benoit Marty 2019-06-20 17:03:14 +02:00
parent 3e97503220
commit 401f878a9c

View File

@ -88,39 +88,43 @@ class TimelineEventController(private val dateFormatter: TimelineDateFormatter,


private val listUpdateCallback = object : ListUpdateCallback { private val listUpdateCallback = object : ListUpdateCallback {


@Synchronized
override fun onChanged(position: Int, count: Int, payload: Any?) { override fun onChanged(position: Int, count: Int, payload: Any?) {
assertUpdateCallbacksAllowed() synchronized(modelCache) {
(position until (position + count)).forEach { assertUpdateCallbacksAllowed()
modelCache[it] = null (position until (position + count)).forEach {
modelCache[it] = null
}
requestModelBuild()
} }
requestModelBuild()
} }


@Synchronized
override fun onMoved(fromPosition: Int, toPosition: Int) { override fun onMoved(fromPosition: Int, toPosition: Int) {
assertUpdateCallbacksAllowed() synchronized(modelCache) {
val model = modelCache.removeAt(fromPosition) assertUpdateCallbacksAllowed()
modelCache.add(toPosition, model) val model = modelCache.removeAt(fromPosition)
requestModelBuild() modelCache.add(toPosition, model)
requestModelBuild()
}
} }


@Synchronized
override fun onInserted(position: Int, count: Int) { override fun onInserted(position: Int, count: Int) {
assertUpdateCallbacksAllowed() synchronized(modelCache) {
(0 until count).forEach { assertUpdateCallbacksAllowed()
modelCache.add(position, null) (0 until count).forEach {
modelCache.add(position, null)
}
requestModelBuild()
} }
requestModelBuild()
} }


@Synchronized
override fun onRemoved(position: Int, count: Int) { override fun onRemoved(position: Int, count: Int) {
assertUpdateCallbacksAllowed() synchronized(modelCache) {
(0 until count).forEach { assertUpdateCallbacksAllowed()
modelCache.removeAt(position) (0 until count).forEach {
modelCache.removeAt(position)
}
requestModelBuild()
} }
requestModelBuild()
} }
} }


@ -134,17 +138,21 @@ class TimelineEventController(private val dateFormatter: TimelineDateFormatter,
this.timeline?.listener = this this.timeline?.listener = this


// Clear cache // Clear cache
for (i in 0 until modelCache.size) { synchronized(modelCache) {
modelCache[i] = null for (i in 0 until modelCache.size) {
modelCache[i] = null
}
} }
} }


if (this.eventIdToHighlight != eventIdToHighlight) { if (this.eventIdToHighlight != eventIdToHighlight) {
// Clear cache to force a refresh // Clear cache to force a refresh
for (i in 0 until modelCache.size) { synchronized(modelCache) {
if (modelCache[i]?.eventId == eventIdToHighlight for (i in 0 until modelCache.size) {
|| modelCache[i]?.eventId == this.eventIdToHighlight) { if (modelCache[i]?.eventId == eventIdToHighlight
modelCache[i] = null || modelCache[i]?.eventId == this.eventIdToHighlight) {
modelCache[i] = null
}
} }
} }
this.eventIdToHighlight = eventIdToHighlight this.eventIdToHighlight = eventIdToHighlight
@ -194,28 +202,29 @@ class TimelineEventController(private val dateFormatter: TimelineDateFormatter,
require(inSubmitList || Looper.myLooper() == backgroundHandler.looper) require(inSubmitList || Looper.myLooper() == backgroundHandler.looper)
} }


@Synchronized
private fun getModels(): List<EpoxyModel<*>> { private fun getModels(): List<EpoxyModel<*>> {
(0 until modelCache.size).forEach { position -> synchronized(modelCache) {
// Should be build if not cached or if cached but contains mergedHeader or formattedDay (0 until modelCache.size).forEach { position ->
// We then are sure we always have items up to date. // Should be build if not cached or if cached but contains mergedHeader or formattedDay
if (modelCache[position] == null // We then are sure we always have items up to date.
|| modelCache[position]?.mergedHeaderModel != null if (modelCache[position] == null
|| modelCache[position]?.formattedDayModel != null) { || modelCache[position]?.mergedHeaderModel != null
modelCache[position] = buildItemModels(position, currentSnapshot) || modelCache[position]?.formattedDayModel != null) {
} modelCache[position] = buildItemModels(position, currentSnapshot)
}
return modelCache
.map {
val eventModel = if (it == null || collapsedEventIds.contains(it.localId)) {
null
} else {
it.eventModel
}
listOf(eventModel, it?.mergedHeaderModel, it?.formattedDayModel)
} }
.flatten() }
.filterNotNull() return modelCache
.map {
val eventModel = if (it == null || collapsedEventIds.contains(it.localId)) {
null
} else {
it.eventModel
}
listOf(eventModel, it?.mergedHeaderModel, it?.formattedDayModel)
}
.flatten()
.filterNotNull()
}
} }




@ -296,16 +305,17 @@ class TimelineEventController(private val dateFormatter: TimelineDateFormatter,
} }


fun searchPositionOfEvent(eventId: String): Int? { fun searchPositionOfEvent(eventId: String): Int? {
// Search in the cache synchronized(modelCache) {
modelCache.forEachIndexed { idx, cacheItemData -> // Search in the cache
if (cacheItemData?.eventId == eventId) { modelCache.forEachIndexed { idx, cacheItemData ->
return idx if (cacheItemData?.eventId == eventId) {
return idx
}
} }

return null
} }

return null
} }

} }


private data class CacheItemData( private data class CacheItemData(