diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index d45c38e..6f3909a 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -18,6 +18,7 @@ xmlns:tools="http://schemas.android.com/tools"> { @@ -145,7 +104,7 @@ class SampleActivity : ComponentActivity(), StreamClientListener { "Disconnect", true, { - lifecycleScope.launch { streamClient?.disconnect() } + lifecycleScope.launch { streamClient.disconnect() } Unit }, ) @@ -174,6 +133,11 @@ class SampleActivity : ComponentActivity(), StreamClientListener { } } } + + override fun onDestroy() { + super.onDestroy() + handle?.cancel() + } } @Composable diff --git a/app/src/main/java/io/getstream/android/core/sample/SampleApp.kt b/app/src/main/java/io/getstream/android/core/sample/SampleApp.kt new file mode 100644 index 0000000..a8e8488 --- /dev/null +++ b/app/src/main/java/io/getstream/android/core/sample/SampleApp.kt @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2014-2025 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-core-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.android.core.sample + +import android.annotation.SuppressLint +import android.app.Application +import android.os.Build +import io.getstream.android.core.api.StreamClient +import io.getstream.android.core.api.authentication.StreamTokenProvider +import io.getstream.android.core.api.model.config.StreamClientSerializationConfig +import io.getstream.android.core.api.model.value.StreamApiKey +import io.getstream.android.core.api.model.value.StreamHttpClientInfoHeader +import io.getstream.android.core.api.model.value.StreamToken +import io.getstream.android.core.api.model.value.StreamUserId +import io.getstream.android.core.api.model.value.StreamWsUrl +import io.getstream.android.core.api.serialization.StreamEventSerialization +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob + +class SampleApp : Application() { + + lateinit var streamClient: StreamClient + private val userId = StreamUserId.fromString("sample-user") + private val token = + StreamToken.fromString( + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoicGV0YXIifQ.mZFi4iSblaIoyo9JDdcxIkGkwI-tuApeSBawxpz42rs" + ) + private val coroutinesScope = CoroutineScope(SupervisorJob() + Dispatchers.IO) + + companion object { + lateinit var instance: SampleApp + } + + @SuppressLint("NotKeepingInstance") + override fun onCreate() { + super.onCreate() + instance = this + streamClient = + StreamClient( + context = this.applicationContext, + scope = coroutinesScope, + apiKey = StreamApiKey.fromString("pd67s34fzpgw"), + userId = userId, + products = listOf("feeds", "chat", "video"), + wsUrl = + StreamWsUrl.fromString( + "wss://chat-edge-frankfurt-ce1.stream-io-api.com/api/v2/connect" + ), + clientInfoHeader = + StreamHttpClientInfoHeader.create( + product = "android-core", + productVersion = "1.1.0", + os = "Android", + apiLevel = Build.VERSION.SDK_INT, + deviceModel = "Pixel 7 Pro", + app = "Stream Android Core Sample", + appVersion = "1.0.0", + ), + tokenProvider = + object : StreamTokenProvider { + override suspend fun loadToken(userId: StreamUserId): StreamToken { + return token + } + }, + serializationConfig = + StreamClientSerializationConfig.default( + object : StreamEventSerialization { + override fun serialize(data: Unit): Result = Result.success("") + + override fun deserialize(raw: String): Result = + Result.success(Unit) + } + ), + ) + } +} diff --git a/app/src/main/java/io/getstream/android/core/sample/client/StreamClient.kt b/app/src/main/java/io/getstream/android/core/sample/client/StreamClient.kt deleted file mode 100644 index bd4941f..0000000 --- a/app/src/main/java/io/getstream/android/core/sample/client/StreamClient.kt +++ /dev/null @@ -1,158 +0,0 @@ -/* - * Copyright (c) 2014-2025 Stream.io Inc. All rights reserved. - * - * Licensed under the Stream License; - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://github.com/GetStream/stream-core-android/blob/main/LICENSE - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.getstream.android.core.sample.client - -import android.content.Context -import io.getstream.android.core.api.StreamClient -import io.getstream.android.core.api.authentication.StreamTokenManager -import io.getstream.android.core.api.authentication.StreamTokenProvider -import io.getstream.android.core.api.components.StreamAndroidComponentsProvider -import io.getstream.android.core.api.log.StreamLogger -import io.getstream.android.core.api.log.StreamLoggerProvider -import io.getstream.android.core.api.model.config.StreamClientSerializationConfig -import io.getstream.android.core.api.model.value.StreamApiKey -import io.getstream.android.core.api.model.value.StreamHttpClientInfoHeader -import io.getstream.android.core.api.model.value.StreamUserId -import io.getstream.android.core.api.model.value.StreamWsUrl -import io.getstream.android.core.api.observers.lifecycle.StreamLifecycleMonitor -import io.getstream.android.core.api.observers.network.StreamNetworkMonitor -import io.getstream.android.core.api.processing.StreamBatcher -import io.getstream.android.core.api.processing.StreamRetryProcessor -import io.getstream.android.core.api.processing.StreamSerialProcessingQueue -import io.getstream.android.core.api.processing.StreamSingleFlightProcessor -import io.getstream.android.core.api.recovery.StreamConnectionRecoveryEvaluator -import io.getstream.android.core.api.serialization.StreamEventSerialization -import io.getstream.android.core.api.socket.StreamConnectionIdHolder -import io.getstream.android.core.api.socket.StreamWebSocketFactory -import io.getstream.android.core.api.socket.listeners.StreamClientListener -import io.getstream.android.core.api.socket.monitor.StreamHealthMonitor -import io.getstream.android.core.api.subscribe.StreamSubscriptionManager -import kotlinx.coroutines.CoroutineScope - -/** - * Creates a [createStreamClient] instance with the given configuration and dependencies. - * - * @param scope The coroutine scope. - * @param apiKey The API key. - * @param userId The user ID. - * @param wsUrl The WebSocket URL. - * @param clientInfoHeader The client info header. - * @param tokenProvider The token provider. - * @return A new [createStreamClient] instance. - */ -fun createStreamClient( - context: Context, - scope: CoroutineScope, - apiKey: StreamApiKey, - userId: StreamUserId, - wsUrl: StreamWsUrl, - clientInfoHeader: StreamHttpClientInfoHeader, - tokenProvider: StreamTokenProvider, -): StreamClient { - val logProvider = - StreamLoggerProvider.Companion.defaultAndroidLogger( - minLevel = StreamLogger.LogLevel.Verbose, - honorAndroidIsLoggable = false, - ) - val clientSubscriptionManager = - StreamSubscriptionManager( - logger = logProvider.taggedLogger("SCClientSubscriptions"), - maxStrongSubscriptions = 250, - maxWeakSubscriptions = 250, - ) - val singleFlight = StreamSingleFlightProcessor(scope) - val tokenManager = StreamTokenManager(userId, tokenProvider, singleFlight) - val serialQueue = - StreamSerialProcessingQueue( - logger = logProvider.taggedLogger("SCSerialProcessing"), - scope = scope, - ) - val retryProcessor = StreamRetryProcessor(logger = logProvider.taggedLogger("SCRetryProcessor")) - val connectionIdHolder = StreamConnectionIdHolder() - val socketFactory = - StreamWebSocketFactory(logger = logProvider.taggedLogger("SCWebSocketFactory")) - val healthMonitor = - StreamHealthMonitor(logger = logProvider.taggedLogger("SCHealthMonitor"), scope = scope) - val batcher = - StreamBatcher( - scope = scope, - batchSize = 10, - initialDelayMs = 100L, - maxDelayMs = 1_000L, - ) - - val androidComponentsProvider = StreamAndroidComponentsProvider(context) - val connectivityManager = androidComponentsProvider.connectivityManager().getOrThrow() - val wifiManager = androidComponentsProvider.wifiManager().getOrThrow() - val telephonyManager = androidComponentsProvider.telephonyManager().getOrThrow() - val networkMonitor = - StreamNetworkMonitor( - logger = logProvider.taggedLogger("SCNetworkMonitor"), - scope = scope, - connectivityManager = connectivityManager, - wifiManager = wifiManager, - telephonyManager = telephonyManager, - subscriptionManager = - StreamSubscriptionManager( - logger = logProvider.taggedLogger("SCNetworkMonitorSubscriptions") - ), - ) - val lifecycleMonitor = - StreamLifecycleMonitor( - logger = logProvider.taggedLogger("SCLifecycleMonitor"), - subscriptionManager = - StreamSubscriptionManager( - logger = logProvider.taggedLogger("SCLifecycleMonitorSubscriptions") - ), - lifecycle = androidComponentsProvider.lifecycle(), - ) - - return StreamClient( - scope = scope, - apiKey = apiKey, - userId = userId, - wsUrl = wsUrl, - products = listOf("feeds"), - clientInfoHeader = clientInfoHeader, - tokenProvider = tokenProvider, - logProvider = logProvider, - clientSubscriptionManager = clientSubscriptionManager, - tokenManager = tokenManager, - singleFlight = singleFlight, - serialQueue = serialQueue, - retryProcessor = retryProcessor, - connectionIdHolder = connectionIdHolder, - socketFactory = socketFactory, - healthMonitor = healthMonitor, - networkMonitor = networkMonitor, - serializationConfig = - StreamClientSerializationConfig.default( - object : StreamEventSerialization { - override fun serialize(data: Unit): Result = Result.success("") - - override fun deserialize(raw: String): Result = Result.success(Unit) - } - ), - lifecycleMonitor = lifecycleMonitor, - connectionRecoveryEvaluator = - StreamConnectionRecoveryEvaluator( - logger = logProvider.taggedLogger("SCConnectionRecoveryEvaluator"), - singleFlightProcessor = singleFlight, - ), - batcher = batcher, - ) -} diff --git a/stream-android-core/src/main/java/io/getstream/android/core/api/StreamClient.kt b/stream-android-core/src/main/java/io/getstream/android/core/api/StreamClient.kt index f67201f..2fa1e24 100644 --- a/stream-android-core/src/main/java/io/getstream/android/core/api/StreamClient.kt +++ b/stream-android-core/src/main/java/io/getstream/android/core/api/StreamClient.kt @@ -17,9 +17,11 @@ package io.getstream.android.core.api import android.annotation.SuppressLint +import android.content.Context import io.getstream.android.core.annotations.StreamInternalApi import io.getstream.android.core.api.authentication.StreamTokenManager import io.getstream.android.core.api.authentication.StreamTokenProvider +import io.getstream.android.core.api.components.StreamAndroidComponentsProvider import io.getstream.android.core.api.http.StreamOkHttpInterceptors import io.getstream.android.core.api.log.StreamLoggerProvider import io.getstream.android.core.api.model.config.StreamClientSerializationConfig @@ -206,36 +208,85 @@ public interface StreamClient : StreamObservable { @SuppressLint("ExposeAsStateFlow") @StreamInternalApi public fun StreamClient( + + // Android + scope: CoroutineScope, + context: Context, + // Client config apiKey: StreamApiKey, userId: StreamUserId, wsUrl: StreamWsUrl, products: List, clientInfoHeader: StreamHttpClientInfoHeader, - clientSubscriptionManager: StreamSubscriptionManager, - // Token tokenProvider: StreamTokenProvider, - tokenManager: StreamTokenManager, - // Processing - singleFlight: StreamSingleFlightProcessor, - serialQueue: StreamSerialProcessingQueue, - retryProcessor: StreamRetryProcessor, - scope: CoroutineScope, - // Socket - connectionIdHolder: StreamConnectionIdHolder, - socketFactory: StreamWebSocketFactory, - batcher: StreamBatcher, - // Monitoring - healthMonitor: StreamHealthMonitor, - networkMonitor: StreamNetworkMonitor, - lifecycleMonitor: StreamLifecycleMonitor, - connectionRecoveryEvaluator: StreamConnectionRecoveryEvaluator, - // Http - httpConfig: StreamHttpConfig? = null, - // Serialization serializationConfig: StreamClientSerializationConfig, + httpConfig: StreamHttpConfig? = null, + + // Component provider + androidComponentsProvider: StreamAndroidComponentsProvider = + StreamAndroidComponentsProvider(context.applicationContext), + // Logging logProvider: StreamLoggerProvider = StreamLoggerProvider.defaultAndroidLogger(), + + // Subscriptions + clientSubscriptionManager: StreamSubscriptionManager = + StreamSubscriptionManager( + logger = logProvider.taggedLogger("SCClientSubscriptions"), + maxStrongSubscriptions = 250, + maxWeakSubscriptions = 250, + ), + + // Processing + singleFlight: StreamSingleFlightProcessor = StreamSingleFlightProcessor(scope), + serialQueue: StreamSerialProcessingQueue = + StreamSerialProcessingQueue( + logger = logProvider.taggedLogger("SCSerialProcessing"), + scope = scope, + ), + retryProcessor: StreamRetryProcessor = + StreamRetryProcessor(logger = logProvider.taggedLogger("SCRetryProcessor")), + + // Token + tokenManager: StreamTokenManager = StreamTokenManager(userId, tokenProvider, singleFlight), + + // Socket + connectionIdHolder: StreamConnectionIdHolder = StreamConnectionIdHolder(), + socketFactory: StreamWebSocketFactory = + StreamWebSocketFactory(logger = logProvider.taggedLogger("SCWebSocketFactory")), + batcher: StreamBatcher = + StreamBatcher(scope = scope, batchSize = 10, initialDelayMs = 100L, maxDelayMs = 1_000L), + + // Monitoring + healthMonitor: StreamHealthMonitor = + StreamHealthMonitor(logger = logProvider.taggedLogger("SCHealthMonitor"), scope = scope), + networkMonitor: StreamNetworkMonitor = + StreamNetworkMonitor( + logger = logProvider.taggedLogger("SCNetworkMonitor"), + scope = scope, + connectivityManager = androidComponentsProvider.connectivityManager().getOrThrow(), + wifiManager = androidComponentsProvider.wifiManager().getOrThrow(), + telephonyManager = androidComponentsProvider.telephonyManager().getOrThrow(), + subscriptionManager = + StreamSubscriptionManager( + logger = logProvider.taggedLogger("SCNetworkMonitorSubscriptions") + ), + ), + lifecycleMonitor: StreamLifecycleMonitor = + StreamLifecycleMonitor( + logger = logProvider.taggedLogger("SCLifecycleMonitor"), + subscriptionManager = + StreamSubscriptionManager( + logger = logProvider.taggedLogger("SCLifecycleMonitorSubscriptions") + ), + lifecycle = androidComponentsProvider.lifecycle(), + ), + connectionRecoveryEvaluator: StreamConnectionRecoveryEvaluator = + StreamConnectionRecoveryEvaluator( + logger = logProvider.taggedLogger("SCConnectionRecoveryEvaluator"), + singleFlightProcessor = singleFlight, + ), ): StreamClient { val clientLogger = logProvider.taggedLogger(tag = "SCClient") val parent = scope.coroutineContext[Job] diff --git a/stream-android-core/src/test/java/io/getstream/android/core/api/StreamClientFactoryTest.kt b/stream-android-core/src/test/java/io/getstream/android/core/api/StreamClientFactoryTest.kt index 47002dc..231f713 100644 --- a/stream-android-core/src/test/java/io/getstream/android/core/api/StreamClientFactoryTest.kt +++ b/stream-android-core/src/test/java/io/getstream/android/core/api/StreamClientFactoryTest.kt @@ -141,6 +141,7 @@ internal class StreamClientFactoryTest { val client = StreamClient( + context = mockk(relaxed = true), apiKey = deps.apiKey, userId = deps.userId, wsUrl = deps.wsUrl,