Browse Source

RoomList: avoid to many recompositions

feature/bma/flipper
ganfra 2 years ago
parent
commit
fcf7e8d7f1
  1. 40
      features/roomlist/src/main/java/io/element/android/x/features/roomlist/RoomListScreen.kt
  2. 5
      features/roomlist/src/main/java/io/element/android/x/features/roomlist/RoomListViewModel.kt
  3. 3
      features/roomlist/src/main/java/io/element/android/x/features/roomlist/RoomListViewState.kt
  4. 5
      features/roomlist/src/main/java/io/element/android/x/features/roomlist/model/MatrixUser.kt
  5. 22
      libraries/core/src/main/java/io/element/android/x/core/data/LogCompositions.kt
  6. 7
      libraries/matrix/src/main/java/io/element/android/x/matrix/MatrixClient.kt
  7. 1
      libraries/matrix/src/main/java/io/element/android/x/matrix/room/RoomSummary.kt
  8. 46
      libraries/matrix/src/main/java/io/element/android/x/matrix/room/RoomSummaryDataSource.kt

40
features/roomlist/src/main/java/io/element/android/x/features/roomlist/RoomListScreen.kt

@ -16,53 +16,57 @@ import androidx.compose.ui.unit.sp
import com.airbnb.mvrx.Success import com.airbnb.mvrx.Success
import com.airbnb.mvrx.compose.collectAsState import com.airbnb.mvrx.compose.collectAsState
import com.airbnb.mvrx.compose.mavericksViewModel import com.airbnb.mvrx.compose.mavericksViewModel
import io.element.android.x.core.data.LogCompositions
import io.element.android.x.designsystem.components.Avatar import io.element.android.x.designsystem.components.Avatar
import io.element.android.x.features.roomlist.model.MatrixUser
import io.element.android.x.matrix.core.RoomId import io.element.android.x.matrix.core.RoomId
import io.element.android.x.matrix.room.RoomSummary import io.element.android.x.matrix.room.RoomSummary
@Composable @Composable
fun RoomListScreen( fun RoomListScreen(
viewModel: RoomListViewModel = mavericksViewModel(),
onSuccessLogout: () -> Unit = { }, onSuccessLogout: () -> Unit = { },
onRoomClicked: (RoomId) -> Unit = { } onRoomClicked: (RoomId) -> Unit = { }
) { ) {
val state by viewModel.collectAsState() val viewModel: RoomListViewModel = mavericksViewModel()
if (state.logoutAction is Success) { val logoutAction by viewModel.collectAsState(RoomListViewState::logoutAction)
if (logoutAction is Success) {
onSuccessLogout() onSuccessLogout()
return return
} }
LogCompositions(tag = "RoomListScreen", msg = "Root")
val roomSummaries by viewModel.collectAsState(RoomListViewState::rooms)
val matrixUser by viewModel.collectAsState(RoomListViewState::user)
RoomListContent( RoomListContent(
state = state, roomSummaries = roomSummaries().orEmpty(),
matrixUser = matrixUser,
onRoomClicked = onRoomClicked, onRoomClicked = onRoomClicked,
onLogoutClicked = { onLogoutClicked = viewModel::logout
viewModel.handle(RoomListActions.Logout)
}
) )
} }
@Composable @Composable
fun RoomListContent( fun RoomListContent(
state: RoomListViewState, roomSummaries: List<RoomSummary>,
matrixUser: MatrixUser,
onRoomClicked: (RoomId) -> Unit, onRoomClicked: (RoomId) -> Unit,
onLogoutClicked: () -> Unit, onLogoutClicked: () -> Unit,
) { ) {
LogCompositions(tag = "RoomListScreen", msg = "Content")
Surface(color = MaterialTheme.colorScheme.background) { Surface(color = MaterialTheme.colorScheme.background) {
Column( Column(
modifier = Modifier.fillMaxSize() modifier = Modifier.fillMaxSize()
) { ) {
RoomListTopBar( RoomListTopBar(
state = state, matrixUser = matrixUser,
onLogoutClicked = onLogoutClicked onLogoutClicked = onLogoutClicked
) )
val rooms = state.rooms LazyColumn {
if (rooms is Success) { items(roomSummaries, key = { it.identifier() }) { room ->
LazyColumn { RoomItem(room = room) {
items(rooms()) { room -> onRoomClicked(it)
RoomItem(room = room) {
onRoomClicked(it)
}
} }
} }
} }
} }
} }
@ -70,14 +74,14 @@ fun RoomListContent(
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun RoomListTopBar(state: RoomListViewState, onLogoutClicked: () -> Unit) { fun RoomListTopBar(matrixUser: MatrixUser, onLogoutClicked: () -> Unit) {
LogCompositions(tag = "RoomListScreen", msg = "TopBar")
TopAppBar( TopAppBar(
title = { title = {
Row( Row(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
val matrixUser = state.user
Avatar(data = matrixUser.avatarData) Avatar(data = matrixUser.avatarData)
Spacer(modifier = Modifier.width(8.dp)) Spacer(modifier = Modifier.width(8.dp))
Text("${matrixUser.username}") Text("${matrixUser.username}")

5
features/roomlist/src/main/java/io/element/android/x/features/roomlist/RoomListViewModel.kt

@ -4,6 +4,7 @@ import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.Loading import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.MavericksViewModel import com.airbnb.mvrx.MavericksViewModel
import com.airbnb.mvrx.Success import com.airbnb.mvrx.Success
import io.element.android.x.features.roomlist.model.MatrixUser
import io.element.android.x.matrix.MatrixClient import io.element.android.x.matrix.MatrixClient
import io.element.android.x.matrix.MatrixInstance import io.element.android.x.matrix.MatrixInstance
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -25,6 +26,10 @@ class RoomListViewModel(initialState: RoomListViewState) :
} }
} }
fun logout(){
handleLogout()
}
private fun handleInit() { private fun handleInit() {
viewModelScope.launch { viewModelScope.launch {
val client = getClient() val client = getClient()

3
features/roomlist/src/main/java/io/element/android/x/features/roomlist/RoomListViewState.kt

@ -3,9 +3,8 @@ package io.element.android.x.features.roomlist
import com.airbnb.mvrx.Async import com.airbnb.mvrx.Async
import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.MavericksState
import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.Uninitialized
import io.element.android.x.features.roomlist.model.MatrixUser
import io.element.android.x.matrix.room.RoomSummary import io.element.android.x.matrix.room.RoomSummary
import org.matrix.rustcomponents.sdk.Room
import org.matrix.rustcomponents.sdk.UpdateSummary
data class RoomListViewState( data class RoomListViewState(
val user: MatrixUser = MatrixUser(), val user: MatrixUser = MatrixUser(),

5
features/roomlist/src/main/java/io/element/android/x/features/roomlist/MatrixUser.kt → features/roomlist/src/main/java/io/element/android/x/features/roomlist/model/MatrixUser.kt

@ -1,5 +1,8 @@
package io.element.android.x.features.roomlist package io.element.android.x.features.roomlist.model
import androidx.compose.runtime.Stable
@Stable
data class MatrixUser( data class MatrixUser(
val username: String? = null, val username: String? = null,
val avatarUrl: String? = null, val avatarUrl: String? = null,

22
libraries/core/src/main/java/io/element/android/x/core/data/LogCompositions.kt

@ -0,0 +1,22 @@
package io.element.android.x.core.data
import android.util.Log
import androidx.compose.runtime.Composable
import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.remember
import io.element.android.x.core.BuildConfig
// Note the inline function below which ensures that this function is essentially
// copied at the call site to ensure that its logging only recompositions from the
// original call site.
@Composable
inline fun LogCompositions(tag: String, msg: String) {
if (BuildConfig.DEBUG) {
val ref = remember { Ref(0) }
SideEffect { ref.value++ }
Log.d(tag, "Compositions: $msg ${ref.value}")
}
}
class Ref(var value: Int)

7
libraries/matrix/src/main/java/io/element/android/x/matrix/MatrixClient.kt

@ -1,6 +1,5 @@
package io.element.android.x.matrix package io.element.android.x.matrix
import android.util.Log
import io.element.android.x.core.data.CoroutineDispatchers import io.element.android.x.core.data.CoroutineDispatchers
import io.element.android.x.matrix.core.UserId import io.element.android.x.matrix.core.UserId
import io.element.android.x.matrix.room.RoomSummaryDataSource import io.element.android.x.matrix.room.RoomSummaryDataSource
@ -33,13 +32,13 @@ class MatrixClient internal constructor(
private val slidingSyncObserver = object : SlidingSyncObserver { private val slidingSyncObserver = object : SlidingSyncObserver {
override fun didReceiveSyncUpdate(summary: UpdateSummary) { override fun didReceiveSyncUpdate(summary: UpdateSummary) {
Timber.v("didReceiveSyncUpdate=$summary") Timber.v("didReceiveSyncUpdate=$summary on Thread: ${Thread.currentThread()}")
roomSummaryDataSource.updateRoomsWithIdentifiers(summary.rooms) roomSummaryDataSource.updateRoomsWithIdentifiers(summary.rooms)
} }
} }
private val slidingSyncView = SlidingSyncViewBuilder() private val slidingSyncView = SlidingSyncViewBuilder()
.timelineLimit(limit = 10u) .timelineLimit(limit = 1u)
.requiredState(requiredState = listOf(RequiredState(key = "m.room.avatar", value = ""))) .requiredState(requiredState = listOf(RequiredState(key = "m.room.avatar", value = "")))
.name(name = "HomeScreenView") .name(name = "HomeScreenView")
.syncMode(mode = SlidingSyncMode.FULL_SYNC) .syncMode(mode = SlidingSyncMode.FULL_SYNC)
@ -65,6 +64,7 @@ class MatrixClient internal constructor(
} }
fun stopSync() { fun stopSync() {
roomSummaryDataSource.stopSync()
slidingSync.setObserver(null) slidingSync.setObserver(null)
slidingSyncObserverToken?.cancel() slidingSyncObserverToken?.cancel()
} }
@ -73,6 +73,7 @@ class MatrixClient internal constructor(
override fun close() { override fun close() {
stopSync() stopSync()
roomSummaryDataSource.close()
client.setDelegate(null) client.setDelegate(null)
} }

1
libraries/matrix/src/main/java/io/element/android/x/matrix/room/RoomSummary.kt

@ -2,6 +2,7 @@ package io.element.android.x.matrix.room
import io.element.android.x.matrix.core.RoomId import io.element.android.x.matrix.core.RoomId
sealed interface RoomSummary { sealed interface RoomSummary {
data class Empty(val identifier: String) : RoomSummary data class Empty(val identifier: String) : RoomSummary
data class Filled(val details: RoomSummaryDetails) : RoomSummary data class Filled(val details: RoomSummaryDetails) : RoomSummary

46
libraries/matrix/src/main/java/io/element/android/x/matrix/room/RoomSummaryDataSource.kt

@ -5,14 +5,14 @@ import io.element.android.x.matrix.core.RoomId
import io.element.android.x.matrix.room.message.RoomMessageFactory import io.element.android.x.matrix.room.message.RoomMessageFactory
import io.element.android.x.matrix.sync.roomListDiff import io.element.android.x.matrix.sync.roomListDiff
import io.element.android.x.matrix.sync.state import io.element.android.x.matrix.sync.state
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.cancelChildren
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.*
import kotlinx.coroutines.flow.onEach
import org.matrix.rustcomponents.sdk.* import org.matrix.rustcomponents.sdk.*
import timber.log.Timber
import java.io.Closeable
import java.util.* import java.util.*
interface RoomSummaryDataSource { interface RoomSummaryDataSource {
@ -24,7 +24,7 @@ internal class RustRoomSummaryDataSource(
private val slidingSyncView: SlidingSyncView, private val slidingSyncView: SlidingSyncView,
private val coroutineDispatchers: CoroutineDispatchers, private val coroutineDispatchers: CoroutineDispatchers,
private val roomMessageFactory: RoomMessageFactory = RoomMessageFactory(), private val roomMessageFactory: RoomMessageFactory = RoomMessageFactory(),
) : RoomSummaryDataSource { ) : RoomSummaryDataSource, Closeable {
private val coroutineScope = CoroutineScope(SupervisorJob() + coroutineDispatchers.io) private val coroutineScope = CoroutineScope(SupervisorJob() + coroutineDispatchers.io)
@ -33,6 +33,7 @@ internal class RustRoomSummaryDataSource(
init { init {
slidingSyncView.roomListDiff() slidingSyncView.roomListDiff()
.buffer(50)
.onEach { diff -> .onEach { diff ->
updateRoomSummaries { updateRoomSummaries {
applyDiff(diff) applyDiff(diff)
@ -40,32 +41,43 @@ internal class RustRoomSummaryDataSource(
}.launchIn(coroutineScope) }.launchIn(coroutineScope)
slidingSyncView.state() slidingSyncView.state()
.onEach { newRoomState -> .onEach { slidingSyncState ->
state.value = newRoomState Timber.v("New sliding sync state: $slidingSyncState")
state.value = slidingSyncState
}.launchIn(coroutineScope) }.launchIn(coroutineScope)
} }
fun stopSync() {
coroutineScope.coroutineContext.cancelChildren()
}
override fun close() {
coroutineScope.cancel()
}
override fun roomSummaries(): Flow<List<RoomSummary>> { override fun roomSummaries(): Flow<List<RoomSummary>> {
return roomSummaries return roomSummaries.sample(100)
} }
internal fun updateRoomsWithIdentifiers(identifiers: List<String>) { internal fun updateRoomsWithIdentifiers(identifiers: List<String>) {
Timber.v("UpdateRooms with identifiers: $identifiers")
if (state.value != SlidingSyncState.LIVE) { if (state.value != SlidingSyncState.LIVE) {
return return
} }
val roomSummaryList = roomSummaries.value.toMutableList() updateRoomSummaries {
for (identifier in identifiers) { for (identifier in identifiers) {
val index = roomSummaryList.indexOfFirst { it.identifier() == identifier } val index = indexOfFirst { it.identifier() == identifier }
if (index == -1) { if (index == -1) {
continue continue
}
val updatedRoomSummary = buildRoomSummaryForIdentifier(identifier)
set(index, updatedRoomSummary)
} }
val updatedRoomSummary = buildRoomSummaryForIdentifier(identifier)
roomSummaryList[index] = updatedRoomSummary
} }
roomSummaries.value = roomSummaryList
} }
private fun MutableList<RoomSummary>.applyDiff(diff: SlidingSyncViewRoomsListDiff) { private fun MutableList<RoomSummary>.applyDiff(diff: SlidingSyncViewRoomsListDiff) {
Timber.v("ApplyDiff: $diff")
if (diff.isInvalidation()) { if (diff.isInvalidation()) {
return return
} }

Loading…
Cancel
Save