Fix timelines not loading after marking a room as read from notification when app was killed (#7202)

This can only happen when the app was killed by the OS and then you received some notification.

This notification action creates a new `DefaultSessionPreferencesStore` when trying to fetch `isSendPublicReadReceiptsEnabled`, which would contain a different session coroutine scope than the one created when instantiating the navigation graph (`@SessionCoroutineScope`), resulting in flows that never emit any items and close automatically as 'completed'.

This in turn prevented the timeline items subscription in `TimelinePresenter` from progressing, since we're also checking `isRenderReadReceiptsEnabled` there, which is closed and in turn closes the timeline items flow.
This commit is contained in:
Jorge Martin Espinosa
2026-07-15 15:01:35 +02:00
committed by GitHub
parent f9513193e2
commit d0b4a21cfe
2 changed files with 15 additions and 3 deletions
@@ -15,7 +15,7 @@ import io.element.android.libraries.matrix.api.MatrixClient
@ContributesBinding(AppScope::class)
class DefaultSessionGraphFactory(
private val appGraph: AppGraph
private val appGraph: AppGraph,
) : SessionGraphFactory {
override fun create(client: MatrixClient): Any {
return appGraph.sessionGraphFactory.create(client)
@@ -105,13 +105,21 @@ class NotificationBroadcastReceiverHandler(
@Suppress("unused")
private fun handleMarkAsRead(sessionId: SessionId, roomId: RoomId, threadId: ThreadId?) = appCoroutineScope.launch {
val client = matrixClientProvider.getOrRestore(sessionId).getOrNull() ?: return@launch
val isSendPublicReadReceiptsEnabled = sessionPreferencesStore.get(sessionId, this).isSendPublicReadReceiptsEnabled().first()
val isSendPublicReadReceiptsEnabled = sessionPreferencesStore.get(
sessionId = sessionId,
// Make sure `sessionCoroutineScope` is used here, otherwise we'll have several instances using different coroutines,
// and it will not work as expected.
sessionCoroutineScope = client.sessionCoroutineScope,
).isSendPublicReadReceiptsEnabled().first()
val receiptType = if (isSendPublicReadReceiptsEnabled) {
ReceiptType.READ
} else {
ReceiptType.READ_PRIVATE
}
val room = client.getJoinedRoom(roomId) ?: return@launch
val (room, needsDestroy) = activeRoomsHolder.getActiveRoomMatching(sessionId, roomId)?.let { it to false }
?: client.getJoinedRoom(roomId)?.let { it to true }
?: return@launch
val timeline = if (threadId != null) {
room.createTimeline(CreateTimelineParams.Threaded(threadId)).getOrNull()
} else {
@@ -131,6 +139,10 @@ class NotificationBroadcastReceiverHandler(
if (timeline?.mode != Timeline.Mode.Live) {
timeline?.close()
}
if (needsDestroy) {
room.destroy()
}
}
private fun handleSmartReply(