From fe0c468256cf927c0529e91504088ff0c5ddff75 Mon Sep 17 00:00:00 2001 From: Matt Creaser Date: Wed, 15 Jan 2025 16:00:29 -0400 Subject: [PATCH] feat(all): Add ability to use OkHttp4 with Amplify v2.x (#2970) --- .../analytics/pinpoint/PinpointManager.kt | 2 + .../auth/cognito/AWSCognitoAuthService.kt | 5 ++ .../auth/plugins/core/CognitoClientFactory.kt | 3 + aws-core/build.gradle.kts | 3 + .../com/amplifyframework/util/AmplifyHttp.kt | 59 +++++++++++++++++++ .../location/service/AmazonLocationService.kt | 36 ++++------- .../cloudwatch/AWSCloudWatchLoggingPlugin.kt | 3 +- .../aws/service/AWSComprehendService.kt | 3 +- .../aws/service/AWSPollyService.kt | 2 + .../aws/service/AWSRekognitionService.kt | 3 +- .../aws/service/AWSTextractService.kt | 5 +- .../aws/service/AWSTranslateService.kt | 2 + .../AWSPinpointPushNotificationsPlugin.kt | 2 + .../S3StorageTransferClientProvider.kt | 2 + configuration/consumer-rules.pro | 3 + documents/OkHttp4.md | 54 +++++++++++++++++ gradle/libs.versions.toml | 2 + 17 files changed, 159 insertions(+), 30 deletions(-) create mode 100644 aws-core/src/main/java/com/amplifyframework/util/AmplifyHttp.kt create mode 100644 documents/OkHttp4.md diff --git a/aws-analytics-pinpoint/src/main/java/com/amplifyframework/analytics/pinpoint/PinpointManager.kt b/aws-analytics-pinpoint/src/main/java/com/amplifyframework/analytics/pinpoint/PinpointManager.kt index 7a6d8d8cc7..3cefa27d34 100644 --- a/aws-analytics-pinpoint/src/main/java/com/amplifyframework/analytics/pinpoint/PinpointManager.kt +++ b/aws-analytics-pinpoint/src/main/java/com/amplifyframework/analytics/pinpoint/PinpointManager.kt @@ -24,6 +24,7 @@ import com.amplifyframework.pinpoint.core.data.AndroidAppDetails import com.amplifyframework.pinpoint.core.data.AndroidDeviceDetails import com.amplifyframework.pinpoint.core.database.PinpointDatabase import com.amplifyframework.pinpoint.core.util.getUniqueId +import com.amplifyframework.util.setHttpEngine /** * PinpointManager is the entry point to Pinpoint Analytics and Targeting. @@ -36,6 +37,7 @@ internal class PinpointManager constructor( val analyticsClient: AnalyticsClient val targetingClient: TargetingClient internal val pinpointClient: PinpointClient = PinpointClient { + setHttpEngine() credentialsProvider = this@PinpointManager.credentialsProvider region = awsPinpointConfiguration.region } diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/AWSCognitoAuthService.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/AWSCognitoAuthService.kt index f275e619c6..30018e2b33 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/AWSCognitoAuthService.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/AWSCognitoAuthService.kt @@ -22,6 +22,7 @@ import aws.sdk.kotlin.services.cognitoidentityprovider.endpoints.CognitoIdentity import aws.smithy.kotlin.runtime.client.RequestInterceptorContext import aws.smithy.kotlin.runtime.client.endpoints.Endpoint import aws.smithy.kotlin.runtime.http.interceptors.HttpInterceptor +import com.amplifyframework.util.setHttpEngine interface AWSCognitoAuthService { val cognitoIdentityProviderClient: CognitoIdentityProviderClient? @@ -33,6 +34,8 @@ interface AWSCognitoAuthService { val customPairs: MutableMap = mutableMapOf() val cognitoIdentityProviderClient = configuration.userPool?.let { it -> CognitoIdentityProviderClient { + setHttpEngine() + this.region = it.region this.endpointProvider = it.endpoint?.let { endpoint -> CognitoIdentityProviderEndpointProvider { Endpoint(endpoint) } @@ -50,6 +53,8 @@ interface AWSCognitoAuthService { val cognitoIdentityClient = configuration.identityPool?.let { it -> CognitoIdentityClient { + setHttpEngine() + this.region = it.region this.interceptors += object : HttpInterceptor { override suspend fun modifyBeforeSerialization(context: RequestInterceptorContext): Any { diff --git a/aws-auth-plugins-core/src/main/java/com/amplifyframework/auth/plugins/core/CognitoClientFactory.kt b/aws-auth-plugins-core/src/main/java/com/amplifyframework/auth/plugins/core/CognitoClientFactory.kt index f0537238ee..a51fca1ba3 100644 --- a/aws-auth-plugins-core/src/main/java/com/amplifyframework/auth/plugins/core/CognitoClientFactory.kt +++ b/aws-auth-plugins-core/src/main/java/com/amplifyframework/auth/plugins/core/CognitoClientFactory.kt @@ -22,6 +22,7 @@ import aws.smithy.kotlin.runtime.http.interceptors.HttpInterceptor import com.amplifyframework.auth.AWSCognitoAuthMetadataType import com.amplifyframework.auth.plugins.core.data.AWSCognitoIdentityPoolConfiguration import com.amplifyframework.plugins.core.BuildConfig +import com.amplifyframework.util.setHttpEngine internal object CognitoClientFactory { fun createIdentityClient( @@ -29,6 +30,8 @@ internal object CognitoClientFactory { pluginKey: String, pluginVersion: String, ) = CognitoIdentityClient { + setHttpEngine() + this.region = identityPool.region this.interceptors += object : HttpInterceptor { override suspend fun modifyBeforeSerialization(context: RequestInterceptorContext): Any { diff --git a/aws-core/build.gradle.kts b/aws-core/build.gradle.kts index 47b7a9e85e..61ebfd06e2 100644 --- a/aws-core/build.gradle.kts +++ b/aws-core/build.gradle.kts @@ -35,6 +35,9 @@ dependencies { implementation(libs.kotlin.stdlib) implementation(libs.kotlin.coroutines) + implementation(libs.aws.smithy.http) + compileOnly(libs.aws.smithy.okhttp4) + implementation(libs.aws.credentials) // slf4j dependency is added to fix https://github.com/awslabs/aws-sdk-kotlin/issues/993#issuecomment-1678885524 implementation(libs.slf4j) diff --git a/aws-core/src/main/java/com/amplifyframework/util/AmplifyHttp.kt b/aws-core/src/main/java/com/amplifyframework/util/AmplifyHttp.kt new file mode 100644 index 0000000000..208d5079a3 --- /dev/null +++ b/aws-core/src/main/java/com/amplifyframework/util/AmplifyHttp.kt @@ -0,0 +1,59 @@ +/* + * Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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 com.amplifyframework.util + +import aws.smithy.kotlin.runtime.http.config.HttpClientConfig +import aws.smithy.kotlin.runtime.http.engine.okhttp4.OkHttp4Engine +import com.amplifyframework.annotations.InternalAmplifyApi +import com.amplifyframework.core.Amplify + +internal object AmplifyHttp { + + enum class Version { + OkHttp4, + OkHttp5 + } + + private val logger = Amplify.Logging.logger("HttpEngine") + + val availableVersion: Version by lazy { + // Check to see if OkHttp4 Engine is available on the runtime classpath. If it is then the customer has + // explicitly added it, so we can use that. Otherwise, use the OkHttp5 engine. + try { + Class.forName("aws.smithy.kotlin.runtime.http.engine.okhttp4.OkHttp4Engine") + logger.info("Using OkHttp4 Engine") + Version.OkHttp4 + } catch (e: ClassNotFoundException) { + logger.info("Using default OkHttp5 Engine") + Version.OkHttp5 + } + } +} + +/** + * This function is used to determine, at runtime, whether we should use the OkHttp4Engine instead of the + * default OkHttp5Engine with Kotlin SDK. This allows customers that cannot use OkHttp5 (which is currently an alpha + * release) to use OkHttp4 throughout Amplify by adding a dependency on aws.smithy.kotlin:http-client-engine-okhttp4 + * to their runtime classpath. + * This must be called when instantiating any Client instance from the Kotlin SDK. + */ +@InternalAmplifyApi +fun HttpClientConfig.Builder.setHttpEngine() { + // The default engine is OkHttp5. If we should use OkHttp4 instead then override it here. + if (AmplifyHttp.availableVersion == AmplifyHttp.Version.OkHttp4) { + this.httpClient = OkHttp4Engine() + } +} diff --git a/aws-geo-location/src/main/java/com/amplifyframework/geo/location/service/AmazonLocationService.kt b/aws-geo-location/src/main/java/com/amplifyframework/geo/location/service/AmazonLocationService.kt index 2549031e96..1e860dbf73 100644 --- a/aws-geo-location/src/main/java/com/amplifyframework/geo/location/service/AmazonLocationService.kt +++ b/aws-geo-location/src/main/java/com/amplifyframework/geo/location/service/AmazonLocationService.kt @@ -26,6 +26,7 @@ import com.amplifyframework.geo.models.Coordinates import com.amplifyframework.geo.models.CountryCode import com.amplifyframework.geo.models.Place import com.amplifyframework.geo.models.SearchArea +import com.amplifyframework.util.setHttpEngine /** * Implements the backend provider for the location plugin using @@ -33,17 +34,12 @@ import com.amplifyframework.geo.models.SearchArea * @param credentialsProvider AWS credentials provider for authorizing API calls * @param region AWS region for the Amazon Location Service */ -internal class AmazonLocationService( - credentialsProvider: CredentialsProvider, - region: String -) : GeoService { - override val provider: LocationClient - - init { - provider = LocationClient.invoke { - this.credentialsProvider = credentialsProvider - this.region = region - } +internal class AmazonLocationService(credentialsProvider: CredentialsProvider, region: String) : + GeoService { + override val provider: LocationClient = LocationClient { + setHttpEngine() + this.credentialsProvider = credentialsProvider + this.region = region } override suspend fun getStyleJson(mapName: String): String { @@ -75,17 +71,11 @@ internal class AmazonLocationService( val response = provider.searchPlaceIndexForText(request) return response.results - ?.mapNotNull { it.place } - ?.map { - AmazonLocationPlace(it) - } ?: listOf() + .mapNotNull { it.place } + .map { AmazonLocationPlace(it) } } - override suspend fun reverseGeocode( - index: String, - position: Coordinates, - limit: Int - ): List { + override suspend fun reverseGeocode(index: String, position: Coordinates, limit: Int): List { val request = SearchPlaceIndexForPositionRequest.invoke { this.position = listOf(position.longitude, position.latitude) indexName = index @@ -94,9 +84,7 @@ internal class AmazonLocationService( val response = provider.searchPlaceIndexForPosition(request) return response.results - ?.mapNotNull { it.place } - ?.map { - AmazonLocationPlace(it) - } ?: listOf() + .mapNotNull { it.place } + .map { AmazonLocationPlace(it) } } } diff --git a/aws-logging-cloudwatch/src/main/java/com/amplifyframework/logging/cloudwatch/AWSCloudWatchLoggingPlugin.kt b/aws-logging-cloudwatch/src/main/java/com/amplifyframework/logging/cloudwatch/AWSCloudWatchLoggingPlugin.kt index 7630ca6f09..218838a9b1 100644 --- a/aws-logging-cloudwatch/src/main/java/com/amplifyframework/logging/cloudwatch/AWSCloudWatchLoggingPlugin.kt +++ b/aws-logging-cloudwatch/src/main/java/com/amplifyframework/logging/cloudwatch/AWSCloudWatchLoggingPlugin.kt @@ -28,9 +28,9 @@ import com.amplifyframework.logging.LoggingPlugin import com.amplifyframework.logging.cloudwatch.models.AWSCloudWatchLoggingPluginConfiguration import com.amplifyframework.logging.cloudwatch.worker.CloudwatchRouterWorker import com.amplifyframework.logging.cloudwatch.worker.CloudwatchWorkerFactory +import com.amplifyframework.util.setHttpEngine import java.net.URL import kotlinx.serialization.ExperimentalSerializationApi -import kotlinx.serialization.decodeFromString import kotlinx.serialization.json.Json import org.json.JSONObject @@ -96,6 +96,7 @@ class AWSCloudWatchLoggingPlugin @JvmOverloads constructor( val awsLoggingConfig = awsCloudWatchLoggingPluginConfig ?: getConfigFromFile(pluginConfiguration) loggingConstraintsResolver.context = context cloudWatchLogsClient = CloudWatchLogsClient { + setHttpEngine() credentialsProvider = CognitoCredentialsProvider() region = awsLoggingConfig.region } diff --git a/aws-predictions/src/main/java/com/amplifyframework/predictions/aws/service/AWSComprehendService.kt b/aws-predictions/src/main/java/com/amplifyframework/predictions/aws/service/AWSComprehendService.kt index 4007bd0e27..093f0e2f2d 100644 --- a/aws-predictions/src/main/java/com/amplifyframework/predictions/aws/service/AWSComprehendService.kt +++ b/aws-predictions/src/main/java/com/amplifyframework/predictions/aws/service/AWSComprehendService.kt @@ -40,7 +40,7 @@ import com.amplifyframework.predictions.models.Sentiment import com.amplifyframework.predictions.models.SentimentType import com.amplifyframework.predictions.models.Syntax import com.amplifyframework.predictions.result.InterpretResult -import java.util.ArrayList +import com.amplifyframework.util.setHttpEngine import java.util.concurrent.Executors import kotlinx.coroutines.runBlocking @@ -52,6 +52,7 @@ internal class AWSComprehendService( private val authCredentialsProvider: CredentialsProvider ) { val client: ComprehendClient = ComprehendClient { + setHttpEngine() this.region = pluginConfiguration.defaultRegion this.credentialsProvider = authCredentialsProvider } diff --git a/aws-predictions/src/main/java/com/amplifyframework/predictions/aws/service/AWSPollyService.kt b/aws-predictions/src/main/java/com/amplifyframework/predictions/aws/service/AWSPollyService.kt index 795ee99ae4..0178dfeacd 100644 --- a/aws-predictions/src/main/java/com/amplifyframework/predictions/aws/service/AWSPollyService.kt +++ b/aws-predictions/src/main/java/com/amplifyframework/predictions/aws/service/AWSPollyService.kt @@ -27,6 +27,7 @@ import com.amplifyframework.predictions.PredictionsException import com.amplifyframework.predictions.aws.AWSPredictionsPluginConfiguration import com.amplifyframework.predictions.aws.models.AWSVoiceType import com.amplifyframework.predictions.result.TextToSpeechResult +import com.amplifyframework.util.setHttpEngine import java.io.InputStream import java.util.concurrent.Executors import kotlinx.coroutines.runBlocking @@ -40,6 +41,7 @@ internal class AWSPollyService( ) { val client: PollyClient = AmazonPollyPresigningClient( PollyClient { + setHttpEngine() this.region = pluginConfiguration.defaultRegion this.credentialsProvider = authCredentialsProvider } diff --git a/aws-predictions/src/main/java/com/amplifyframework/predictions/aws/service/AWSRekognitionService.kt b/aws-predictions/src/main/java/com/amplifyframework/predictions/aws/service/AWSRekognitionService.kt index fb072ff73c..80b5e4b0d2 100644 --- a/aws-predictions/src/main/java/com/amplifyframework/predictions/aws/service/AWSRekognitionService.kt +++ b/aws-predictions/src/main/java/com/amplifyframework/predictions/aws/service/AWSRekognitionService.kt @@ -47,7 +47,7 @@ import com.amplifyframework.predictions.result.IdentifyEntityMatchesResult import com.amplifyframework.predictions.result.IdentifyLabelsResult import com.amplifyframework.predictions.result.IdentifyResult import com.amplifyframework.predictions.result.IdentifyTextResult -import java.lang.StringBuilder +import com.amplifyframework.util.setHttpEngine import java.net.MalformedURLException import java.net.URL import java.nio.ByteBuffer @@ -64,6 +64,7 @@ internal class AWSRekognitionService( ) { val client: RekognitionClient = RekognitionClient { + setHttpEngine() this.region = pluginConfiguration.defaultRegion this.credentialsProvider = authCredentialsProvider } diff --git a/aws-predictions/src/main/java/com/amplifyframework/predictions/aws/service/AWSTextractService.kt b/aws-predictions/src/main/java/com/amplifyframework/predictions/aws/service/AWSTextractService.kt index a40deadc21..45dfe32d1d 100644 --- a/aws-predictions/src/main/java/com/amplifyframework/predictions/aws/service/AWSTextractService.kt +++ b/aws-predictions/src/main/java/com/amplifyframework/predictions/aws/service/AWSTextractService.kt @@ -32,10 +32,8 @@ import com.amplifyframework.predictions.models.Table import com.amplifyframework.predictions.models.TextFormatType import com.amplifyframework.predictions.result.IdentifyDocumentTextResult import com.amplifyframework.predictions.result.IdentifyResult -import java.lang.StringBuilder +import com.amplifyframework.util.setHttpEngine import java.nio.ByteBuffer -import java.util.ArrayList -import java.util.HashMap import java.util.concurrent.Executors import kotlinx.coroutines.runBlocking @@ -47,6 +45,7 @@ internal class AWSTextractService( private val authCredentialsProvider: CredentialsProvider ) { val client: TextractClient = TextractClient { + setHttpEngine() this.region = pluginConfiguration.defaultRegion this.credentialsProvider = authCredentialsProvider } diff --git a/aws-predictions/src/main/java/com/amplifyframework/predictions/aws/service/AWSTranslateService.kt b/aws-predictions/src/main/java/com/amplifyframework/predictions/aws/service/AWSTranslateService.kt index f542cc1f39..3bff2d250f 100644 --- a/aws-predictions/src/main/java/com/amplifyframework/predictions/aws/service/AWSTranslateService.kt +++ b/aws-predictions/src/main/java/com/amplifyframework/predictions/aws/service/AWSTranslateService.kt @@ -22,6 +22,7 @@ import com.amplifyframework.predictions.PredictionsException import com.amplifyframework.predictions.aws.AWSPredictionsPluginConfiguration import com.amplifyframework.predictions.models.LanguageType import com.amplifyframework.predictions.result.TranslateTextResult +import com.amplifyframework.util.setHttpEngine import java.util.concurrent.Executors import kotlinx.coroutines.runBlocking @@ -33,6 +34,7 @@ internal class AWSTranslateService( private val authCredentialsProvider: CredentialsProvider ) { val client: TranslateClient = TranslateClient { + setHttpEngine() this.region = pluginConfiguration.defaultRegion this.credentialsProvider = authCredentialsProvider } diff --git a/aws-push-notifications-pinpoint/src/main/java/com/amplifyframework/pushnotifications/pinpoint/AWSPinpointPushNotificationsPlugin.kt b/aws-push-notifications-pinpoint/src/main/java/com/amplifyframework/pushnotifications/pinpoint/AWSPinpointPushNotificationsPlugin.kt index 9e0be9fb9f..05a3afe02c 100644 --- a/aws-push-notifications-pinpoint/src/main/java/com/amplifyframework/pushnotifications/pinpoint/AWSPinpointPushNotificationsPlugin.kt +++ b/aws-push-notifications-pinpoint/src/main/java/com/amplifyframework/pushnotifications/pinpoint/AWSPinpointPushNotificationsPlugin.kt @@ -44,6 +44,7 @@ import com.amplifyframework.pinpoint.core.data.AndroidAppDetails import com.amplifyframework.pinpoint.core.data.AndroidDeviceDetails import com.amplifyframework.pinpoint.core.database.PinpointDatabase import com.amplifyframework.pinpoint.core.util.getUniqueId +import com.amplifyframework.util.setHttpEngine import com.google.firebase.messaging.FirebaseMessaging import java.util.concurrent.ConcurrentHashMap import kotlin.random.Random @@ -133,6 +134,7 @@ class AWSPinpointPushNotificationsPlugin : PushNotificationsPlugin S3Client @@ -24,6 +25,7 @@ internal class S3StorageTransferClientProvider( @JvmStatic fun getS3Client(region: String, authCredentialsProvider: AuthCredentialsProvider): S3Client { return S3Client { + setHttpEngine() this.region = region this.credentialsProvider = authCredentialsProvider } diff --git a/configuration/consumer-rules.pro b/configuration/consumer-rules.pro index 24be28fad1..38865c9600 100644 --- a/configuration/consumer-rules.pro +++ b/configuration/consumer-rules.pro @@ -2,3 +2,6 @@ -keep class com.amazonaws.** { *; } -keep class com.amplifyframework.** { *; } + +# We check for specific engine classes on the classpath to determine whether Amplify should use OkHttp4 instead of OkHttp5 +-keepnames class aws.smithy.kotlin.runtime.http.engine.okhttp4.* \ No newline at end of file diff --git a/documents/OkHttp4.md b/documents/OkHttp4.md new file mode 100644 index 0000000000..abc83b5be5 --- /dev/null +++ b/documents/OkHttp4.md @@ -0,0 +1,54 @@ +# Using OkHttp4 in Amplify Android + +Amplify Android v2 uses OkHttp5 by default, but starting with release `2.26.0` it will switch all clients to use OkHttp4 if the `OkHttp4Engine` +is available on the runtime classpath. Please use these steps to switch to using OkHttp4. + +## 1. Upgrade Amplify if necessary + +You must be using at least Amplify `2.26.0` to use OkHttp4. + +## 2. Add the required dependency + +Add the dependency on the `OkHttp4Engine` library to your application's `build.gradle.kts` + +```kotlin +dependencies { + implementation("aws.smithy.kotlin:http-client-engine-okhttp4:1.3.32") // Version must align with Smithy dependency in Amplify +} +``` + +To determine the correct version for the above dependency check in Amplify's [libs.versions.toml](../gradle/libs.versions.toml) file. +Ensure that you are viewing the file version for the Amplify version you are using, and then check the version entry for `aws-smithy`. +Remember to keep these versions in sync when you update Amplify. + +## 3. Force the OkHttp version + +Add the following snippet in your application's `build.gradle.kts` file: + +```kotlin +configurations.configureEach { + // Force replace OkHttp5 with OkHttp4 + resolutionStrategy { + force("com.squareup.okhttp3:okhttp:4.12.0") // Or whicher OkHttp version you want + } + // Exclude other OkHttp5 dependencies + exclude(group = "com.squareup.okhttp3", module = "okhttp-coroutines") +} +``` + +## 4. Add Proguard/R8 rules + +If you are using obfuscation/minification you may encounter compilation errors in affected builds. Check +`build/outputs/mapping//missing_rules.txt` for any rules that are needed. The following +rules may need to be added to `proguard-rules.pro`: + +``` +-dontwarn com.google.errorprone.annotations.Immutable +-dontwarn okhttp3.ConnectionListener$Companion +-dontwarn okhttp3.ConnectionListener +-dontwarn okhttp3.coroutines.ExecuteAsyncKt +``` + +## Troubleshooting + +Please refer to the [AWS SDK for Kotlin document](https://github.com/smithy-lang/smithy-kotlin/tree/main/runtime/protocol/http-client-engines/http-client-engine-okhttp4) on this topic or [Open an Issue](https://github.com/aws-amplify/amplify-android/issues/new/choose) if you run into any problems. \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8f6cec9446..2b07480351 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -83,6 +83,8 @@ aws-polly = { module = "aws.sdk.kotlin:polly", version.ref = "aws-kotlin" } aws-rekognition = { module = "aws.sdk.kotlin:rekognition", version.ref = "aws-kotlin" } aws-s3 = { module = "aws.sdk.kotlin:s3", version.ref = "aws-kotlin" } aws-signing = { module = "aws.smithy.kotlin:aws-signing-default", version.ref = "aws-smithy" } +aws-smithy-http = { module = "aws.smithy.kotlin:http-client-jvm", version.ref = "aws-smithy" } +aws-smithy-okhttp4 = { module = "aws.smithy.kotlin:http-client-engine-okhttp4", version.ref = "aws-smithy" } aws-textract = { module = "aws.sdk.kotlin:textract", version.ref = "aws-kotlin" } aws-translate = { module = "aws.sdk.kotlin:translate", version.ref = "aws-kotlin" } firebasemessaging = { module = "com.google.firebase:firebase-messaging-ktx", version.ref = "fcm" }