Skip to content

Commit 0b052a3

Browse files
committed
Refactor: Move message receipt logic to client
Moves the `MessageReceiptRepository` and related classes from the `stream-chat-android-offline` module to the `stream-chat-android-client` module. This refactoring centralizes persistence logic within the client module. The moved classes have been renamed and their package structure updated. Method names within the repository have been made more specific (e.g., `upsert` is now `upsertMessageReceipts`). The `MessageReceiptRepository` is now an internal interface with a companion object factory.
1 parent c92f85f commit 0b052a3

File tree

23 files changed

+316
-236
lines changed

23 files changed

+316
-236
lines changed

stream-chat-android-client/api/stream-chat-android-client.api

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2983,6 +2983,24 @@ public abstract interface class io/getstream/chat/android/client/persistance/rep
29832983
public abstract fun createRepositoryFactory (Lio/getstream/chat/android/models/User;)Lio/getstream/chat/android/client/persistance/repository/factory/RepositoryFactory;
29842984
}
29852985

2986+
public final class io/getstream/chat/android/client/persistence/db/ChatClientDatabase_Impl {
2987+
public static final field Companion Lio/getstream/chat/android/client/persistence/db/ChatClientDatabase$Companion;
2988+
public fun <init> ()V
2989+
public fun clearAllTables ()V
2990+
public fun getAutoMigrations (Ljava/util/Map;)Ljava/util/List;
2991+
public fun getRequiredAutoMigrationSpecs ()Ljava/util/Set;
2992+
public fun messageReceiptDao ()Lio/getstream/chat/android/client/persistence/db/dao/MessageReceiptDao;
2993+
}
2994+
2995+
public final class io/getstream/chat/android/client/persistence/db/dao/MessageReceiptDao_Impl : io/getstream/chat/android/client/persistence/db/dao/MessageReceiptDao {
2996+
public fun <init> (Landroidx/room/RoomDatabase;)V
2997+
public fun deleteAll (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
2998+
public fun deleteByMessageIds (Ljava/util/List;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
2999+
public static fun getRequiredConverters ()Ljava/util/List;
3000+
public fun selectAllByType (Ljava/lang/String;I)Lkotlinx/coroutines/flow/Flow;
3001+
public fun upsert (Ljava/util/List;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
3002+
}
3003+
29863004
public abstract interface class io/getstream/chat/android/client/plugin/Plugin : io/getstream/chat/android/client/plugin/DependencyResolver, io/getstream/chat/android/client/plugin/listeners/BlockUserListener, io/getstream/chat/android/client/plugin/listeners/ChannelMarkReadListener, io/getstream/chat/android/client/plugin/listeners/CreateChannelListener, io/getstream/chat/android/client/plugin/listeners/DeleteChannelListener, io/getstream/chat/android/client/plugin/listeners/DeleteMessageForMeListener, io/getstream/chat/android/client/plugin/listeners/DeleteMessageListener, io/getstream/chat/android/client/plugin/listeners/DeleteReactionListener, io/getstream/chat/android/client/plugin/listeners/DraftMessageListener, io/getstream/chat/android/client/plugin/listeners/EditMessageListener, io/getstream/chat/android/client/plugin/listeners/FetchCurrentUserListener, io/getstream/chat/android/client/plugin/listeners/GetMessageListener, io/getstream/chat/android/client/plugin/listeners/HideChannelListener, io/getstream/chat/android/client/plugin/listeners/LiveLocationListener, io/getstream/chat/android/client/plugin/listeners/MarkAllReadListener, io/getstream/chat/android/client/plugin/listeners/PushPreferencesListener, io/getstream/chat/android/client/plugin/listeners/QueryBlockedUsersListener, io/getstream/chat/android/client/plugin/listeners/QueryChannelListener, io/getstream/chat/android/client/plugin/listeners/QueryChannelsListener, io/getstream/chat/android/client/plugin/listeners/QueryMembersListener, io/getstream/chat/android/client/plugin/listeners/QueryThreadsListener, io/getstream/chat/android/client/plugin/listeners/SendAttachmentListener, io/getstream/chat/android/client/plugin/listeners/SendGiphyListener, io/getstream/chat/android/client/plugin/listeners/SendMessageListener, io/getstream/chat/android/client/plugin/listeners/SendReactionListener, io/getstream/chat/android/client/plugin/listeners/ShuffleGiphyListener, io/getstream/chat/android/client/plugin/listeners/ThreadQueryListener, io/getstream/chat/android/client/plugin/listeners/TypingEventListener, io/getstream/chat/android/client/plugin/listeners/UnblockUserListener {
29873005
public fun getErrorHandler ()Lio/getstream/chat/android/client/errorhandler/ErrorHandler;
29883006
public fun onAttachmentSendRequest (Ljava/lang/String;Ljava/lang/String;Lio/getstream/chat/android/models/Message;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;

stream-chat-android-client/src/main/java/io/getstream/chat/android/client/persistance/repository/MessageReceiptRepository.kt

Lines changed: 0 additions & 34 deletions
This file was deleted.
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Copyright (c) 2014-2025 Stream.io Inc. All rights reserved.
3+
*
4+
* Licensed under the Stream License;
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://github.com/GetStream/stream-chat-android/blob/main/LICENSE
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.getstream.chat.android.client.persistence.db.dao
18+
19+
import androidx.room.Dao
20+
import androidx.room.Insert
21+
import androidx.room.OnConflictStrategy
22+
import androidx.room.Query
23+
import io.getstream.chat.android.client.persistence.db.entity.MessageReceiptEntity
24+
import kotlinx.coroutines.flow.Flow
25+
26+
@Dao
27+
internal interface MessageReceiptDao {
28+
29+
@Insert(onConflict = OnConflictStrategy.REPLACE)
30+
suspend fun upsert(receipts: List<MessageReceiptEntity>)
31+
32+
@Query("SELECT * FROM message_receipt WHERE type = :type ORDER BY createdAt ASC LIMIT :limit")
33+
fun selectAllByType(type: String, limit: Int): Flow<List<MessageReceiptEntity>>
34+
35+
@Query("DELETE FROM message_receipt WHERE messageId IN (:messageIds)")
36+
suspend fun deleteByMessageIds(messageIds: List<String>)
37+
38+
@Query("DELETE FROM message_receipt")
39+
suspend fun deleteAll()
40+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
* Copyright (c) 2014-2025 Stream.io Inc. All rights reserved.
3+
*
4+
* Licensed under the Stream License;
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://github.com/GetStream/stream-chat-android/blob/main/LICENSE
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.getstream.chat.android.client.persistence.db.entity
18+
19+
import androidx.room.Entity
20+
import androidx.room.PrimaryKey
21+
import java.util.Date
22+
23+
@Entity(tableName = "message_receipt")
24+
internal data class MessageReceiptEntity(
25+
@PrimaryKey
26+
val messageId: String,
27+
val type: String,
28+
val createdAt: Date,
29+
val cid: String,
30+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* Copyright (c) 2014-2025 Stream.io Inc. All rights reserved.
3+
*
4+
* Licensed under the Stream License;
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://github.com/GetStream/stream-chat-android/blob/main/LICENSE
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.getstream.chat.android.client.persistence.repository
18+
19+
import io.getstream.chat.android.client.persistence.db.ChatClientDatabase
20+
import io.getstream.chat.android.client.receipts.MessageReceipt
21+
import kotlinx.coroutines.flow.Flow
22+
23+
internal interface MessageReceiptRepository {
24+
25+
companion object {
26+
operator fun invoke(database: ChatClientDatabase): MessageReceiptRepository =
27+
MessageReceiptRepositoryImpl(
28+
dao = database.messageReceiptDao(),
29+
)
30+
}
31+
32+
suspend fun upsertMessageReceipts(receipts: List<MessageReceipt>)
33+
34+
fun getAllMessageReceiptsByType(type: String, limit: Int): Flow<List<MessageReceipt>>
35+
36+
suspend fun deleteMessageReceiptsByMessageIds(messageIds: List<String>)
37+
38+
suspend fun clearMessageReceipts()
39+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
* Copyright (c) 2014-2025 Stream.io Inc. All rights reserved.
3+
*
4+
* Licensed under the Stream License;
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://github.com/GetStream/stream-chat-android/blob/main/LICENSE
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.getstream.chat.android.client.persistence.repository
18+
19+
import io.getstream.chat.android.client.persistence.db.dao.MessageReceiptDao
20+
import io.getstream.chat.android.client.persistence.db.entity.MessageReceiptEntity
21+
import io.getstream.chat.android.client.receipts.MessageReceipt
22+
import kotlinx.coroutines.flow.Flow
23+
import kotlinx.coroutines.flow.map
24+
25+
internal class MessageReceiptRepositoryImpl(
26+
private val dao: MessageReceiptDao,
27+
) : MessageReceiptRepository {
28+
29+
override suspend fun upsertMessageReceipts(receipts: List<MessageReceipt>) {
30+
dao.upsert(receipts.map(MessageReceipt::toEntity))
31+
}
32+
33+
override fun getAllMessageReceiptsByType(type: String, limit: Int): Flow<List<MessageReceipt>> =
34+
dao.selectAllByType(type, limit).map { receipts ->
35+
receipts.map(MessageReceiptEntity::toModel)
36+
}
37+
38+
override suspend fun deleteMessageReceiptsByMessageIds(messageIds: List<String>) {
39+
dao.deleteByMessageIds(messageIds)
40+
}
41+
42+
override suspend fun clearMessageReceipts() {
43+
dao.deleteAll()
44+
}
45+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* Copyright (c) 2014-2025 Stream.io Inc. All rights reserved.
3+
*
4+
* Licensed under the Stream License;
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://github.com/GetStream/stream-chat-android/blob/main/LICENSE
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.getstream.chat.android.client.persistence.repository
18+
19+
import io.getstream.chat.android.client.persistence.db.entity.MessageReceiptEntity
20+
import io.getstream.chat.android.client.receipts.MessageReceipt
21+
22+
internal fun MessageReceipt.toEntity() = MessageReceiptEntity(
23+
messageId = messageId,
24+
type = type,
25+
createdAt = createdAt,
26+
cid = cid,
27+
)
28+
29+
internal fun MessageReceiptEntity.toModel() = MessageReceipt(
30+
messageId = messageId,
31+
type = type,
32+
createdAt = createdAt,
33+
cid = cid,
34+
)

stream-chat-android-core/src/main/java/io/getstream/chat/android/models/MessageReceipt.kt renamed to stream-chat-android-client/src/main/java/io/getstream/chat/android/client/receipts/MessageReceipt.kt

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,19 +14,17 @@
1414
* limitations under the License.
1515
*/
1616

17-
package io.getstream.chat.android.models
17+
package io.getstream.chat.android.client.receipts
1818

19-
import androidx.compose.runtime.Immutable
2019
import java.util.Date
2120

22-
@Immutable
23-
public data class MessageReceipt(
24-
public val messageId: String,
25-
public val type: String,
26-
public val createdAt: Date,
27-
public val cid: String,
21+
internal data class MessageReceipt(
22+
val messageId: String,
23+
val type: String,
24+
val createdAt: Date,
25+
val cid: String,
2826
) {
29-
public companion object {
30-
public const val TYPE_DELIVERED: String = "delivered"
27+
companion object {
28+
const val TYPE_DELIVERY: String = "delivery"
3129
}
3230
}

stream-chat-android-client/src/main/java/io/getstream/chat/android/client/receipts/MessageReceiptManager.kt

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,9 @@
1616

1717
package io.getstream.chat.android.client.receipts
1818

19-
import io.getstream.chat.android.client.persistance.repository.MessageReceiptRepository
19+
import io.getstream.chat.android.client.persistence.repository.MessageReceiptRepository
2020
import io.getstream.chat.android.client.utils.message.isDeleted
2121
import io.getstream.chat.android.models.Message
22-
import io.getstream.chat.android.models.MessageReceipt
2322
import io.getstream.chat.android.models.MessageType
2423
import io.getstream.chat.android.models.User
2524
import io.getstream.chat.android.models.UserId
@@ -29,7 +28,8 @@ import kotlinx.coroutines.launch
2928
import java.util.Date
3029

3130
/**
32-
* Manages message delivery receipts: creating and storing them in the repository.
31+
* Manages message delivery receipts: creating and storing them in the repository
32+
* for later reporting to the server.
3333
*/
3434
internal class MessageReceiptManager(
3535
private val scope: CoroutineScope,
@@ -48,7 +48,7 @@ internal class MessageReceiptManager(
4848
}
4949

5050
// Check if delivery receipts are enabled for the current user
51-
if (!currentUser.isDeliveryReceiptsEnabled()) {
51+
if (!currentUser.isDeliveryReceiptsEnabled) {
5252
logger.w { "[markMessagesAsDelivered] Delivery receipts disabled for user ${currentUser.id}" }
5353
return
5454
}
@@ -71,7 +71,7 @@ internal class MessageReceiptManager(
7171

7272
scope.launch {
7373
val receipts = filteredMessages.map { message -> message.toDeliveryReceipt() }
74-
messageReceiptRepository.upsert(receipts)
74+
messageReceiptRepository.upsertMessageReceipts(receipts)
7575

7676
logger.d { "[markMessagesAsDelivered] ${filteredMessages.size} delivery receipts upserted" }
7777
}
@@ -103,6 +103,3 @@ internal class MessageReceiptManager(
103103
cid = cid,
104104
)
105105
}
106-
107-
private fun User.isDeliveryReceiptsEnabled(): Boolean =
108-
privacySettings?.deliveryReceipts?.enabled ?: false

stream-chat-android-client/src/main/java/io/getstream/chat/android/client/receipts/MessageReceiptReporter.kt

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,8 @@
1717
package io.getstream.chat.android.client.receipts
1818

1919
import io.getstream.chat.android.client.ChatClient
20-
import io.getstream.chat.android.client.persistance.repository.MessageReceiptRepository
20+
import io.getstream.chat.android.client.persistence.repository.MessageReceiptRepository
2121
import io.getstream.chat.android.models.Message
22-
import io.getstream.chat.android.models.MessageReceipt
2322
import io.getstream.log.taggedLogger
2423
import io.getstream.result.onSuccessSuspend
2524
import kotlinx.coroutines.CoroutineScope
@@ -44,7 +43,11 @@ internal class MessageReceiptReporter(
4443

4544
@OptIn(FlowPreview::class)
4645
fun init() {
47-
messageReceiptRepository.getAllByType(type = MessageReceipt.TYPE_DELIVERY, limit = MAX_BATCH_SIZE)
46+
logger.d { "[init] Handling message receipts reporting…" }
47+
messageReceiptRepository.getAllMessageReceiptsByType(
48+
type = MessageReceipt.TYPE_DELIVERY,
49+
limit = MAX_BATCH_SIZE,
50+
)
4851
.sample(REPORT_INTERVAL_IN_MS)
4952
.filterNot(List<MessageReceipt>::isEmpty)
5053
.map { receipts ->
@@ -60,8 +63,15 @@ internal class MessageReceiptReporter(
6063
chatClient.markMessagesAsDelivered(messages)
6164
.execute()
6265
.onSuccessSuspend {
66+
logger.d { "[init] Successfully reported delivery receipts for ${messages.size} messages" }
6367
val deliveredMessageIds = messages.map(Message::id)
64-
messageReceiptRepository.deleteByMessageIds(deliveredMessageIds)
68+
messageReceiptRepository.deleteMessageReceiptsByMessageIds(deliveredMessageIds)
69+
}
70+
.onError { error ->
71+
logger.e {
72+
"[init] Failed to report delivery receipts for ${messages.size} messages: " +
73+
error.message
74+
}
6575
}
6676
}
6777
.launchIn(scope)

0 commit comments

Comments
 (0)