Skip to content

Commit e71c17a

Browse files
committed
Expose markMessageAsDelivered and allow custom handling of incoming push messages before they are processed by the SDK.
1 parent 0a0f08c commit e71c17a

File tree

9 files changed

+76
-32
lines changed

9 files changed

+76
-32
lines changed

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ public final class io/getstream/chat/android/client/ChatClient {
113113
public final fun keystroke (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lio/getstream/result/call/Call;
114114
public static synthetic fun keystroke$default (Lio/getstream/chat/android/client/ChatClient;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lio/getstream/result/call/Call;
115115
public final fun markAllRead ()Lio/getstream/result/call/Call;
116+
public final fun markMessageAsDelivered (Ljava/lang/String;)Lio/getstream/result/call/Call;
116117
public final fun markMessageRead (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lio/getstream/result/call/Call;
117118
public final fun markRead (Ljava/lang/String;Ljava/lang/String;)Lio/getstream/result/call/Call;
118119
public final fun markThreadRead (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lio/getstream/result/call/Call;
@@ -2877,7 +2878,8 @@ public final class io/getstream/chat/android/client/notifications/handler/Notifi
28772878
public static final fun createNotificationHandler (Landroid/content/Context;Lio/getstream/chat/android/client/notifications/handler/NotificationConfig;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function0;Lio/getstream/chat/android/client/notifications/handler/UserIconBuilder;Lio/getstream/android/push/permissions/NotificationPermissionHandler;Lkotlin/jvm/functions/Function2;)Lio/getstream/chat/android/client/notifications/handler/NotificationHandler;
28782879
public static final fun createNotificationHandler (Landroid/content/Context;Lio/getstream/chat/android/client/notifications/handler/NotificationConfig;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function0;Lio/getstream/chat/android/client/notifications/handler/UserIconBuilder;Lio/getstream/android/push/permissions/NotificationPermissionHandler;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function3;)Lio/getstream/chat/android/client/notifications/handler/NotificationHandler;
28792880
public static final fun createNotificationHandler (Landroid/content/Context;Lio/getstream/chat/android/client/notifications/handler/NotificationConfig;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function0;Lio/getstream/chat/android/client/notifications/handler/UserIconBuilder;Lio/getstream/android/push/permissions/NotificationPermissionHandler;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function2;)Lio/getstream/chat/android/client/notifications/handler/NotificationHandler;
2880-
public static synthetic fun createNotificationHandler$default (Landroid/content/Context;Lio/getstream/chat/android/client/notifications/handler/NotificationConfig;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function0;Lio/getstream/chat/android/client/notifications/handler/UserIconBuilder;Lio/getstream/android/push/permissions/NotificationPermissionHandler;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lio/getstream/chat/android/client/notifications/handler/NotificationHandler;
2881+
public static final fun createNotificationHandler (Landroid/content/Context;Lio/getstream/chat/android/client/notifications/handler/NotificationConfig;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function0;Lio/getstream/chat/android/client/notifications/handler/UserIconBuilder;Lio/getstream/android/push/permissions/NotificationPermissionHandler;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function1;)Lio/getstream/chat/android/client/notifications/handler/NotificationHandler;
2882+
public static synthetic fun createNotificationHandler$default (Landroid/content/Context;Lio/getstream/chat/android/client/notifications/handler/NotificationConfig;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function0;Lio/getstream/chat/android/client/notifications/handler/UserIconBuilder;Lio/getstream/android/push/permissions/NotificationPermissionHandler;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lio/getstream/chat/android/client/notifications/handler/NotificationHandler;
28812883
}
28822884

28832885
public abstract interface class io/getstream/chat/android/client/notifications/handler/PushDeviceGenerator {

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

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2883,6 +2883,33 @@ internal constructor(
28832883
}
28842884
}
28852885

2886+
/**
2887+
* Request to mark the message with the given id as delivered if:
2888+
*
2889+
* - Delivery receipts are enabled for the current user.
2890+
* - Delivery events are enabled in the channel config.
2891+
*
2892+
* and if all of the following conditions are met for the message:
2893+
*
2894+
* - Not sent by the current user.
2895+
* - Not shadow banned.
2896+
* - Not sent by a muted user.
2897+
* - Not yet marked as read by the current user.
2898+
* - Not yet marked as delivered by the current user.
2899+
*
2900+
* @param messageId The ID of the message to mark as delivered.
2901+
*/
2902+
@CheckResult
2903+
public fun markMessageAsDelivered(messageId: String): Call<Unit> =
2904+
CoroutineCall(userScope) {
2905+
messageReceiptManager.markMessageAsDelivered(messageId)
2906+
Result.Success(Unit)
2907+
}.doOnStart(userScope) {
2908+
logger.d { "[markMessageAsDelivered] #doOnStart; messageId: $messageId" }
2909+
}.doOnResult(userScope) {
2910+
logger.v { "[markMessageAsDelivered] #doOnResult; completed($messageId)" }
2911+
}
2912+
28862913
/**
28872914
* Marks the given message as read.
28882915
*

stream-chat-android-client/src/main/java/io/getstream/chat/android/client/notifications/ChatNotifications.kt

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,11 +56,9 @@ internal class ChatNotificationsImpl(
5656
private val notificationConfig: NotificationConfig,
5757
private val context: Context,
5858
private val scope: CoroutineScope = CoroutineScope(DispatcherProvider.IO),
59-
private val chatClientProvider: () -> ChatClient = { ChatClient.instance() },
6059
) : ChatNotifications {
6160
private val logger by taggedLogger("Chat:Notifications")
6261

63-
private val chatClient: ChatClient by lazy { chatClientProvider() }
6462
private val pushTokenUpdateHandler = PushTokenUpdateHandler()
6563
private val showedMessages = mutableSetOf<String>()
6664
private val permissionManager: NotificationPermissionManager =
@@ -109,8 +107,6 @@ internal class ChatNotificationsImpl(
109107

110108
pushNotificationReceivedListener.onPushNotificationReceived(pushMessage.channelType, pushMessage.channelId)
111109

112-
scope.launch { chatClient.messageReceiptManager.markMessageAsDelivered(pushMessage.messageId) }
113-
114110
if (notificationConfig.shouldShowNotificationOnPush() && !handler.onPushMessage(pushMessage)) {
115111
handlePushMessage(pushMessage)
116112
}

stream-chat-android-client/src/main/java/io/getstream/chat/android/client/notifications/handler/ChatNotificationHandler.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,14 @@ import io.getstream.chat.android.client.extensions.getUsersExcludingCurrent
3535
import io.getstream.chat.android.client.receivers.NotificationMessageReceiver
3636
import io.getstream.chat.android.models.Channel
3737
import io.getstream.chat.android.models.Message
38+
import io.getstream.chat.android.models.PushMessage
3839
import io.getstream.chat.android.models.User
3940

4041
/**
4142
* Class responsible for handling chat notifications.
4243
* Notification channel should only be accessed if Build.VERSION.SDK_INT >= Build.VERSION_CODES.O.
4344
*/
44-
@Suppress("TooManyFunctions")
45+
@Suppress("TooManyFunctions", "LongParameterList")
4546
internal class ChatNotificationHandler(
4647
private val context: Context,
4748
private val newMessageIntent: (message: Message, channel: Channel) -> Intent,
@@ -53,6 +54,7 @@ internal class ChatNotificationHandler(
5354
private val currentUserProvider: () -> User? = {
5455
ChatClient.instance().getCurrentUser() ?: ChatClient.instance().getStoredUser()
5556
},
57+
private val onNewPushMessage: (pushMessage: PushMessage) -> Boolean,
5658
) : NotificationHandler {
5759

5860
private val sharedPreferences: SharedPreferences by lazy {
@@ -88,6 +90,8 @@ internal class ChatNotificationHandler(
8890
showNotificationInternal(ChatNotification.MessageNew(channel, message))
8991
}
9092

93+
override fun onPushMessage(message: PushMessage): Boolean = onNewPushMessage(message)
94+
9195
private fun showNotificationInternal(notification: ChatNotification) {
9296
when (notification) {
9397
is ChatNotification.MessageNew -> showMessageNewNotification(notification)

stream-chat-android-client/src/main/java/io/getstream/chat/android/client/notifications/handler/MessagingStyleNotificationHandler.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import io.getstream.android.push.permissions.NotificationPermissionStatus
3131
import io.getstream.chat.android.client.ChatClient
3232
import io.getstream.chat.android.models.Channel
3333
import io.getstream.chat.android.models.Message
34+
import io.getstream.chat.android.models.PushMessage
3435
import io.getstream.chat.android.models.User
3536
import io.getstream.log.taggedLogger
3637

@@ -52,6 +53,7 @@ internal class MessagingStyleNotificationHandler(
5253
private val notificationTextFormatter: (currentUser: User?, message: Message) -> CharSequence,
5354
private val actionsProvider: (notificationId: Int, channel: Channel, message: Message) -> List<Action>,
5455
notificationBuilderTransformer: (NotificationCompat.Builder, ChatNotification) -> NotificationCompat.Builder,
56+
private val onNewPushMessage: (pushMessage: PushMessage) -> Boolean,
5557
) : NotificationHandler {
5658

5759
private val logger by taggedLogger("Chat:MsnHandler")
@@ -107,6 +109,8 @@ internal class MessagingStyleNotificationHandler(
107109
getShownNotifications().forEach(::dismissNotification)
108110
}
109111

112+
override fun onPushMessage(message: PushMessage): Boolean = onNewPushMessage(message)
113+
110114
private fun showNotificationInternal(chatNotification: ChatNotification) {
111115
ChatClient.instance().launch {
112116
val notificationId = factory.createNotificationId(chatNotification)

stream-chat-android-client/src/main/java/io/getstream/chat/android/client/notifications/handler/NotificationHandlerFactory.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import io.getstream.chat.android.client.R
3232
import io.getstream.chat.android.client.receivers.NotificationMessageReceiver
3333
import io.getstream.chat.android.models.Channel
3434
import io.getstream.chat.android.models.Message
35+
import io.getstream.chat.android.models.PushMessage
3536
import io.getstream.chat.android.models.User
3637
import kotlin.reflect.full.primaryConstructor
3738

@@ -55,6 +56,8 @@ public object NotificationHandlerFactory {
5556
* @param actionsProvider Lambda expression used to provide actions for the notification.
5657
* @param notificationBuilderTransformer Lambda expression used to transform the [NotificationCompat.Builder]
5758
* before building the notification.
59+
* @param onNewPushMessage Lambda expression called when a new push message is received. Return true if the
60+
* push message was handled and no further processing is required.
5861
*
5962
* @return A [NotificationHandler] instance.
6063
* @see NotificationHandler
@@ -75,6 +78,7 @@ public object NotificationHandlerFactory {
7578
getDefaultActionsProvider(context),
7679
notificationBuilderTransformer: (NotificationCompat.Builder, ChatNotification) -> NotificationCompat.Builder =
7780
{ builder, _ -> builder },
81+
onNewPushMessage: (pushMessage: PushMessage) -> Boolean = { false },
7882
): NotificationHandler = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
7983
MessagingStyleNotificationHandler(
8084
context = context,
@@ -85,6 +89,7 @@ public object NotificationHandlerFactory {
8589
notificationTextFormatter = notificationTextFormatter,
8690
actionsProvider = actionsProvider,
8791
notificationBuilderTransformer = notificationBuilderTransformer,
92+
onNewPushMessage = onNewPushMessage,
8893
)
8994
} else {
9095
ChatNotificationHandler(
@@ -94,6 +99,7 @@ public object NotificationHandlerFactory {
9499
notificationTextFormatter = notificationTextFormatter,
95100
actionsProvider = actionsProvider,
96101
notificationBuilderTransformer = notificationBuilderTransformer,
102+
onNewPushMessage = onNewPushMessage,
97103
)
98104
}
99105

stream-chat-android-client/src/test/java/io/getstream/chat/android/client/notifications/ChatNotificationsImplTest.kt

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,9 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
2222
import androidx.work.WorkInfo
2323
import androidx.work.WorkManager
2424
import androidx.work.testing.WorkManagerTestInitHelper
25-
import io.getstream.chat.android.client.ChatClient
2625
import io.getstream.chat.android.client.notifications.handler.NotificationConfig
2726
import io.getstream.chat.android.client.notifications.handler.NotificationHandler
2827
import io.getstream.chat.android.client.randomPushMessage
29-
import io.getstream.chat.android.client.receipts.MessageReceiptManager
3028
import io.getstream.chat.android.models.PushMessage
3129
import kotlinx.coroutines.CoroutineScope
3230
import kotlinx.coroutines.test.UnconfinedTestDispatcher
@@ -37,7 +35,6 @@ import org.junit.runner.RunWith
3735
import org.mockito.kotlin.doReturn
3836
import org.mockito.kotlin.mock
3937
import org.mockito.kotlin.verify
40-
import org.mockito.kotlin.verifyBlocking
4138
import org.mockito.kotlin.whenever
4239
import org.robolectric.annotation.Config
4340

@@ -60,17 +57,6 @@ internal class ChatNotificationsImplTest {
6057
)
6158
}
6259

63-
@Test
64-
fun `onPushMessage marks message as delivered`() {
65-
val pushMessage = randomPushMessage()
66-
val fixture = Fixture()
67-
val sut = fixture.get()
68-
69-
sut.onPushMessage(pushMessage)
70-
71-
fixture.verifyMarkMessageAsDeliveredCalled(messageId = pushMessage.messageId)
72-
}
73-
7460
@Test
7561
fun `onPushMessage schedules work when shouldShowNotificationOnPush is true and handler does not handle message`() {
7662
val pushMessage = randomPushMessage()
@@ -113,11 +99,6 @@ internal class ChatNotificationsImplTest {
11399

114100
private var mockNotificationHandler = mock<NotificationHandler>()
115101
private var notificationConfig = NotificationConfig()
116-
private val mockMessageReceiptManager = mock<MessageReceiptManager>()
117-
118-
private val mockChatClient = mock<ChatClient> {
119-
on { messageReceiptManager } doReturn mockMessageReceiptManager
120-
}
121102

122103
fun givenOnPushMessageHandled(pushMessage: PushMessage, handled: Boolean) = apply {
123104
whenever(mockNotificationHandler.onPushMessage(pushMessage)) doReturn handled
@@ -127,10 +108,6 @@ internal class ChatNotificationsImplTest {
127108
notificationConfig = config
128109
}
129110

130-
fun verifyMarkMessageAsDeliveredCalled(messageId: String) {
131-
verifyBlocking(mockMessageReceiptManager) { markMessageAsDelivered(messageId) }
132-
}
133-
134111
fun get(): ChatNotificationsImpl {
135112
val context = ApplicationProvider.getApplicationContext<Context>()
136113
WorkManagerTestInitHelper.initializeTestWorkManager(context)
@@ -140,7 +117,6 @@ internal class ChatNotificationsImplTest {
140117
notificationConfig = notificationConfig,
141118
context = context,
142119
scope = CoroutineScope(UnconfinedTestDispatcher()),
143-
chatClientProvider = { mockChatClient },
144120
)
145121
}
146122
}

stream-chat-android-client/src/test/java/io/getstream/chat/android/client/notifications/handler/ChatNotificationHandlerTest.kt

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,18 @@ import android.content.SharedPreferences
2525
import androidx.core.app.NotificationCompat
2626
import androidx.test.core.app.ApplicationProvider
2727
import androidx.test.ext.junit.runners.AndroidJUnit4
28+
import io.getstream.chat.android.client.randomPushMessage
2829
import io.getstream.chat.android.models.Channel
2930
import io.getstream.chat.android.models.Message
31+
import io.getstream.chat.android.models.PushMessage
3032
import io.getstream.chat.android.models.User
33+
import io.getstream.chat.android.randomBoolean
3134
import io.getstream.chat.android.randomChannel
3235
import io.getstream.chat.android.randomMessage
3336
import io.getstream.chat.android.randomUser
3437
import org.junit.Before
3538
import org.junit.Test
39+
import org.junit.jupiter.api.Assertions.assertEquals
3640
import org.junit.runner.RunWith
3741
import org.mockito.kotlin.any
3842
import org.mockito.kotlin.anyOrNull
@@ -53,6 +57,7 @@ internal class ChatNotificationHandlerTest {
5357
private val mockSharedPreferences: SharedPreferences = mock()
5458
private val mockSharedPreferencesEditor: SharedPreferences.Editor = mock()
5559
private val mockNotificationChannel: NotificationChannel = mock()
60+
private val mockOnNewPushMessage: (PushMessage) -> Boolean = mock()
5661

5762
private val testChannel = randomChannel(type = "messaging", id = "test_channel")
5863
private val testMessage = randomMessage(id = "test_message", text = "Test message content")
@@ -62,8 +67,10 @@ internal class ChatNotificationHandlerTest {
6267
private val notificationChannel: () -> NotificationChannel = { mockNotificationChannel }
6368
private val notificationTextFormatter: (User?, Message) -> CharSequence = { _, message -> message.text }
6469
private val actionsProvider: (Int, Channel, Message) -> List<NotificationCompat.Action> = { _, _, _ -> emptyList() }
65-
private val notificationBuilderTransformer:
66-
(NotificationCompat.Builder, ChatNotification) -> NotificationCompat.Builder = { builder, _ -> builder }
70+
private val notificationBuilderTransformer: (
71+
NotificationCompat.Builder,
72+
ChatNotification,
73+
) -> NotificationCompat.Builder = { builder, _ -> builder }
6774
private val currentUserProvider: () -> User? = { testUser }
6875

6976
private lateinit var chatNotificationHandler: ChatNotificationHandler
@@ -95,6 +102,7 @@ internal class ChatNotificationHandlerTest {
95102
actionsProvider = actionsProvider,
96103
notificationBuilderTransformer = notificationBuilderTransformer,
97104
currentUserProvider = currentUserProvider,
105+
onNewPushMessage = mockOnNewPushMessage,
98106
)
99107
}
100108

@@ -171,4 +179,18 @@ internal class ChatNotificationHandlerTest {
171179
// Verify SharedPreferences interactions for storing notification ID without summary
172180
verify(mockSharedPreferencesEditor, times(1)).putStringSet(any(), any())
173181
}
182+
183+
@Test
184+
fun `onPushMessage should be handleable`() {
185+
// Given
186+
val handled = randomBoolean()
187+
val pushMessage = randomPushMessage()
188+
whenever(mockOnNewPushMessage.invoke(pushMessage)) doReturn handled
189+
190+
// When
191+
val actual = chatNotificationHandler.onPushMessage(message = pushMessage)
192+
193+
// Then
194+
assertEquals(handled, actual)
195+
}
174196
}

stream-chat-android-compose-sample/src/main/java/io/getstream/chat/android/compose/sample/ChatHelper.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,13 @@ object ChatHelper {
6969
parentMessageId = message.parentId,
7070
)
7171
},
72+
onNewPushMessage = { pushMessage ->
73+
ChatClient.instance()
74+
.markMessageAsDelivered(messageId = pushMessage.messageId)
75+
.enqueue()
76+
// Return false to let the SDK handle the push message and show a notification
77+
false
78+
},
7279
)
7380

7481
val offlinePlugin = StreamOfflinePluginFactory(context)

0 commit comments

Comments
 (0)