From ca50257f9d96b60f267fdb8c76a9e359f8d836fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jc=20Mi=C3=B1arro?= Date: Thu, 12 Dec 2024 01:13:15 +0100 Subject: [PATCH 1/3] Await Depednecy Resolve process until ChatClient is properly initialized --- .../api/stream-chat-android-client.api | 1 + .../chat/android/client/ChatClient.kt | 24 ++++++++++++++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/stream-chat-android-client/api/stream-chat-android-client.api b/stream-chat-android-client/api/stream-chat-android-client.api index 7107aca3ba7..beafd4e14cc 100644 --- a/stream-chat-android-client/api/stream-chat-android-client.api +++ b/stream-chat-android-client/api/stream-chat-android-client.api @@ -9,6 +9,7 @@ public final class io/getstream/chat/android/client/BuildConfig { public final class io/getstream/chat/android/client/ChatClient { public static final field Companion Lio/getstream/chat/android/client/ChatClient$Companion; public static final field DEFAULT_SORT Lio/getstream/chat/android/models/querysort/QuerySorter; + public static final field RESOLVE_DEPENDENCY_TIMEOUT J public final fun acceptInvite (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lio/getstream/result/call/Call; public final fun addDevice (Lio/getstream/chat/android/models/Device;)Lio/getstream/result/call/Call; public final fun addMembers (Ljava/lang/String;Ljava/lang/String;Lio/getstream/chat/android/client/query/AddMembersParams;)Lio/getstream/result/call/Call; diff --git a/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/ChatClient.kt b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/ChatClient.kt index ffcc4a5c78a..43685308623 100644 --- a/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/ChatClient.kt +++ b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/ChatClient.kt @@ -150,6 +150,7 @@ import io.getstream.chat.android.client.utils.observable.Disposable import io.getstream.chat.android.client.utils.retry.NoRetryPolicy import io.getstream.chat.android.client.utils.stringify import io.getstream.chat.android.core.internal.InternalStreamChatApi +import io.getstream.chat.android.core.internal.StreamHandsOff import io.getstream.chat.android.models.AppSettings import io.getstream.chat.android.models.Attachment import io.getstream.chat.android.models.BannedUser @@ -359,7 +360,7 @@ internal constructor( @Suppress("ThrowsCount") internal inline fun resolvePluginDependency(): T { StreamLog.v(TAG) { "[resolvePluginDependency] P: ${P::class.simpleName}, T: ${T::class.simpleName}" } - val initState = clientState.initializationState.value + val initState = awaitInitializationState(RESOLVE_DEPENDENCY_TIMEOUT) if (initState != InitializationState.COMPLETE) { StreamLog.e(TAG) { "[resolvePluginDependency] failed (initializationState is not COMPLETE): $initState " } throw IllegalStateException("ChatClient::connectUser() must be called before resolving any dependency") @@ -375,6 +376,26 @@ internal constructor( ) } + @PublishedApi + @InternalStreamChatApi + @StreamHandsOff( + "This method is used to avoid race-condition between plugin initialization and dependency resolution.", + ) + internal fun awaitInitializationState(timeoutMilliseconds: Long): InitializationState? { + var initState: InitializationState? = clientState.initializationState.value + var spendTime = 0L + inheritScope { Job(it) }.launch { + initState = withTimeoutOrNull(timeoutMilliseconds) { + clientState.initializationState.first { it == InitializationState.COMPLETE } + } + } + while (initState == InitializationState.INITIALIZING && spendTime < timeoutMilliseconds) { + java.lang.Thread.sleep(INITIALIZATION_DELAY) + spendTime += INITIALIZATION_DELAY + } + return initState + } + /** * Error handlers for API calls. */ @@ -3947,6 +3968,7 @@ internal constructor( private const val MESSAGE_ACTION_SHUFFLE = "shuffle" private val THIRTY_DAYS_IN_MILLISECONDS = 30.days.inWholeMilliseconds private const val INITIALIZATION_DELAY = 100L + public const val RESOLVE_DEPENDENCY_TIMEOUT: Long = 10_000L private const val ARG_TYPING_PARENT_ID = "parent_id" From a28b478a3c84507dbdf229c4f38e3e0d5346f7b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jc=20Mi=C3=B1arro?= Date: Thu, 12 Dec 2024 14:10:57 +0100 Subject: [PATCH 2/3] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d09afaec23b..ead5376f6de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ### 🐞 Fixed ### ⬆️ Improved +- Internal "Resolve Dependency" process improvements. [#5514](https://github.com/GetStream/stream-chat-android/pull/5514) ### ✅ Added - Add `Channel.membership.pinnedAt` property notifiying if/when a channel was pinned by the current user. [#5513](https://github.com/GetStream/stream-chat-android/pull/5513) From 2378f37c78d359223f93e5a6e986f406ccacbaa8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jc=20Mi=C3=B1arro?= Date: Thu, 12 Dec 2024 16:41:48 +0100 Subject: [PATCH 3/3] Fix tests --- .../main/java/io/getstream/chat/android/client/ChatClient.kt | 3 +-- .../compose/viewmodel/channels/ChannelListViewModelTest.kt | 1 + .../compose/viewmodel/messages/MessageComposerViewModelTest.kt | 1 + .../chat/android/state/extensions/ChatClientExtensionTests.kt | 2 ++ .../android/ui/viewmodels/channels/ChannelListViewModelTest.kt | 1 + .../ui/viewmodels/messages/MessageComposerViewModelTest.kt | 1 + 6 files changed, 7 insertions(+), 2 deletions(-) diff --git a/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/ChatClient.kt b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/ChatClient.kt index 43685308623..babad63829e 100644 --- a/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/ChatClient.kt +++ b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/ChatClient.kt @@ -376,12 +376,11 @@ internal constructor( ) } - @PublishedApi @InternalStreamChatApi @StreamHandsOff( "This method is used to avoid race-condition between plugin initialization and dependency resolution.", ) - internal fun awaitInitializationState(timeoutMilliseconds: Long): InitializationState? { + public fun awaitInitializationState(timeoutMilliseconds: Long): InitializationState? { var initState: InitializationState? = clientState.initializationState.value var spendTime = 0L inheritScope { Job(it) }.launch { diff --git a/stream-chat-android-compose/src/test/kotlin/io/getstream/chat/android/compose/viewmodel/channels/ChannelListViewModelTest.kt b/stream-chat-android-compose/src/test/kotlin/io/getstream/chat/android/compose/viewmodel/channels/ChannelListViewModelTest.kt index 1c41b433ba8..4526b74d9d8 100644 --- a/stream-chat-android-compose/src/test/kotlin/io/getstream/chat/android/compose/viewmodel/channels/ChannelListViewModelTest.kt +++ b/stream-chat-android-compose/src/test/kotlin/io/getstream/chat/android/compose/viewmodel/channels/ChannelListViewModelTest.kt @@ -330,6 +330,7 @@ internal class ChannelListViewModelTest { fun givenCurrentUser(currentUser: User = User(id = "Jc")) = apply { whenever(clientState.user) doReturn MutableStateFlow(currentUser) whenever(clientState.initializationState) doReturn MutableStateFlow(InitializationState.COMPLETE) + whenever(chatClient.awaitInitializationState(any())) doReturn InitializationState.COMPLETE } fun givenChannelMutes(channelMutes: List = emptyList()) = apply { diff --git a/stream-chat-android-compose/src/test/kotlin/io/getstream/chat/android/compose/viewmodel/messages/MessageComposerViewModelTest.kt b/stream-chat-android-compose/src/test/kotlin/io/getstream/chat/android/compose/viewmodel/messages/MessageComposerViewModelTest.kt index f6c0b588fe7..fb21627bfce 100644 --- a/stream-chat-android-compose/src/test/kotlin/io/getstream/chat/android/compose/viewmodel/messages/MessageComposerViewModelTest.kt +++ b/stream-chat-android-compose/src/test/kotlin/io/getstream/chat/android/compose/viewmodel/messages/MessageComposerViewModelTest.kt @@ -402,6 +402,7 @@ internal class MessageComposerViewModelTest { whenever(clientState.user) doReturn MutableStateFlow(currentUser) whenever(chatClient.clientState) doReturn clientState whenever(clientState.initializationState) doReturn MutableStateFlow(InitializationState.COMPLETE) + whenever(chatClient.awaitInitializationState(any())) doReturn InitializationState.COMPLETE } fun givenChannelQuery(channel: Channel = Channel()) = apply { diff --git a/stream-chat-android-state/src/test/java/io/getstream/chat/android/state/extensions/ChatClientExtensionTests.kt b/stream-chat-android-state/src/test/java/io/getstream/chat/android/state/extensions/ChatClientExtensionTests.kt index 10a14961375..13fa76f8cd5 100644 --- a/stream-chat-android-state/src/test/java/io/getstream/chat/android/state/extensions/ChatClientExtensionTests.kt +++ b/stream-chat-android-state/src/test/java/io/getstream/chat/android/state/extensions/ChatClientExtensionTests.kt @@ -46,6 +46,7 @@ import org.amshove.kluent.shouldBeEqualTo import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.RegisterExtension +import org.mockito.kotlin.any import org.mockito.kotlin.doAnswer import org.mockito.kotlin.doReturn import org.mockito.kotlin.mock @@ -136,6 +137,7 @@ internal class ChatClientExtensionTests { userFlow.value = null Unit.asCall() } + on(it.awaitInitializationState(any())) doReturn InitializationState.COMPLETE } } diff --git a/stream-chat-android-ui-components/src/test/kotlin/io/getstream/chat/android/ui/viewmodels/channels/ChannelListViewModelTest.kt b/stream-chat-android-ui-components/src/test/kotlin/io/getstream/chat/android/ui/viewmodels/channels/ChannelListViewModelTest.kt index d6572ad02e4..1d74186b865 100644 --- a/stream-chat-android-ui-components/src/test/kotlin/io/getstream/chat/android/ui/viewmodels/channels/ChannelListViewModelTest.kt +++ b/stream-chat-android-ui-components/src/test/kotlin/io/getstream/chat/android/ui/viewmodels/channels/ChannelListViewModelTest.kt @@ -264,6 +264,7 @@ internal class ChannelListViewModelTest { whenever(clientState.user) doReturn MutableStateFlow(currentUser) whenever(clientState.initializationState) doReturn MutableStateFlow(InitializationState.COMPLETE) whenever(chatClient.getCurrentUser()) doReturn currentUser + whenever(chatClient.awaitInitializationState(any())) doReturn InitializationState.COMPLETE } fun givenChannelMutes(channelMutes: List = emptyList()) = apply { diff --git a/stream-chat-android-ui-components/src/test/kotlin/io/getstream/chat/android/ui/viewmodels/messages/MessageComposerViewModelTest.kt b/stream-chat-android-ui-components/src/test/kotlin/io/getstream/chat/android/ui/viewmodels/messages/MessageComposerViewModelTest.kt index c4b6a22d595..2145b206838 100644 --- a/stream-chat-android-ui-components/src/test/kotlin/io/getstream/chat/android/ui/viewmodels/messages/MessageComposerViewModelTest.kt +++ b/stream-chat-android-ui-components/src/test/kotlin/io/getstream/chat/android/ui/viewmodels/messages/MessageComposerViewModelTest.kt @@ -403,6 +403,7 @@ internal class MessageComposerViewModelTest { whenever(clientState.user) doReturn MutableStateFlow(currentUser) whenever(chatClient.clientState) doReturn clientState whenever(clientState.initializationState) doReturn MutableStateFlow(InitializationState.COMPLETE) + whenever(chatClient.awaitInitializationState(any())) doReturn InitializationState.COMPLETE } fun givenChannelQuery(channel: Channel = Channel()) = apply {