Skip to content
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
2 changes: 1 addition & 1 deletion app/src/main/java/com/into/websoso/WebsosoApp.kt
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ class WebsosoApp : Application() {

private fun subscribeSessionState() {
sessionManager.sessionExpired.collectWithLifecycle(ProcessLifecycleOwner.get()) {
navigatorProvider.navigateToLoginActivity()
navigatorProvider.navigateToLoginActivity(::startActivity)
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.into.websoso.core.common.util.navigator

import android.content.Context
import android.content.Intent
import com.into.websoso.core.common.navigator.NavigatorProvider
import com.into.websoso.ui.login.LoginActivity
import com.into.websoso.ui.main.MainActivity
Expand All @@ -18,19 +19,19 @@ internal class WebsosoNavigator
constructor(
@ApplicationContext private val context: Context,
) : NavigatorProvider {
override fun navigateToLoginActivity() {
override fun navigateToLoginActivity(startActivity: (Intent) -> Unit) {
val intent = LoginActivity.getIntent(context)
context.startActivity(intent)
startActivity(intent)
}

override fun navigateToMainActivity() {
override fun navigateToMainActivity(startActivity: (Intent) -> Unit) {
val intent = MainActivity.getIntent(context, true)
context.startActivity(intent)
startActivity(intent)
}

override fun navigateToOnboardingActivity() {
override fun navigateToOnboardingActivity(startActivity: (Intent) -> Unit) {
val intent = OnboardingActivity.getIntent(context)
context.startActivity(intent)
startActivity(intent)
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
package com.into.websoso.data.di

import android.content.SharedPreferences
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import com.into.websoso.data.remote.api.AuthApi
import com.into.websoso.data.remote.api.FeedApi
import com.into.websoso.data.remote.api.NovelApi
import com.into.websoso.data.remote.api.PushMessageApi
Expand Down Expand Up @@ -39,13 +37,6 @@ object RepositoryModule {
@Singleton
fun provideNovelRepository(novelApi: NovelApi): NovelRepository = NovelRepository(novelApi)

@Provides
@Singleton
fun provideAuthRepository(
authApi: AuthApi,
preferences: SharedPreferences,
): AuthRepository = AuthRepository(authApi, preferences)

@Provides
@Singleton
fun provideVersionRepository(versionApi: VersionApi): VersionRepository = VersionRepository(versionApi)
Expand Down
4 changes: 0 additions & 4 deletions app/src/main/java/com/into/websoso/data/remote/api/AuthApi.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,22 @@ import com.into.websoso.data.remote.request.UserProfileRequestDto
import com.into.websoso.data.remote.response.UserNicknameValidityResponseDto
import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.Header
import retrofit2.http.POST
import retrofit2.http.Query

interface AuthApi {
@GET("users/nickname/check")
suspend fun getNicknameValidity(
@Header("Authorization") authorization: String,
@Query("nickname") nickname: String,
): UserNicknameValidityResponseDto

@POST("users/profile")
suspend fun postUserProfile(
@Header("Authorization") authorization: String,
@Body userProfileRequestDto: UserProfileRequestDto,
)

@POST("users/fcm-token")
suspend fun postFCMToken(
@Header("Authorization") authorization: String,
@Body fcmTokenRequestDto: FCMTokenRequestDto,
)
}
Original file line number Diff line number Diff line change
@@ -1,75 +1,39 @@
package com.into.websoso.data.repository

import android.content.SharedPreferences
import com.into.websoso.data.remote.api.AuthApi
import com.into.websoso.data.remote.request.FCMTokenRequestDto
import com.into.websoso.data.remote.request.UserProfileRequestDto
import javax.inject.Inject
import javax.inject.Singleton

@Singleton
class AuthRepository
@Inject
constructor(
private val authApi: AuthApi,
private val authStorage: SharedPreferences,
) {
var accessToken: String
get() = authStorage.getString(ACCESS_TOKEN_KEY, "").orEmpty()
private set(value) = authStorage.edit().putString(ACCESS_TOKEN_KEY, value).apply()

var refreshToken: String
get() = authStorage.getString(REFRESH_TOKEN_KEY, "").orEmpty()
private set(value) = authStorage.edit().putString(REFRESH_TOKEN_KEY, value).apply()

var isAutoLogin: Boolean
get() = authStorage.getBoolean(AUTO_LOGIN_KEY, false)
private set(value) = authStorage.edit().putBoolean(AUTO_LOGIN_KEY, value).apply()

suspend fun fetchNicknameValidity(
authorization: String,
nickname: String,
): Boolean = authApi.getNicknameValidity("Bearer $authorization", nickname).isValid
suspend fun fetchNicknameValidity(nickname: String): Boolean = authApi.getNicknameValidity(nickname).isValid

suspend fun signUp(
authorization: String,
nickname: String,
gender: String,
birth: Int,
genrePreferences: List<String>,
) {
authApi.postUserProfile(
"Bearer $authorization",
UserProfileRequestDto(nickname, gender, birth, genrePreferences),
)
}

fun updateAccessToken(accessToken: String) {
this.accessToken = accessToken
}

fun updateRefreshToken(refreshToken: String) {
this.refreshToken = refreshToken
}

fun updateIsAutoLogin(isAutoLogin: Boolean) {
this.isAutoLogin = isAutoLogin
}

suspend fun saveFCMToken(
fcmToken: String,
deviceIdentifier: String,
) {
authApi.postFCMToken(
authorization = "Bearer $accessToken",
FCMTokenRequestDto(
fcmToken = fcmToken,
deviceIdentifier = deviceIdentifier,
),
)
}

companion object {
private const val ACCESS_TOKEN_KEY = "ACCESS_TOKEN"
private const val REFRESH_TOKEN_KEY = "REFRESH_TOKEN"
private const val AUTO_LOGIN_KEY = "AUTO_LOGIN"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ class LogoutDialogFragment : BaseDialogFragment<DialogLogoutBinding>(R.layout.di
private fun collectUiEffect() {
accountInfoViewModel.uiEffect.collectWithLifecycle(viewLifecycleOwner) { uiEffect ->
when (uiEffect) {
UiEffect.NavigateToLogin -> websosoNavigator.navigateToLoginActivity()
UiEffect.NavigateToLogin -> websosoNavigator.navigateToLoginActivity(::startActivity)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,6 @@ class OnboardingActivity : BaseActivity<ActivityOnboardingBinding>(R.layout.acti
companion object {
private const val ANIMATION_PROPERTY_NAME = "progress"
private const val ANIMATION_DURATION_TIME = 200L
const val ACCESS_TOKEN_KEY = "ACCESS_TOKEN"
const val REFRESH_TOKEN_KEY = "REFRESH_TOKEN"

fun getIntent(context: Context): Intent = Intent(context, OnboardingActivity::class.java)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,12 @@ package com.into.websoso.ui.onboarding

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.into.websoso.data.repository.AuthRepository
import com.into.websoso.domain.model.NicknameValidationResult
import com.into.websoso.domain.usecase.CheckNicknameValidityUseCase
import com.into.websoso.domain.usecase.ValidateNicknameUseCase
import com.into.websoso.ui.onboarding.OnboardingActivity.Companion.ACCESS_TOKEN_KEY
import com.into.websoso.ui.onboarding.OnboardingActivity.Companion.REFRESH_TOKEN_KEY
import com.into.websoso.ui.onboarding.first.model.NicknameInputType
import com.into.websoso.ui.onboarding.first.model.OnboardingFirstUiState
import com.into.websoso.ui.onboarding.model.OnboardingPage
Expand All @@ -27,7 +24,6 @@ class OnboardingViewModel
private val authRepository: AuthRepository,
private val validateNicknameUseCase: ValidateNicknameUseCase,
private val checkNicknameValidityUseCase: CheckNicknameValidityUseCase,
private val savedStateHandle: SavedStateHandle,
) : ViewModel() {
private val _currentPage = MutableLiveData(OnboardingPage.FIRST)
val currentPage: LiveData<OnboardingPage> get() = _currentPage
Expand Down Expand Up @@ -60,18 +56,6 @@ class OnboardingViewModel
private val _isUserProfileSubmit = MutableLiveData<Boolean>(false)
val isUserProfileSubmit: LiveData<Boolean> get() = _isUserProfileSubmit

var accessToken: String
get() = savedStateHandle[ACCESS_TOKEN_KEY] ?: ""
private set(value) {
savedStateHandle[ACCESS_TOKEN_KEY] = value
}

var refreshToken: String
get() = savedStateHandle[REFRESH_TOKEN_KEY] ?: ""
private set(value) {
savedStateHandle[REFRESH_TOKEN_KEY] = value
}

fun validateNickname() {
val currentInput: String = currentNicknameInput.value.orEmpty()
if (currentInput.isEmpty()) {
Expand Down Expand Up @@ -113,7 +97,7 @@ class OnboardingViewModel
private fun dispatchNicknameDuplication(nickname: String) {
viewModelScope.launch {
runCatching {
authRepository.fetchNicknameValidity(accessToken, nickname)
authRepository.fetchNicknameValidity(nickname)
}.onSuccess { isNicknameValid ->
Comment on lines 98 to 101
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

네트워크 호출은 IO 디스패처에서 수행하도록 분리하세요
viewModelScope.launch의 기본 디스패처는 Dispatchers.Main이기 때문에 현재 코드에서는 메인 스레드에서 API 호출이 실행될 수 있습니다. 레트로핏이 자체적으로 스레드 전환을 하도록 설정돼 있지 않다면 ANR 및 UI 지연이 발생할 여지가 있습니다.

아래와 같이 withContext(Dispatchers.IO)로 IO 스레드에서 실행되게 하시는 것을 권장드립니다.

             viewModelScope.launch {
-                runCatching {
-                    authRepository.fetchNicknameValidity(nickname)
-                }.onSuccess { isNicknameValid ->
+                runCatching {
+                    withContext(Dispatchers.IO) {
+                        authRepository.fetchNicknameValidity(nickname)
+                    }
+                }.onSuccess { isNicknameValid ->

추가로, withContext 사용 시 다음 import 가 필요합니다.

import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
🤖 Prompt for AI Agents
In app/src/main/java/com/into/websoso/ui/onboarding/OnboardingViewModel.kt
around lines 98 to 101, the network call inside viewModelScope.launch is running
on the main thread by default, which can cause UI delays or ANRs. To fix this,
wrap the network call authRepository.fetchNicknameValidity(nickname) inside
withContext(Dispatchers.IO) to ensure it runs on the IO dispatcher. Also, add
the necessary imports for Dispatchers and withContext.

when (isNicknameValid) {
true -> {
Expand Down Expand Up @@ -237,17 +221,13 @@ class OnboardingViewModel
)
userProfile.value?.let { profile ->
authRepository.signUp(
authorization = accessToken,
nickname = profile.nickname,
gender = profile.gender,
birth = profile.birthYear,
genrePreferences = profile.genrePreferences,
)
}
}.onSuccess {
authRepository.updateAccessToken(accessToken)
authRepository.updateRefreshToken(refreshToken)
authRepository.updateIsAutoLogin(true)
_isUserProfileSubmit.value = true
}.onFailure { exception ->
exception.printStackTrace()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ class SplashActivity : AppCompatActivity(R.layout.activity_splash) {
private fun collectUiEffect() {
splashViewModel.uiEffect.collectWithLifecycle(this) { uiEffect ->
when (uiEffect) {
NavigateToLogin -> websosoNavigator.navigateToLoginActivity()
NavigateToMain -> websosoNavigator.navigateToMainActivity()
NavigateToLogin -> websosoNavigator.navigateToLoginActivity(::startActivity)
NavigateToMain -> websosoNavigator.navigateToMainActivity(::startActivity)
ShowDialog -> showMinimumVersionDialog()
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ class WithdrawSecondActivity : BaseActivity<ActivityWithdrawSecondBinding>(activ
}

withdrawSecondViewModel.isWithDrawSuccess.observe(this) { isWithdrawSuccess ->
if (isWithdrawSuccess) websosoNavigator.navigateToLoginActivity()
if (isWithdrawSuccess) websosoNavigator.navigateToLoginActivity(::startActivity)
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
package com.into.websoso.core.common.navigator

import android.content.Intent
import dagger.hilt.EntryPoint
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent

interface NavigatorProvider {
fun navigateToLoginActivity()
fun navigateToLoginActivity(startActivity: (Intent) -> Unit)

fun navigateToMainActivity()
fun navigateToMainActivity(startActivity: (Intent) -> Unit)

fun navigateToOnboardingActivity()
fun navigateToOnboardingActivity(startActivity: (Intent) -> Unit)
}

@EntryPoint
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
Expand All @@ -36,6 +37,7 @@ fun SignInScreen(
websosoNavigator: NavigatorProvider,
signInViewModel: SignInViewModel = hiltViewModel(),
) {
val context = LocalContext.current
val latestEvent by rememberUpdatedState(signInViewModel.uiEvent)
val pagerState = rememberPagerState { Onboarding_Images.size }

Expand All @@ -51,9 +53,9 @@ fun SignInScreen(
// TODO: 실패 시 커스텀 스낵 바 구현
}

NavigateToHome -> websosoNavigator.navigateToMainActivity()
NavigateToHome -> websosoNavigator.navigateToMainActivity(context::startActivity)

NavigateToOnboarding -> websosoNavigator.navigateToOnboardingActivity()
NavigateToOnboarding -> websosoNavigator.navigateToOnboardingActivity(context::startActivity)
}
}

Expand Down