Timeline rework : first version working for backward navigation (need more testing)

This commit is contained in:
ganfra
2019-03-15 19:27:56 +01:00
parent 820709d433
commit c12bc5e02d
23 changed files with 500 additions and 499 deletions

View File

@ -31,7 +31,6 @@ import im.vector.matrix.android.internal.util.PagingRequestHelper
import im.vector.matrix.android.testCoroutineDispatchers
import io.realm.Realm
import io.realm.RealmConfiguration
import org.amshove.kluent.shouldEqual
import org.junit.Before
import org.junit.Rule
import org.junit.Test

View File

@ -20,13 +20,16 @@ package im.vector.matrix.android.api.session.room.timeline
interface Timeline {
var listener: Timeline.Listener?
fun size(): Int
fun snapshot(): List<TimelineEvent>
fun paginate(direction: Direction, count: Int)
fun addListener(listener: Listener)
fun removeListener(listener: Listener)
fun removeAllListeners()
fun start()
fun dispose()
interface Listener {
fun onUpdated(snapshot: List<TimelineEvent>)
}
enum class Direction(val value: String) {

View File

@ -27,6 +27,7 @@ import im.vector.matrix.android.api.session.room.model.RoomMember
data class TimelineEvent(
val root: Event,
val localId: String,
val displayIndex: Int,
val roomMember: RoomMember?
) {

View File

@ -16,22 +16,11 @@
package im.vector.matrix.android.api.session.room.timeline
import androidx.lifecycle.LiveData
/**
* This interface defines methods to interact with the timeline. It's implemented at the room level.
*/
interface TimelineService {
/**
* This is the main method of the service. It allows to listen for live [TimelineData].
* It's automatically refreshed as soon as timeline data gets updated, through sync or pagination.
*
* @param eventId: an optional eventId to start loading timeline around.
* @return the [LiveData] of [TimelineData]
*/
fun timeline(eventId: String? = null): LiveData<TimelineData>
fun createTimeline(eventId: String?): Timeline
}

View File

@ -26,6 +26,7 @@ import java.util.concurrent.atomic.AtomicReference
internal interface LiveEntityObserver {
fun start()
fun dispose()
fun isStarted(): Boolean
}
internal abstract class RealmLiveEntityObserver<T : RealmObject>(protected val monarchy: Monarchy)
@ -56,6 +57,10 @@ internal abstract class RealmLiveEntityObserver<T : RealmObject>(protected val m
}
}
override fun isStarted(): Boolean {
return isStarted.get()
}
protected open fun onChanged(realmResults: RealmResults<T>, changeSet: OrderedCollectionChangeSet) {
val insertionIndexes = changeSet.insertions
val updateIndexes = changeSet.changes

View File

@ -18,12 +18,13 @@ package im.vector.matrix.android.internal.database.model
import io.realm.RealmObject
import io.realm.RealmResults
import io.realm.annotations.Index
import io.realm.annotations.LinkingObjects
import io.realm.annotations.PrimaryKey
import java.util.*
internal open class EventEntity(@PrimaryKey var localId: String = UUID.randomUUID().toString(),
var eventId: String = "",
@Index var eventId: String = "",
var roomId: String = "",
var type: String = "",
var content: String? = null,

View File

@ -28,7 +28,7 @@ import im.vector.matrix.android.internal.session.room.send.EventFactory
import im.vector.matrix.android.internal.session.room.timeline.DefaultTimelineService
import im.vector.matrix.android.internal.session.room.timeline.GetContextOfEventTask
import im.vector.matrix.android.internal.session.room.timeline.PaginationTask
import im.vector.matrix.android.internal.session.room.timeline.TimelineBoundaryCallback
import im.vector.matrix.android.internal.session.room.timeline.TimelineEventFactory
import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.util.PagingRequestHelper
import java.util.concurrent.Executors
@ -44,9 +44,9 @@ internal class RoomFactory(private val loadRoomMembersTask: LoadRoomMembersTask,
fun instantiate(roomId: String): Room {
val helper = PagingRequestHelper(Executors.newSingleThreadExecutor())
val timelineBoundaryCallback = TimelineBoundaryCallback(roomId, taskExecutor, paginationTask, monarchy, helper)
val roomMemberExtractor = RoomMemberExtractor(monarchy, roomId)
val timelineService = DefaultTimelineService(roomId, monarchy, taskExecutor, timelineBoundaryCallback, contextOfEventTask, roomMemberExtractor)
val timelineEventFactory = TimelineEventFactory(roomMemberExtractor)
val timelineService = DefaultTimelineService(roomId, monarchy, taskExecutor, contextOfEventTask, timelineEventFactory, paginationTask, helper)
val sendService = DefaultSendService(roomId, eventFactory, monarchy)
val readService = DefaultReadService(roomId, monarchy, setReadMarkersTask, taskExecutor)
return DefaultRoom(

View File

@ -27,6 +27,7 @@ import im.vector.matrix.android.internal.session.room.RoomAPI
import im.vector.matrix.android.internal.session.sync.SyncTokenStore
import im.vector.matrix.android.internal.task.Task
import im.vector.matrix.android.internal.util.tryTransactionSync
import io.realm.kotlin.createObject
internal interface LoadRoomMembersTask : Task<LoadRoomMembersTask.Params, Boolean> {
@ -60,7 +61,7 @@ internal class DefaultLoadRoomMembersTask(private val roomAPI: RoomAPI,
.tryTransactionSync { realm ->
// We ignore all the already known members
val roomEntity = RoomEntity.where(realm, roomId).findFirst()
?: throw IllegalStateException("You shouldn't use this method without a room")
?: realm.createObject(roomId)
val roomMembers = RoomMembers(realm, roomId).getLoaded()
val eventsToInsert = response.roomMemberEvents.filter { !roomMembers.containsKey(it.stateKey) }
@ -73,9 +74,9 @@ internal class DefaultLoadRoomMembersTask(private val roomAPI: RoomAPI,
private fun areAllMembersAlreadyLoaded(roomId: String): Boolean {
return monarchy
.fetchAllCopiedSync { RoomEntity.where(it, roomId) }
.firstOrNull()
?.areAllMembersLoaded ?: false
.fetchAllCopiedSync { RoomEntity.where(it, roomId) }
.firstOrNull()
?.areAllMembersLoaded ?: false
}
}

View File

@ -18,20 +18,26 @@
package im.vector.matrix.android.internal.session.room.timeline
import com.zhuinden.monarchy.Monarchy
import androidx.annotation.UiThread
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.room.timeline.Timeline
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.matrix.android.internal.database.RealmLiveEntityObserver
import im.vector.matrix.android.internal.database.mapper.asDomain
import im.vector.matrix.android.internal.database.model.ChunkEntityFields
import im.vector.matrix.android.internal.database.model.EventEntity
import im.vector.matrix.android.internal.database.model.EventEntityFields
import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.session.room.members.RoomMemberExtractor
import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.task.configureWith
import im.vector.matrix.android.internal.util.PagingRequestHelper
import io.realm.OrderedCollectionChangeSet
import io.realm.OrderedRealmCollectionChangeListener
import io.realm.Realm
import io.realm.RealmConfiguration
import io.realm.RealmQuery
import io.realm.RealmResults
import io.realm.Sort
import java.util.*
import kotlin.collections.ArrayList
private const val INITIAL_LOAD_SIZE = 30
@ -39,103 +45,136 @@ private const val INITIAL_LOAD_SIZE = 30
internal class DefaultTimeline(
private val roomId: String,
private val initialEventId: String? = null,
private val monarchy: Monarchy,
private val realmConfiguration: RealmConfiguration,
private val taskExecutor: TaskExecutor,
private val boundaryCallback: TimelineBoundaryCallback,
private val contextOfEventTask: GetContextOfEventTask,
private val roomMemberExtractor: RoomMemberExtractor
private val timelineEventFactory: TimelineEventFactory,
private val paginationTask: PaginationTask,
private val helper: PagingRequestHelper
) : Timeline {
override var listener: Timeline.Listener? = null
private lateinit var realm: Realm
private lateinit var liveEvents: RealmResults<EventEntity>
private var prevDisplayIndex: Int = 0
private var nextDisplayIndex: Int = 0
private val isLive = initialEventId == null
private val builtEvents = Collections.synchronizedList<TimelineEvent>(ArrayList())
private val listeners = mutableListOf<Timeline.Listener>()
private val builtEvents = mutableListOf<TimelineEvent>()
private lateinit var liveResults: RealmResults<EventEntity>
private val entityObserver = object : RealmLiveEntityObserver<EventEntity>(monarchy) {
override val query: Monarchy.Query<EventEntity>
get() = buildQuery(initialEventId)
override fun onChanged(realmResults: RealmResults<EventEntity>, changeSet: OrderedCollectionChangeSet) {
private val changeListener = OrderedRealmCollectionChangeListener<RealmResults<EventEntity>> { _, changeSet ->
if (changeSet.state == OrderedCollectionChangeSet.State.INITIAL) {
handleInitialLoad()
} else {
changeSet.insertionRanges.forEach {
val (startIndex, direction) = if (it.startIndex == 0) {
Pair(realmResults[it.length]!!.displayIndex, Timeline.Direction.FORWARDS)
val (startDisplayIndex, direction) = if (it.startIndex == 0) {
Pair(liveEvents[it.length - 1]!!.displayIndex, Timeline.Direction.FORWARDS)
} else {
Pair(realmResults[it.startIndex]!!.displayIndex, Timeline.Direction.FORWARDS)
Pair(liveEvents[it.startIndex]!!.displayIndex, Timeline.Direction.BACKWARDS)
}
addFromLiveResults(startIndex, direction, it.length.toLong())
}
}
override fun processInitialResults(results: RealmResults<EventEntity>) {
// Results are ordered DESCENDING, so first items is the most recent
liveResults = results
val initialDisplayIndex = if (isLive) {
results.first()?.displayIndex
} else {
results.where().equalTo(EventEntityFields.EVENT_ID, initialEventId).findFirst()?.displayIndex
} ?: 0
prevDisplayIndex = initialDisplayIndex
nextDisplayIndex = initialDisplayIndex
val count = Math.min(INITIAL_LOAD_SIZE, results.size).toLong()
if (isLive) {
addFromLiveResults(initialDisplayIndex, Timeline.Direction.BACKWARDS, count)
} else {
val forwardCount = count / 2L
val backwardCount = count - forwardCount
addFromLiveResults(initialDisplayIndex, Timeline.Direction.BACKWARDS, backwardCount)
addFromLiveResults(initialDisplayIndex, Timeline.Direction.BACKWARDS, forwardCount)
insertFromLiveResults(startDisplayIndex, direction, it.length.toLong())
}
}
}
@UiThread
override fun paginate(direction: Timeline.Direction, count: Int) {
monarchy.postToMonarchyThread {
val startDisplayIndex = if (direction == Timeline.Direction.BACKWARDS) prevDisplayIndex else nextDisplayIndex
val shouldHitNetwork = addFromLiveResults(startDisplayIndex, direction, count.toLong()).not()
if (shouldHitNetwork) {
if (direction == Timeline.Direction.BACKWARDS) {
val itemAtEnd = builtEvents.last()
boundaryCallback.onItemAtEndLoaded(itemAtEnd)
} else {
val itemAtFront = builtEvents.first()
boundaryCallback.onItemAtFrontLoaded(itemAtFront)
}
if (direction == Timeline.Direction.FORWARDS && isLive) {
return
}
val startDisplayIndex = if (direction == Timeline.Direction.BACKWARDS) prevDisplayIndex else nextDisplayIndex
val hasBuiltCountItems = insertFromLiveResults(startDisplayIndex, direction, count.toLong())
if (hasBuiltCountItems.not()) {
val token = getToken(direction) ?: return
helper.runIfNotRunning(direction.toRequestType()) {
executePaginationTask(it, token, direction.toPaginationDirection(), 30)
}
}
}
override fun addListener(listener: Timeline.Listener) {
if (listeners.isEmpty()) {
entityObserver.start()
}
listeners.add(listener)
@UiThread
override fun start() {
realm = Realm.getInstance(realmConfiguration)
liveEvents = buildQuery(initialEventId).findAllAsync()
liveEvents.addChangeListener(changeListener)
}
override fun removeListener(listener: Timeline.Listener) {
listeners.remove(listener)
if (listeners.isEmpty()) {
entityObserver.dispose()
@UiThread
override fun dispose() {
liveEvents.removeAllChangeListeners()
realm.close()
}
override fun snapshot(): List<TimelineEvent> = synchronized(builtEvents) {
return builtEvents.toList()
}
override fun size(): Int = synchronized(builtEvents) {
return builtEvents.size
}
private fun handleInitialLoad() = synchronized(builtEvents) {
val initialDisplayIndex = if (isLive) {
liveEvents.firstOrNull()?.displayIndex
} else {
liveEvents.where().equalTo(EventEntityFields.EVENT_ID, initialEventId).findFirst()?.displayIndex
} ?: 0
prevDisplayIndex = initialDisplayIndex
nextDisplayIndex = initialDisplayIndex
val count = Math.min(INITIAL_LOAD_SIZE, liveEvents.size).toLong()
if (count == 0L) {
return@synchronized
}
if (isLive) {
insertFromLiveResults(initialDisplayIndex, Timeline.Direction.BACKWARDS, count)
} else {
val forwardCount = count / 2L
val backwardCount = count - forwardCount
insertFromLiveResults(initialDisplayIndex, Timeline.Direction.BACKWARDS, backwardCount)
insertFromLiveResults(initialDisplayIndex, Timeline.Direction.BACKWARDS, forwardCount)
}
}
override fun removeAllListeners() {
listeners.clear()
if (listeners.isEmpty()) {
entityObserver.dispose()
}
private fun executePaginationTask(requestCallback: PagingRequestHelper.Request.Callback,
from: String,
direction: PaginationDirection,
limit: Int) {
val params = PaginationTask.Params(roomId = roomId,
from = from,
direction = direction,
limit = limit)
paginationTask.configureWith(params)
.enableRetry()
.dispatchTo(object : MatrixCallback<Boolean> {
override fun onSuccess(data: Boolean) {
requestCallback.recordSuccess()
}
override fun onFailure(failure: Throwable) {
requestCallback.recordFailure(failure)
}
})
.executeBy(taskExecutor)
}
private fun getToken(direction: Timeline.Direction): String? {
val chunkEntity = liveEvents.firstOrNull()?.chunk?.firstOrNull() ?: return null
return if (direction == Timeline.Direction.BACKWARDS) chunkEntity.prevToken else chunkEntity.nextToken
}
/**
* This has to be called on MonarchyThread as it access realm live results
* @return true if count items has been added
*/
private fun addFromLiveResults(startDisplayIndex: Int,
direction: Timeline.Direction,
count: Long): Boolean {
private fun insertFromLiveResults(startDisplayIndex: Int,
direction: Timeline.Direction,
count: Long): Boolean = synchronized(builtEvents) {
if (count < 1) {
throw java.lang.IllegalStateException("You should provide a count superior to 0")
}
val offsetResults = getOffsetResults(startDisplayIndex, direction, count)
if (offsetResults.isEmpty()) {
return false
@ -147,18 +186,18 @@ internal class DefaultTimeline(
nextDisplayIndex = offsetIndex + 1
}
offsetResults.forEach { eventEntity ->
val roomMember = roomMemberExtractor.extractFrom(eventEntity)
val timelineEvent = TimelineEvent(eventEntity.asDomain(), eventEntity.localId, roomMember)
val timelineEvent = timelineEventFactory.create(eventEntity)
val position = if (direction == Timeline.Direction.FORWARDS) 0 else builtEvents.size
builtEvents.add(position, timelineEvent)
}
listener?.onUpdated(snapshot())
return offsetResults.size.toLong() == count
}
private fun getOffsetResults(startDisplayIndex: Int,
direction: Timeline.Direction,
count: Long): RealmResults<EventEntity> {
val offsetQuery = liveResults.where()
val offsetQuery = liveEvents.where()
if (direction == Timeline.Direction.BACKWARDS) {
offsetQuery
.sort(EventEntityFields.DISPLAY_INDEX, Sort.DESCENDING)
@ -171,19 +210,27 @@ internal class DefaultTimeline(
return offsetQuery.limit(count).findAll()
}
private fun buildQuery(eventId: String?): Monarchy.Query<EventEntity> {
return Monarchy.Query<EventEntity> { realm ->
val query = if (eventId == null) {
EventEntity
.where(realm, roomId = roomId, linkFilterMode = EventEntity.LinkFilterMode.LINKED_ONLY)
.equalTo("${EventEntityFields.CHUNK}.${ChunkEntityFields.IS_LAST}", true)
} else {
EventEntity
.where(realm, roomId = roomId, linkFilterMode = EventEntity.LinkFilterMode.BOTH)
.`in`("${EventEntityFields.CHUNK}.${ChunkEntityFields.EVENTS.EVENT_ID}", arrayOf(eventId))
}
query.sort(EventEntityFields.DISPLAY_INDEX, Sort.DESCENDING)
private fun buildQuery(eventId: String?): RealmQuery<EventEntity> {
val query = if (eventId == null) {
EventEntity
.where(realm, roomId = roomId, linkFilterMode = EventEntity.LinkFilterMode.LINKED_ONLY)
.equalTo("${EventEntityFields.CHUNK}.${ChunkEntityFields.IS_LAST}", true)
} else {
EventEntity
.where(realm, roomId = roomId, linkFilterMode = EventEntity.LinkFilterMode.BOTH)
.`in`("${EventEntityFields.CHUNK}.${ChunkEntityFields.EVENTS.EVENT_ID}", arrayOf(eventId))
}
query.sort(EventEntityFields.DISPLAY_INDEX, Sort.DESCENDING)
return query
}
private fun Timeline.Direction.toRequestType(): PagingRequestHelper.RequestType {
return if (this == Timeline.Direction.BACKWARDS) PagingRequestHelper.RequestType.BEFORE else PagingRequestHelper.RequestType.AFTER
}
//Todo : remove that
private fun Timeline.Direction.toPaginationDirection(): PaginationDirection {
return if (this == Timeline.Direction.BACKWARDS) PaginationDirection.BACKWARDS else PaginationDirection.FORWARDS
}
}

View File

@ -16,20 +16,17 @@
package im.vector.matrix.android.internal.session.room.timeline
import androidx.lifecycle.LiveData
import androidx.paging.LivePagedListBuilder
import androidx.paging.PagedList
import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.session.room.timeline.*
import im.vector.matrix.android.internal.database.mapper.asDomain
import im.vector.matrix.android.api.session.room.timeline.Timeline
import im.vector.matrix.android.api.session.room.timeline.TimelineEventInterceptor
import im.vector.matrix.android.api.session.room.timeline.TimelineService
import im.vector.matrix.android.internal.database.model.ChunkEntityFields
import im.vector.matrix.android.internal.database.model.EventEntity
import im.vector.matrix.android.internal.database.model.EventEntityFields
import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.session.room.members.RoomMemberExtractor
import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.task.configureWith
import im.vector.matrix.android.internal.util.LiveDataUtils
import im.vector.matrix.android.internal.util.PagingRequestHelper
import im.vector.matrix.android.internal.util.tryTransactionAsync
import io.realm.Realm
@ -43,42 +40,16 @@ private const val EVENT_NOT_FOUND_INDEX = -1
internal class DefaultTimelineService(private val roomId: String,
private val monarchy: Monarchy,
private val taskExecutor: TaskExecutor,
private val boundaryCallback: TimelineBoundaryCallback,
private val contextOfEventTask: GetContextOfEventTask,
private val roomMemberExtractor: RoomMemberExtractor
private val timelineEventFactory: TimelineEventFactory,
private val paginationTask: PaginationTask,
private val helper: PagingRequestHelper
) : TimelineService {
private val eventInterceptors = ArrayList<TimelineEventInterceptor>()
override fun timeline(eventId: String?): LiveData<TimelineData> {
clearUnlinkedEvents()
val initialLoadKey = getInitialLoadKey(eventId)
val realmDataSourceFactory = monarchy.createDataSourceFactory {
buildDataSourceFactoryQuery(it, eventId)
}
val domainSourceFactory = realmDataSourceFactory
.map { eventEntity ->
val roomMember = roomMemberExtractor.extractFrom(eventEntity)
TimelineEvent(eventEntity.asDomain(), eventEntity.localId, roomMember)
}
val pagedListConfig = buildPagedListConfig()
val livePagedListBuilder = LivePagedListBuilder(domainSourceFactory, pagedListConfig)
.setBoundaryCallback(boundaryCallback)
.setInitialLoadKey(initialLoadKey)
val eventsLiveData = monarchy.findAllPagedWithChanges(realmDataSourceFactory, livePagedListBuilder)
return LiveDataUtils.combine(eventsLiveData, boundaryCallback.status) { events, status ->
val isLoadingForward = status.before == PagingRequestHelper.Status.RUNNING
val isLoadingBackward = status.after == PagingRequestHelper.Status.RUNNING
TimelineData(events, isLoadingForward, isLoadingBackward)
}
}
override fun createTimeline(eventId: String?): Timeline {
return DefaultTimeline(roomId, eventId, monarchy, taskExecutor, boundaryCallback, contextOfEventTask, roomMemberExtractor)
return DefaultTimeline(roomId, eventId, monarchy.realmConfiguration, taskExecutor, contextOfEventTask, timelineEventFactory, paginationTask, helper)
}
// PRIVATE FUNCTIONS ***************************************************************************
@ -124,7 +95,8 @@ internal class DefaultTimelineService(private val roomId: String,
private fun indexOfEvent(eventId: String): Int {
var displayIndex = EVENT_NOT_FOUND_INDEX
monarchy.doWithRealm {
displayIndex = EventEntity.where(it, eventId = eventId).findFirst()?.displayIndex ?: EVENT_NOT_FOUND_INDEX
displayIndex = EventEntity.where(it, eventId = eventId).findFirst()?.displayIndex
?: EVENT_NOT_FOUND_INDEX
}
return displayIndex
}

View File

@ -0,0 +1,36 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.internal.session.room.timeline
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.matrix.android.internal.database.mapper.asDomain
import im.vector.matrix.android.internal.database.model.EventEntity
import im.vector.matrix.android.internal.session.room.members.RoomMemberExtractor
internal class TimelineEventFactory(private val roomMemberExtractor: RoomMemberExtractor) {
fun create(eventEntity: EventEntity): TimelineEvent {
val roomMember = roomMemberExtractor.extractFrom(eventEntity)
return TimelineEvent(
eventEntity.asDomain(),
eventEntity.localId,
eventEntity.displayIndex,
roomMember
)
}
}

View File

@ -18,7 +18,12 @@ package im.vector.matrix.android.internal.session.room.timeline
import arrow.core.Try
import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.internal.database.helper.*
import im.vector.matrix.android.internal.database.helper.addAll
import im.vector.matrix.android.internal.database.helper.addOrUpdate
import im.vector.matrix.android.internal.database.helper.addStateEvents
import im.vector.matrix.android.internal.database.helper.deleteOnCascade
import im.vector.matrix.android.internal.database.helper.isUnlinked
import im.vector.matrix.android.internal.database.helper.merge
import im.vector.matrix.android.internal.database.model.ChunkEntity
import im.vector.matrix.android.internal.database.model.RoomEntity
import im.vector.matrix.android.internal.database.query.create
@ -26,6 +31,7 @@ import im.vector.matrix.android.internal.database.query.find
import im.vector.matrix.android.internal.database.query.findAllIncludingEvents
import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.util.tryTransactionSync
import io.realm.kotlin.createObject
internal class TokenChunkEventPersistor(private val monarchy: Monarchy) {
@ -40,7 +46,7 @@ internal class TokenChunkEventPersistor(private val monarchy: Monarchy) {
return monarchy
.tryTransactionSync { realm ->
val roomEntity = RoomEntity.where(realm, roomId).findFirst()
?: throw IllegalStateException("You shouldn't use this method without a room")
?: realm.createObject(roomId)
val nextToken: String?
val prevToken: String?
@ -60,10 +66,10 @@ internal class TokenChunkEventPersistor(private val monarchy: Monarchy) {
var currentChunk = if (direction == PaginationDirection.FORWARDS) {
prevChunk?.apply { this.nextToken = nextToken }
?: ChunkEntity.create(realm, prevToken, nextToken)
?: ChunkEntity.create(realm, prevToken, nextToken)
} else {
nextChunk?.apply { this.prevToken = prevToken }
?: ChunkEntity.create(realm, prevToken, nextToken)
?: ChunkEntity.create(realm, prevToken, nextToken)
}
currentChunk.addAll(roomId, receivedChunk.events, direction, isUnlinked = currentChunk.isUnlinked())