Start to insert room summaries and listen for changes

This commit is contained in:
ganfra 2018-10-16 15:52:30 +02:00
parent b79d23ad24
commit 4904ac894e
29 changed files with 319 additions and 84 deletions

View File

@ -28,6 +28,8 @@ dependencies {
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'com.android.support.constraint:constraint-layout:1.1.3'

implementation 'com.jakewharton.timber:timber:4.7.1'

implementation "org.koin:koin-android:$koin_version"
implementation "org.koin:koin-android-scope:$koin_version"
implementation "org.koin:koin-android-viewmodel:$koin_version"

View File

@ -1,13 +1,18 @@
package im.vector.riotredesign

import android.app.Application
import im.vector.matrix.android.BuildConfig
import im.vector.riotredesign.core.di.AppModule
import org.koin.standalone.StandAloneContext.startKoin
import timber.log.Timber

class Riot : Application() {

override fun onCreate() {
super.onCreate()
if (BuildConfig.DEBUG) {
Timber.plant(Timber.DebugTree())
}
startKoin(listOf(AppModule(this)))
}


View File

@ -4,24 +4,22 @@ import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.view.View
import android.widget.Toast
import im.vector.matrix.android.api.Matrix
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.failure.Failure
import im.vector.matrix.android.internal.database.model.EventEntity
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
import im.vector.matrix.android.internal.events.sync.data.SyncResponse
import im.vector.riotredesign.R
import im.vector.riotredesign.core.platform.RiotActivity
import io.realm.RealmChangeListener
import io.realm.RealmResults
import kotlinx.android.synthetic.main.activity_home.*
import org.koin.android.ext.android.inject
import timber.log.Timber

class HomeActivity : RiotActivity() {

private val matrix by inject<Matrix>()
private val synchronizer = matrix.currentSession?.synchronizer()
private val realmHolder = matrix.currentSession?.realmInstanceHolder()
private val realmHolder = matrix.currentSession?.realmHolder()

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -33,25 +31,25 @@ class HomeActivity : RiotActivity() {
synchronizeButton.visibility = View.GONE
loadingView.visibility = View.VISIBLE
synchronizer?.synchronize(object : MatrixCallback<SyncResponse> {
override fun onSuccess(data: SyncResponse?) {
override fun onSuccess(data: SyncResponse) {
synchronizeButton.visibility = View.VISIBLE
loadingView.visibility = View.GONE
Timber.v("Sync successful")
}

override fun onFailure(failure: Failure) {
synchronizeButton.visibility = View.VISIBLE
loadingView.visibility = View.GONE
Toast.makeText(this@HomeActivity, failure.toString(), Toast.LENGTH_LONG).show()
Timber.e("Sync has failed : %s", failure.toString())
}
})
if (realmHolder != null) {
val results = realmHolder.realm.where(EventEntity::class.java).equalTo("chunk.room.roomId", "!UlckfcnwgLKswCmUbe:matrix.org").findAll()
results.addChangeListener(RealmChangeListener<RealmResults<EventEntity>> {
Toast.makeText(this@HomeActivity, "Room events data changed", Toast.LENGTH_LONG).show()
})
val results = realmHolder.instance.where(RoomSummaryEntity::class.java).findAll()
results.addChangeListener { summaries ->
Timber.v("Summaries updated")
}
}


}

companion object {

View File

@ -22,8 +22,8 @@ class LoginActivity : RiotActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_login)
authenticateButton.setOnClickListener { authenticate() }
checkActiveSessions()
authenticateButton.setOnClickListener { authenticate() }
}

private fun authenticate() {
@ -37,9 +37,8 @@ class LoginActivity : RiotActivity() {
.build()

authenticator.authenticate(homeServerConnectionConfig, login, password, object : MatrixCallback<Session> {
override fun onSuccess(data: Session?) {
matrix.currentSession = data
goToHomeScreen()
override fun onSuccess(data: Session) {
openSessionAndGoToHome(data)
}

override fun onFailure(failure: Failure) {
@ -51,12 +50,16 @@ class LoginActivity : RiotActivity() {

private fun checkActiveSessions() {
if (authenticator.hasActiveSessions()) {
matrix.currentSession = authenticator.getLastActiveSession()
goToHomeScreen()
val session = authenticator.getLastActiveSession()
session?.let {
openSessionAndGoToHome(it)
}
}
}

private fun goToHomeScreen() {
private fun openSessionAndGoToHome(session: Session) {
matrix.currentSession = session
session.open()
val intent = HomeActivity.newIntent(this)
startActivity(intent)
finish()

View File

@ -44,6 +44,7 @@ dependencies {
def arrow_version = "0.7.3"
def support_version = '28.0.0'
def moshi_version = '1.7.0'
def lifecycle_version = "1.1.1"

implementation fileTree(dir: 'libs', include: ['*.aar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
@ -51,6 +52,10 @@ dependencies {
implementation "com.android.support:appcompat-v7:$support_version"
implementation "com.android.support:recyclerview-v7:$support_version"

implementation "android.arch.lifecycle:extensions:$lifecycle_version"
kapt "android.arch.lifecycle:compiler:$lifecycle_version"


// Network
implementation 'com.squareup.retrofit2:retrofit:2.4.0'
implementation 'com.squareup.retrofit2:converter-moshi:2.4.0'

View File

@ -4,7 +4,7 @@ import im.vector.matrix.android.api.failure.Failure

interface MatrixCallback<in T> {

fun onSuccess(data: T?)
fun onSuccess(data: T)

fun onFailure(failure: Failure)


View File

@ -16,7 +16,7 @@ data class Event(
@Json(name = "sender") val sender: String? = null,
@Json(name = "state_key") val stateKey: String? = null,
@Json(name = "room_id") var roomId: String? = null,
@Json(name = "unsigned_data") val unsignedData: UnsignedData? = null
@Json(name = "unsigned") val unsignedData: UnsignedData? = null
) {

val contentAsJsonObject: JsonObject? by lazy {

View File

@ -5,7 +5,7 @@ import com.squareup.moshi.JsonClass

@JsonClass(generateAdapter = true)
data class UnsignedData(
@Json(name = "age") val age: Int,
@Json(name = "age") val age: Long?,
@Json(name = "redacted_because") val redactedEvent: Event? = null,
@Json(name = "transaction_id") val transactionId: String
@Json(name = "transaction_id") val transactionId: String? = null
)

View File

@ -0,0 +1,9 @@
package im.vector.matrix.android.api.rooms

import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass

@JsonClass(generateAdapter = true)
data class RoomNameContent(
@Json(name = "name") val name: String
)

View File

@ -0,0 +1,9 @@
package im.vector.matrix.android.api.rooms

import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass

@JsonClass(generateAdapter = true)
data class RoomTopicContent(
@Json(name = "topic") val topic: String
)

View File

@ -1,15 +1,20 @@
package im.vector.matrix.android.api.session

import im.vector.matrix.android.internal.database.RealmInstanceHolder
import android.support.annotation.MainThread
import im.vector.matrix.android.internal.database.SessionRealmHolder
import im.vector.matrix.android.internal.events.sync.Synchronizer

interface Session {

@MainThread
fun open()

fun synchronizer(): Synchronizer

// Visible for testing request directly. Will be deleted
fun realmInstanceHolder(): RealmInstanceHolder
fun realmHolder(): SessionRealmHolder

@MainThread
fun close()

}

View File

@ -1,11 +0,0 @@
package im.vector.matrix.android.internal.database

import io.realm.Realm
import io.realm.RealmConfiguration

class RealmInstanceHolder(realmConfiguration: RealmConfiguration) {

val realm = Realm.getInstance(realmConfiguration)


}

View File

@ -0,0 +1,25 @@
package im.vector.matrix.android.internal.database

import android.support.annotation.MainThread
import io.realm.Realm
import io.realm.RealmConfiguration

class SessionRealmHolder(private val realmConfiguration: RealmConfiguration
) {

lateinit var instance: Realm

@MainThread
fun open() {
instance = Realm.getInstance(realmConfiguration)
}

@MainThread
fun close() {
instance.close()
Realm.compactRealm(realmConfiguration)
}


}

View File

@ -2,6 +2,7 @@ package im.vector.matrix.android.internal.database.mapper

import com.squareup.moshi.Types
import im.vector.matrix.android.api.events.Event
import im.vector.matrix.android.api.events.UnsignedData
import im.vector.matrix.android.internal.database.model.EventEntity
import im.vector.matrix.android.internal.di.MoshiProvider

@ -12,14 +13,38 @@ object EventMapper {
private val type = Types.newParameterizedType(Map::class.java, String::class.java, Any::class.java)
private val adapter = moshi.adapter<Map<String, Any>>(type)

fun map(event: Event): EventEntity {
internal fun map(event: Event): EventEntity {
val eventEntity = EventEntity()
eventEntity.eventId = event.eventId!!
eventEntity.content = adapter.toJson(event.content)
eventEntity.prevContent = adapter.toJson(event.prevContent)
eventEntity.stateKey = event.stateKey
eventEntity.type = event.type
eventEntity.sender = event.sender
eventEntity.originServerTs = event.originServerTs
eventEntity.age = event.unsignedData?.age ?: event.originServerTs
return eventEntity
}

internal fun map(eventEntity: EventEntity): Event {
return Event(
eventEntity.type,
eventEntity.eventId,
adapter.fromJson(eventEntity.content),
adapter.fromJson(eventEntity.prevContent ?: ""),
eventEntity.originServerTs,
eventEntity.sender,
eventEntity.stateKey,
null,
UnsignedData(eventEntity.age)
)
}
}

fun EventEntity.asDomain(): Event {
return EventMapper.map(this)
}

fun Event.asEntity(): EventEntity {
return EventMapper.map(this)
}

View File

@ -9,9 +9,14 @@ open class EventEntity(@PrimaryKey var eventId: String = "",
var type: String = "",
var content: String = "",
var prevContent: String? = null,
var stateKey: String? = null
var stateKey: String? = null,
var originServerTs: Long? = null,
var sender: String? = null,
var age: Long? = 0
) : RealmObject() {

companion object

@LinkingObjects("events")
val chunk: RealmResults<ChunkEntity>? = null


View File

@ -6,10 +6,10 @@ import io.realm.annotations.Ignore
import io.realm.annotations.PrimaryKey
import kotlin.properties.Delegates

open class RoomEntity : RealmObject() {
open class RoomEntity(@PrimaryKey var roomId: String = "",
var chunks: RealmList<ChunkEntity> = RealmList()
) : RealmObject() {

@PrimaryKey var roomId: String = ""
var chunks: RealmList<ChunkEntity> = RealmList()
private var membershipStr: String = Membership.NONE.name

@delegate:Ignore var membership: Membership by Delegates.observable(Membership.valueOf(membershipStr)) { _, _, newValue ->

View File

@ -0,0 +1,11 @@
package im.vector.matrix.android.internal.database.model

import io.realm.RealmObject
import io.realm.annotations.PrimaryKey

// TODO to be completed
open class RoomSummaryEntity(@PrimaryKey var roomId: String = "",
var displayName: String? = "",
var topic: String? = "",
var lastMessage: EventEntity? = null
) : RealmObject()

View File

@ -0,0 +1,20 @@
package im.vector.matrix.android.internal.database.query

import im.vector.matrix.android.internal.database.model.ChunkEntity
import io.realm.Realm

fun ChunkEntity.Companion.getLastChunkFromRoom(realm: Realm, roomId: String): ChunkEntity? {
return realm.where(ChunkEntity::class.java)
.equalTo("room.roomId", roomId)
.isNull("nextToken")
.and()
.isNotNull("prevToken")
.findAll()
.lastOrNull()
}

fun ChunkEntity.Companion.getChunkIncludingEvents(realm: Realm, eventIds: List<String>): ChunkEntity? {
return realm.where(ChunkEntity::class.java)
.`in`("events.eventId", eventIds.toTypedArray())
.findFirst()
}

View File

@ -0,0 +1,19 @@
package im.vector.matrix.android.internal.database.query

import im.vector.matrix.android.internal.database.model.EventEntity
import io.realm.Realm
import io.realm.RealmResults

fun EventEntity.Companion.getAllFromRoom(realm: Realm, roomId: String): RealmResults<EventEntity> {
return realm.where(EventEntity::class.java)
.equalTo("chunk.room.roomId", roomId)
.findAll()
}

fun RealmResults<EventEntity>.getLast(type: String? = null): EventEntity? {
var query = this.where()
if (type != null) {
query = query.equalTo("type", type)
}
return query.findAll().sort("age").lastOrNull()
}

View File

@ -2,6 +2,7 @@ package im.vector.matrix.android.internal.database.query

import im.vector.matrix.android.internal.database.model.RoomEntity
import io.realm.Realm
import io.realm.RealmResults

fun RoomEntity.Companion.getForId(realm: Realm, roomId: String): RoomEntity? {
return realm.where<RoomEntity>(RoomEntity::class.java)
@ -9,10 +10,10 @@ fun RoomEntity.Companion.getForId(realm: Realm, roomId: String): RoomEntity? {
.findFirst()
}

fun RoomEntity.Companion.getAll(realm: Realm, membership: RoomEntity.Membership? = null): List<RoomEntity> {
fun RoomEntity.Companion.getAllAsync(realm: Realm, membership: RoomEntity.Membership? = null): RealmResults<RoomEntity> {
val query = realm.where(RoomEntity::class.java)
if (membership != null) {
query.equalTo("membership", membership.name)
}
return query.findAll()
return query.findAllAsync()
}

View File

@ -3,7 +3,6 @@ package im.vector.matrix.android.internal.di
import im.vector.matrix.android.api.MatrixOptions
import im.vector.matrix.android.api.thread.MainThreadExecutor
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import io.realm.Realm
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.IO
import kotlinx.coroutines.asCoroutineDispatcher

View File

@ -1,9 +1,7 @@
package im.vector.matrix.android.internal.di

import com.jakewharton.retrofit2.adapter.kotlin.coroutines.CoroutineCallAdapterFactory
import com.squareup.moshi.Moshi
import im.vector.matrix.android.internal.network.AccessTokenInterceptor
import im.vector.matrix.android.internal.network.parsing.UriMoshiAdapter
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import org.koin.dsl.context.ModuleDefinition

View File

@ -1,11 +1,12 @@
package im.vector.matrix.android.internal.di

import im.vector.matrix.android.internal.auth.data.SessionParams
import im.vector.matrix.android.internal.database.RealmInstanceHolder
import im.vector.matrix.android.internal.database.SessionRealmHolder
import im.vector.matrix.android.internal.legacy.MXDataHandler
import im.vector.matrix.android.internal.legacy.MXSession
import im.vector.matrix.android.internal.legacy.data.store.MXFileStore
import im.vector.matrix.android.internal.session.DefaultSession
import im.vector.matrix.android.internal.session.RoomSummaryObserver
import io.realm.RealmConfiguration
import org.koin.dsl.context.ModuleDefinition
import org.koin.dsl.module.Module
@ -21,7 +22,7 @@ class SessionModule(private val sessionParams: SessionParams) : Module {
}

scope(DefaultSession.SCOPE) {
RealmInstanceHolder(get())
SessionRealmHolder(get())
}

scope(DefaultSession.SCOPE) {
@ -31,6 +32,10 @@ class SessionModule(private val sessionParams: SessionParams) : Module {
.build()
}

scope(DefaultSession.SCOPE) {
RoomSummaryObserver(get(), get(), get())
}

scope(DefaultSession.SCOPE) {
val store = MXFileStore(sessionParams.credentials, false, get())
val dataHandler = MXDataHandler(store, sessionParams.credentials)

View File

@ -1,10 +1,12 @@
package im.vector.matrix.android.internal.events.sync

import im.vector.matrix.android.api.events.Event
import im.vector.matrix.android.internal.database.mapper.EventMapper
import im.vector.matrix.android.internal.database.mapper.asEntity
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.getChunkIncludingEvents
import im.vector.matrix.android.internal.database.query.getForId
import im.vector.matrix.android.internal.database.query.getLastChunkFromRoom
import im.vector.matrix.android.internal.events.sync.data.InvitedRoomSync
import im.vector.matrix.android.internal.events.sync.data.RoomSync
import io.realm.Realm
@ -39,8 +41,7 @@ class RoomSyncHandler(private val realmConfiguration: RealmConfiguration) {
roomId: String,
roomSync: RoomSync): RoomEntity {

val roomEntity = RoomEntity.getForId(realm, roomId)
?: RoomEntity().apply { this.roomId = roomId }
val roomEntity = RoomEntity.getForId(realm, roomId) ?: RoomEntity(roomId)

if (roomEntity.membership == RoomEntity.Membership.INVITED) {
roomEntity.chunks.deleteAllFromRealm()
@ -95,16 +96,16 @@ class RoomSyncHandler(private val realmConfiguration: RealmConfiguration) {
isLimited: Boolean = true): ChunkEntity {

val chunkEntity = if (!isLimited) {
realm.where(ChunkEntity::class.java).equalTo("room.roomId", roomId).isNull("nextToken").and().isNotNull("prevToken").findAll().lastOrNull()
ChunkEntity.getLastChunkFromRoom(realm, roomId)
} else {
realm.where(ChunkEntity::class.java).`in`("events.eventId", eventList.map { it.eventId }.toTypedArray()).findFirst()
} ?: ChunkEntity()
val eventIds = eventList.filter { it.eventId != null }.map { it.eventId!! }
ChunkEntity.getChunkIncludingEvents(realm, eventIds)
} ?: ChunkEntity().apply { this.prevToken = prevToken }

chunkEntity.prevToken = prevToken
chunkEntity.nextToken = nextToken
chunkEntity.isLimited = isLimited
eventList.forEach { event ->
val eventEntity = EventMapper.map(event).let {
val eventEntity = event.asEntity().let {
realm.copyToRealmOrUpdate(it)
}
if (!chunkEntity.events.contains(eventEntity)) {

View File

@ -2,6 +2,7 @@ package im.vector.matrix.android.internal.events.sync

import arrow.core.Either
import arrow.core.flatMap
import arrow.core.leftIfNull
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.failure.Failure
import im.vector.matrix.android.api.util.Cancelable
@ -42,6 +43,8 @@ class Synchronizer(private val syncAPI: SyncAPI,
params["filter"] = filterBody.toJSONString()
executeRequest<SyncResponse> {
apiCall = syncAPI.sync(params)
}.leftIfNull {
Failure.Unknown(RuntimeException("Sync response shouln't be null"))
}.flatMap {
token = it?.nextBatch
try {

View File

@ -32,7 +32,7 @@ class Request<DATA> {
} catch (e: Exception) {
when (e) {
is IOException -> Either.Left(Failure.NetworkConnection)
else -> Either.Left(Failure.Unknown(e))
else -> Either.Left(Failure.Unknown(e))
}
}
}

View File

@ -1,8 +1,10 @@
package im.vector.matrix.android.internal.session

import android.os.Looper
import android.support.annotation.MainThread
import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.internal.auth.data.SessionParams
import im.vector.matrix.android.internal.database.RealmInstanceHolder
import im.vector.matrix.android.internal.database.SessionRealmHolder
import im.vector.matrix.android.internal.di.SessionModule
import im.vector.matrix.android.internal.events.sync.SyncModule
import im.vector.matrix.android.internal.events.sync.Synchronizer
@ -12,34 +14,57 @@ import org.koin.standalone.StandAloneContext
import org.koin.standalone.getKoin
import org.koin.standalone.inject

class DefaultSession(sessionParams: SessionParams) : Session, KoinComponent {

private val realmInstanceHolder by inject<RealmInstanceHolder>()
private val synchronizer by inject<Synchronizer>()
private val scope: Scope

init {
val sessionModule = SessionModule(sessionParams)
val syncModule = SyncModule()
StandAloneContext.loadKoinModules(listOf(sessionModule, syncModule))
scope = getKoin().getOrCreateScope(SCOPE)
}

override fun synchronizer(): Synchronizer {
return synchronizer
}

override fun realmInstanceHolder(): RealmInstanceHolder {
return realmInstanceHolder
}

override fun close() {
realmInstanceHolder.realm.close()
scope.close()
}
class DefaultSession(private val sessionParams: SessionParams) : Session, KoinComponent {

companion object {
const val SCOPE: String = "session"
}

private lateinit var scope: Scope

private val realmInstanceHolder by inject<SessionRealmHolder>()
private val synchronizer by inject<Synchronizer>()
private val roomSummaryObserver by inject<RoomSummaryObserver>()

private var isOpen = false

@MainThread
override fun open() {
checkIsMainThread()
assert(!isOpen)
isOpen = true
val sessionModule = SessionModule(sessionParams)
val syncModule = SyncModule()
StandAloneContext.loadKoinModules(listOf(sessionModule, syncModule))
scope = getKoin().getOrCreateScope(SCOPE)
realmInstanceHolder.open()
roomSummaryObserver.start()
}

override fun synchronizer(): Synchronizer {
assert(isOpen)
return synchronizer
}

override fun realmHolder(): SessionRealmHolder {
assert(isOpen)
return realmInstanceHolder
}

@MainThread
override fun close() {
checkIsMainThread()
assert(isOpen)
roomSummaryObserver.dispose()
realmInstanceHolder.close()
scope.close()
isOpen = false
}

private fun checkIsMainThread() {
if (Looper.myLooper() != Looper.getMainLooper()) {
throw IllegalStateException("Should be called on main thread")
}
}

}

View File

@ -0,0 +1,73 @@
package im.vector.matrix.android.internal.session

import im.vector.matrix.android.api.events.EventType
import im.vector.matrix.android.api.rooms.RoomNameContent
import im.vector.matrix.android.api.rooms.RoomTopicContent
import im.vector.matrix.android.internal.database.SessionRealmHolder
import im.vector.matrix.android.internal.database.mapper.asDomain
import im.vector.matrix.android.internal.database.model.EventEntity
import im.vector.matrix.android.internal.database.model.RoomEntity
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
import im.vector.matrix.android.internal.database.query.getAllAsync
import im.vector.matrix.android.internal.database.query.getAllFromRoom
import im.vector.matrix.android.internal.database.query.getLast
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import io.realm.Realm
import io.realm.RealmConfiguration
import io.realm.RealmResults
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import java.util.concurrent.atomic.AtomicBoolean

internal class RoomSummaryObserver(private val mainThreadRealm: SessionRealmHolder,
private val matrixCoroutineDispatchers: MatrixCoroutineDispatchers,
private val realmConfiguration: RealmConfiguration
) {

private lateinit var roomResults: RealmResults<RoomEntity>
private var isStarted = AtomicBoolean(false)

fun start() {
if (isStarted.compareAndSet(false, true)) {
roomResults = RoomEntity.getAllAsync(mainThreadRealm.instance)
roomResults.addChangeListener { rooms, changeSet ->
manageRoomResults(rooms, changeSet.changes)
manageRoomResults(rooms, changeSet.insertions)
}
}
}

fun dispose() {
if (isStarted.compareAndSet(true, false)) {
roomResults.removeAllChangeListeners()
}
}

// PRIVATE

private fun manageRoomResults(rooms: RealmResults<RoomEntity>, indexes: IntArray) {
indexes.forEach {
val room = rooms[it]
if (room != null) {
manageRoom(room.roomId)
}
}
}

private fun manageRoom(roomId: String) = GlobalScope.launch(matrixCoroutineDispatchers.io) {
val realm = Realm.getInstance(realmConfiguration)
val roomEvents = EventEntity.getAllFromRoom(realm, roomId)
val lastNameEvent = roomEvents.getLast(EventType.STATE_ROOM_NAME)?.asDomain()
val lastTopicEvent = roomEvents.getLast(EventType.STATE_ROOM_TOPIC)?.asDomain()
val lastMessageEvent = roomEvents.getLast(EventType.MESSAGE)

realm.executeTransaction { realmInstance ->
val roomSummary = realmInstance.copyToRealmOrUpdate(RoomSummaryEntity(roomId))
roomSummary.displayName = lastNameEvent?.content<RoomNameContent>()?.name
roomSummary.topic = lastTopicEvent?.content<RoomTopicContent>()?.topic
roomSummary.lastMessage = lastMessageEvent
}
realm.close()
}

}