diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 8b7ac1cd..a2676a4d 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -11,8 +11,8 @@ android { namespace = "com.idle.care" defaultConfig { - versionCode = 20 - versionName = "1.2.5" + versionCode = 21 + versionName = "1.2.6" targetSdk = 34 testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" diff --git a/app/release/app-release.aab b/app/release/app-release.aab new file mode 100644 index 00000000..d3828047 Binary files /dev/null and b/app/release/app-release.aab differ diff --git a/core/data/src/main/java/com/idle/data/repository/chatting/ChattingRepositoryImpl.kt b/core/data/src/main/java/com/idle/data/repository/chatting/ChattingRepositoryImpl.kt index d2bada36..fcef22ba 100644 --- a/core/data/src/main/java/com/idle/data/repository/chatting/ChattingRepositoryImpl.kt +++ b/core/data/src/main/java/com/idle/data/repository/chatting/ChattingRepositoryImpl.kt @@ -17,7 +17,7 @@ class ChattingRepositoryImpl @Inject constructor( webSocketDataSource.disconnectWebSocket() override fun subscribeChatMessage(): Flow = - webSocketDataSource.getChatMessageFlow() + webSocketDataSource.chatMessageFlow .filterNotNull() .map { it.toVO() } -} \ No newline at end of file +} diff --git a/core/network/src/main/java/com/idle/network/di/RetrofitModule.kt b/core/network/src/main/java/com/idle/network/di/RetrofitModule.kt index 2d5c9984..ebb4d37d 100644 --- a/core/network/src/main/java/com/idle/network/di/RetrofitModule.kt +++ b/core/network/src/main/java/com/idle/network/di/RetrofitModule.kt @@ -65,6 +65,13 @@ object RetrofitModule { fun provideWebSocketOkHttpClient(): OkHttpClient = OkHttpClient.Builder() .connectTimeout(10, TimeUnit.SECONDS) .readTimeout(0, TimeUnit.MILLISECONDS) + .apply { + if (BuildConfig.DEBUG) { + val loggingInterceptor = HttpLoggingInterceptor() + loggingInterceptor.level = HttpLoggingInterceptor.Level.BODY + addInterceptor(loggingInterceptor) + } + } .build() @Singleton @@ -122,4 +129,4 @@ annotation class AuthOkHttpClient @Qualifier @Retention(AnnotationRetention.BINARY) -annotation class WebSocketOkHttpClient \ No newline at end of file +annotation class WebSocketOkHttpClient diff --git a/core/network/src/main/java/com/idle/network/source/websocket/CareWebSocketListener.kt b/core/network/src/main/java/com/idle/network/source/websocket/CareWebSocketListener.kt index 117e838f..caa6d5a7 100644 --- a/core/network/src/main/java/com/idle/network/source/websocket/CareWebSocketListener.kt +++ b/core/network/src/main/java/com/idle/network/source/websocket/CareWebSocketListener.kt @@ -36,11 +36,7 @@ class ChatMessageListener @Inject constructor(private val json: Json) : WebSocke _chatMessageChannel.value = chatMessageResponse } - override fun onOpen(webSocket: WebSocket, response: Response) { - super.onOpen(webSocket, response) - } - override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) { super.onFailure(webSocket, t, response) } -} \ No newline at end of file +} diff --git a/core/network/src/main/java/com/idle/network/source/websocket/WebSocketDataSource.kt b/core/network/src/main/java/com/idle/network/source/websocket/WebSocketDataSource.kt index ef9d39af..733c241b 100644 --- a/core/network/src/main/java/com/idle/network/source/websocket/WebSocketDataSource.kt +++ b/core/network/src/main/java/com/idle/network/source/websocket/WebSocketDataSource.kt @@ -1,34 +1,47 @@ package com.idle.network.source.websocket -import com.idle.domain.model.error.ErrorHelper import com.idle.network.BuildConfig import com.idle.network.di.WebSocketOkHttpClient import com.idle.network.model.chatting.ChatMessageResponse +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.withContext import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.WebSocket import javax.inject.Inject import javax.inject.Singleton +import kotlin.math.pow @Singleton class WebSocketDataSource @Inject constructor( @WebSocketOkHttpClient private val client: OkHttpClient, private val chatMessageListener: ChatMessageListener, - private val errorHelper: ErrorHelper, ) { + val chatMessageFlow: StateFlow = chatMessageListener.chatMessageFlow + private lateinit var chatMessageWebSocket: WebSocket + private var connectionAttempts = 0 - fun connectWebSocket(): Result { - return try { + suspend fun connectWebSocket(): Result = withContext(Dispatchers.IO) { + try { val chatMessageRequest: Request = Request.Builder() .url(BuildConfig.CARE_WEBSOCKET_URL) .build() chatMessageWebSocket = client.newWebSocket(chatMessageRequest, chatMessageListener) + connectionAttempts = 0 Result.success(Unit) } catch (e: Exception) { - Result.failure(e) + if (connectionAttempts < MAX_RETRY_ATTEMPTS) { + val waitTime = minOf(calculateBackoffTime(connectionAttempts), MAX_WAIT_TIME) + delay(waitTime) + connectionAttempts++ + connectWebSocket() + } else { + Result.failure(e) + } } } @@ -43,5 +56,11 @@ class WebSocketDataSource @Inject constructor( } } - fun getChatMessageFlow(): StateFlow = chatMessageListener.chatMessageFlow -} \ No newline at end of file + private fun calculateBackoffTime(attempt: Int): Long = + (2.0.pow(attempt) * 1000).toLong() + + companion object { + private const val MAX_RETRY_ATTEMPTS = 5 + private const val MAX_WAIT_TIME = 10_000L // 10 seconds + } +} diff --git a/presentation/src/main/java/com/idle/presentation/MainViewModel.kt b/presentation/src/main/java/com/idle/presentation/MainViewModel.kt index fa3a9b70..eebd367a 100644 --- a/presentation/src/main/java/com/idle/presentation/MainViewModel.kt +++ b/presentation/src/main/java/com/idle/presentation/MainViewModel.kt @@ -1,5 +1,6 @@ package com.idle.presentation +import android.util.Log import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.idle.analytics.error.ErrorLoggingHelper @@ -16,6 +17,8 @@ import com.idle.domain.model.profile.CenterManagerAccountStatus import com.idle.domain.repositorry.jobposting.JobPostingRepository import com.idle.domain.usecase.auth.GetAccessTokenUseCase import com.idle.domain.usecase.auth.GetUserTypeUseCase +import com.idle.domain.usecase.chatting.ConnectWebSocketUseCase +import com.idle.domain.usecase.chatting.DisconnectWebSocketUseCase import com.idle.domain.usecase.config.GetForceUpdateInfoUseCase import com.idle.domain.usecase.notification.ReadNotificationUseCase import com.idle.domain.usecase.profile.GetCenterStatusUseCase @@ -47,8 +50,8 @@ class MainViewModel @Inject constructor( private val getCenterStatusUseCase: GetCenterStatusUseCase, private val readNotificationUseCase: ReadNotificationUseCase, private val jobPostingRepository: JobPostingRepository, -// private val connectWebSocketUseCase: ConnectWebSocketUseCase, -// private val disconnectWebSocketUseCase: DisconnectWebSocketUseCase, + private val connectWebSocketUseCase: ConnectWebSocketUseCase, + private val disconnectWebSocketUseCase: DisconnectWebSocketUseCase, private val errorHelper: ErrorHelper, private val eventHelper: EventHelper, val errorLoggingHelper: ErrorLoggingHelper, @@ -71,15 +74,16 @@ class MainViewModel @Inject constructor( } internal fun connectWebSocket() = viewModelScope.launch { -// Log.d("test", "웹소켓 연결") -// connectWebSocketUseCase().onSuccess { } -// .onFailure { } + Log.d("test", "웹소켓 연결") + connectWebSocketUseCase().onFailure { + Log.d("test", it.stackTraceToString()) + } } internal fun disconnectWebSocket() = viewModelScope.launch { -// Log.d("test", "웹소켓 연결해제") -// disconnectWebSocketUseCase().onSuccess { } -// .onFailure { } + Log.d("test", "웹소켓 연결해제") + disconnectWebSocketUseCase().onSuccess { } + .onFailure { } } internal fun setNavigationMenuType(navigationMenuType: NavigationMenuType) {