Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding support for non-hilt Applications #37

Merged
merged 2 commits into from
Oct 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ dependencies {

## Getting Started

The first step to leveraging the Amazon Connect Chat SDK after installation is to import the library into your file. Next, let's call the StartChatContact API and pass the response details into the SDK’s ChatSession object. Here is an [example](TODO - Add link to UI Example) of how we would set this up in Kotlin. For reference, you can visit the [AndroidChatExample demo](https://github.com/amazon-connect/amazon-connect-chat-ui-examples/tree/master/mobileChatExamples/androidChatExample) within the [Amazon Connect Chat UI Examples](https://github.com/amazon-connect/amazon-connect-chat-ui-examples/tree/master) GitHub repository.
The first step to leveraging the Amazon Connect Chat SDK after installation is to import the library into your file. Next, let's call the StartChatContact API and pass the response details into the SDK’s ChatSession object. For reference, you can visit the [AndroidChatExample demo](https://github.com/amazon-connect/amazon-connect-chat-ui-examples/tree/master/mobileChatExamples/androidChatExample) within the [Amazon Connect Chat UI Examples](https://github.com/amazon-connect/amazon-connect-chat-ui-examples/tree/master) GitHub repository.

The majority of the SDKs functionality will be accessed through the `ChatSession` object. In order to use this object in the file, you can inject it using `@HiltViewModel`:

Expand All @@ -70,6 +70,7 @@ If you are not using Hilt, then you can initialise `ChatSession` like this:
```
private val chatSession = ChatSessionProvider.getChatSession(context)
```
> Note: When not using Hilt/Dagger for dependency injection, you may need to manually import the necessary dependencies for [AWSConnectParticipant](https://github.com/aws-amplify/aws-sdk-android) and [AWSCore](https://github.com/aws-amplify/aws-sdk-android) in your app’s build.gradle file. With Hilt/Dagger, these dependencies are managed automatically, but for manual implementations, you must include them explicitly to ensure all required classes are available.

In this example, we are using a `ChatViewModel` class that helps bridge UI and SDK communication. This class is responsible for managing interactions with the SDK's ChatSession object. From here, we can access the SDK's suite of APIs from the `chatSession` property.

Expand Down
25 changes: 25 additions & 0 deletions chat-sdk/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,31 @@ tasks.withType<AbstractPublishToMaven>().configureEach {
dependsOn(tasks.named("assembleRelease"))
}

// For local publishing
// Can be used in example app like below
// Keeping group Id different for local testing purpose
// implementation("com.amazon.connect.chat.sdk:connect-chat-sdk:1.0.0")
publishing {
publications {
// Create a MavenPublication for the release build type
create<MavenPublication>("release") {
afterEvaluate {
artifact(tasks.getByName("bundleReleaseAar"))
}
groupId = "com.amazon.connect.chat.sdk"
artifactId = "connect-chat-sdk"
version = "1.0.0"


}
}
// Define the repository where the artifact will be published
repositories {
mavenLocal()
}
}


// Test summary gradle file
apply(from = "test-summary.gradle.kts")

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import com.amazon.connect.chat.sdk.provider.ConnectionDetailsProvider
import com.amazon.connect.chat.sdk.provider.ConnectionDetailsProviderImpl
import dagger.Module
import dagger.Provides
import dagger.hilt.EntryPoint
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
Expand Down Expand Up @@ -48,7 +49,17 @@ object ChatModule {
attachmentsManager: AttachmentsManager,
messageReceiptsManager: MessageReceiptsManager,
): ChatService {
return ChatServiceImpl(context, awsClient, connectionDetailsProvider, webSocketManager, metricsManager, attachmentsManager, messageReceiptsManager)
return ChatServiceImpl(context, awsClient, connectionDetailsProvider,
webSocketManager, metricsManager, attachmentsManager, messageReceiptsManager)
}

/**
* Entry point for Hilt to provide ChatSession.
*/
@EntryPoint
@InstallIn(SingletonComponent::class)
interface ChatSessionEntryPoint {
fun getChatSession(): ChatSession
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,17 @@
package com.amazon.connect.chat.sdk.di

import android.content.Context
import com.amazon.connect.chat.sdk.network.api.APIClient
import com.amazon.connect.chat.sdk.network.AWSClient
import com.amazon.connect.chat.sdk.network.AWSClientImpl
import com.amazon.connect.chat.sdk.network.api.ApiUrl
import com.amazon.connect.chat.sdk.network.NetworkConnectionManager
import com.amazon.connect.chat.sdk.network.RetrofitServiceCreator
import com.amazon.connect.chat.sdk.network.api.APIClient
import com.amazon.connect.chat.sdk.network.api.AttachmentsInterface
import com.amazon.connect.chat.sdk.network.api.MetricsInterface
import com.amazon.connect.chat.sdk.repository.MetricsManager
import com.amazon.connect.chat.sdk.network.NetworkConnectionManager
import com.amazon.connect.chat.sdk.repository.AttachmentsManager
import com.amazon.connect.chat.sdk.repository.MessageReceiptsManager
import com.amazon.connect.chat.sdk.repository.MessageReceiptsManagerImpl
import com.amazon.connect.chat.sdk.repository.MetricsManager
import com.amazon.connect.chat.sdk.utils.MetricsUtils.getMetricsEndpoint
import com.amazonaws.services.connectparticipant.AmazonConnectParticipantClient
import dagger.Module
Expand All @@ -31,8 +31,6 @@ import javax.inject.Singleton
@InstallIn(SingletonComponent::class)
object NetworkModule {

private const val defaultApiUrl = "https://www.example.com/v1/"

/**
* Provides a singleton instance of OkHttpClient.
*
Expand Down Expand Up @@ -67,14 +65,14 @@ object NetworkModule {
@Provides
@Singleton
fun provideMetricsInterface(retrofitBuilder: Retrofit.Builder): MetricsInterface {
return createService(MetricsInterface::class.java, retrofitBuilder, url=getMetricsEndpoint())
return RetrofitServiceCreator.createService(MetricsInterface::class.java, retrofitBuilder, url = getMetricsEndpoint())
}

/**
* Provides a singleton instance of MetricsInterface.
*
* @param retrofitBuilder The Retrofit.Builder instance for creating the service.
* @return An instance of MetricsInterface.
* @param apiClient The APIClient instance for API operations.
* @return An instance of MetricsManager.
*/
@Provides
@Singleton
Expand All @@ -83,10 +81,11 @@ object NetworkModule {
}

/**
* Provides a singleton instance of MetricsInterface.
*
* @param retrofitBuilder The Retrofit.Builder instance for creating the service.
* @return An instance of MetricsInterface.
* Provides a singleton instance of AttachmentsManager.
* @param context The application context.
* @param awsClient The AWSClient instance for AWS SDK calls.
* @param apiClient The APIClient instance for API operations.
* @return An instance of AttachmentsManager.
*/
@Provides
@Singleton
Expand All @@ -103,7 +102,7 @@ object NetworkModule {
@Provides
@Singleton
fun provideAttachmentsInterface(retrofitBuilder: Retrofit.Builder): AttachmentsInterface {
return createService(AttachmentsInterface::class.java, retrofitBuilder)
return RetrofitServiceCreator.createService(AttachmentsInterface::class.java, retrofitBuilder)
}

/**
Expand Down Expand Up @@ -163,21 +162,4 @@ object NetworkModule {
fun provideMessageReceiptsManager(): MessageReceiptsManager {
return MessageReceiptsManagerImpl()
}

/**
* Creates a Retrofit service for the specified class.
*
* @param T The type of the service.
* @param clazz The class of the service.
* @param retrofitBuilder The Retrofit.Builder instance for creating the service.
* @return An instance of the specified service class.
*/
private fun <T> createService(clazz: Class<T>, retrofitBuilder: Retrofit.Builder, url: String? = null): T {
// Check if the service has an annotation
val apiUrlAnnotation = clazz.annotations.find { it is ApiUrl } as ApiUrl?
// Take the URL value, otherwise use the default
val apiUrl = url ?: (apiUrlAnnotation?.url ?: defaultApiUrl)
// Create the service using the extracted URL
return retrofitBuilder.baseUrl(apiUrl).build().create(clazz)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,15 @@ class AWSClientImpl @Inject constructor(
private val connectParticipantClient: AmazonConnectParticipantClient
) : AWSClient {

companion object {
fun create(): AWSClient {
// Create an AmazonConnectParticipantClient
val connectParticipantClient = AmazonConnectParticipantClient()

return AWSClientImpl(connectParticipantClient)
}
}

override fun configure(config: GlobalConfig) {
connectParticipantClient.setRegion(Region.getRegion(config.region))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import android.net.NetworkRequest
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow

class NetworkConnectionManager private constructor(context: Context) {
class NetworkConnectionManager(context: Context) {

private val connectivityManager: ConnectivityManager =
context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package com.amazon.connect.chat.sdk.network

import com.amazon.connect.chat.sdk.network.api.ApiUrl
import retrofit2.Retrofit

object RetrofitServiceCreator {
private const val defaultApiUrl = "https://www.example.com/v1/"

/**
* Creates a Retrofit service for the specified class.
*
* @param T The type of the service.
* @param clazz The class of the service.
* @param retrofitBuilder The Retrofit.Builder instance for creating the service.
* @return An instance of the specified service class.
*/
fun <T> createService(clazz: Class<T>, retrofitBuilder: Retrofit.Builder, url: String? = null): T {
// Check if the service has an @ApiUrl annotation
val apiUrlAnnotation = clazz.annotations.find { it is ApiUrl } as ApiUrl?
// Take the URL value, otherwise use the default
val apiUrl = url ?: (apiUrlAnnotation?.url ?: defaultApiUrl)
// Create the service using the extracted URL
return retrofitBuilder.baseUrl(apiUrl).build().create(clazz)
}
}
Original file line number Diff line number Diff line change
@@ -1,33 +1,127 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package com.amazon.connect.chat.sdk.provider

import android.content.Context
import com.amazon.connect.chat.sdk.ChatSession
import dagger.hilt.EntryPoint
import dagger.hilt.InstallIn
import com.amazon.connect.chat.sdk.ChatSessionImpl
import com.amazon.connect.chat.sdk.di.ChatModule
import com.amazon.connect.chat.sdk.network.AWSClientImpl
import com.amazon.connect.chat.sdk.network.NetworkConnectionManager
import com.amazon.connect.chat.sdk.network.RetrofitServiceCreator.createService
import com.amazon.connect.chat.sdk.network.WebSocketManagerImpl
import com.amazon.connect.chat.sdk.network.api.APIClient
import com.amazon.connect.chat.sdk.network.api.AttachmentsInterface
import com.amazon.connect.chat.sdk.network.api.MetricsInterface
import com.amazon.connect.chat.sdk.provider.ConnectionDetailsProviderImpl
import com.amazon.connect.chat.sdk.repository.AttachmentsManager
import com.amazon.connect.chat.sdk.repository.ChatServiceImpl
import com.amazon.connect.chat.sdk.repository.MessageReceiptsManagerImpl
import com.amazon.connect.chat.sdk.repository.MetricsManager
import com.amazon.connect.chat.sdk.utils.MetricsUtils.getMetricsEndpoint
import com.amazon.connect.chat.sdk.utils.logger.SDKLogger
import dagger.hilt.android.EntryPointAccessors
import dagger.hilt.components.SingletonComponent

@EntryPoint
@InstallIn(SingletonComponent::class)
interface ChatSessionEntryPoint {
fun getChatSession(): ChatSession
}
import kotlinx.coroutines.Dispatchers
import okhttp3.OkHttpClient
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory

object ChatSessionProvider {
@Volatile
private var chatSession: ChatSession? = null

// Public method for customers to get ChatSession
fun getChatSession(context: Context): ChatSession {
if (chatSession == null) {
val appContext = context.applicationContext
return chatSession ?: synchronized(this) {
chatSession ?: initializeChatSession(context).also {
chatSession = it
}
}
}

// Private method to initialize ChatSession using Hilt or manual fallback
private fun initializeChatSession(context: Context): ChatSession {
return if (isHiltAvailable()) {
// Use Hilt's EntryPoint mechanism if Hilt is available
val entryPoint = EntryPointAccessors.fromApplication(
appContext,
ChatSessionEntryPoint::class.java
context.applicationContext,
ChatModule.ChatSessionEntryPoint::class.java
)
chatSession = entryPoint.getChatSession()
entryPoint.getChatSession()
} else {
// Fallback to manual initialization
createChatSessionManually(context)
}
return chatSession!!
}
}

// Method to check if Hilt is available
private fun isHiltAvailable(): Boolean {
return try {
Class.forName("dagger.hilt.EntryPoints")
true
} catch (e: ClassNotFoundException) {
SDKLogger.logger.logDebug {"Hilt is not available"}
false
}
}

// Manual initialization of ChatSession for non-Hilt users
private fun createChatSessionManually(context: Context): ChatSession {
val appContext = context.applicationContext

// Step 1: Create AWS Client
val awsClient = AWSClientImpl.create()

// Step 2: Create Network Connection Manager
val networkConnectionManager = NetworkConnectionManager(appContext)

// Step 3: Create WebSocket Manager
val webSocketManager = WebSocketManagerImpl(
dispatcher = Dispatchers.IO,
networkConnectionManager = networkConnectionManager
)

// Step 4: Create Retrofit Builder
val retrofitBuilder = createRetrofitBuilder()

// Step 5: Create API Client
val apiClient = createAPIClient(retrofitBuilder)

// Step 6: Create Other Dependencies
val metricsManager = MetricsManager(apiClient)
val attachmentsManager = AttachmentsManager(appContext, awsClient, apiClient)
val messageReceiptsManager = MessageReceiptsManagerImpl()
val connectionDetailsProvider = ConnectionDetailsProviderImpl()

// Step 7: Create ChatService and return ChatSessionImpl
val chatService = ChatServiceImpl(
appContext,
awsClient,
connectionDetailsProvider,
webSocketManager,
metricsManager,
attachmentsManager,
messageReceiptsManager
)

return ChatSessionImpl(chatService)
}

// Helper method to create Retrofit Builder
private fun createRetrofitBuilder(): Retrofit.Builder {
val okHttpClient = OkHttpClient.Builder().build()
return Retrofit.Builder()
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
}

// Helper method to create APIClient
private fun createAPIClient(retrofitBuilder: Retrofit.Builder): APIClient {
val metricsInterface: MetricsInterface = createService(
MetricsInterface::class.java,
retrofitBuilder,
url = getMetricsEndpoint()
)
val attachmentsInterface: AttachmentsInterface = createService(
AttachmentsInterface::class.java,
retrofitBuilder
)
return APIClient(metricsInterface, attachmentsInterface)
}
}
2 changes: 1 addition & 1 deletion chat-sdk/version.properties
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
sdkVersion=0.0.1-alpha
sdkVersion=1.0.1
groupId=software.aws.connect
artifactId=amazon-connect-chat-android
2 changes: 1 addition & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ awsSdkConnectParticipant = { module = "com.amazonaws:aws-android-sdk-connectpart
robolectric = { module = "org.robolectric:robolectric", version.ref = "robolectric" }
serializationJson = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "serializationJson" }
coilCompose = { module = "io.coil-kt:coil-compose", version.ref = "coilCompose" }
chatSdk = { module = "com.amazon.connect.chat:library", version.ref = "chatSdk" }
chatSdk = { module = "software.aws.connect:amazon-connect-chat-android", version.ref = "chatSdk" }
runtimeLivedata = { group = "androidx.compose.runtime", name = "runtime-livedata", version.ref = "runtimeLivedata" }
appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
material = { group = "com.google.android.material", name = "material", version.ref = "material" }
Expand Down
Loading