Skip to content

Feature/kakao login#27

Merged
hyunjung-choi merged 7 commits intodevelopfrom
feature/kakao-login
Jul 10, 2025
Merged

Feature/kakao login#27
hyunjung-choi merged 7 commits intodevelopfrom
feature/kakao-login

Conversation

@hyunjung-choi
Copy link
Copy Markdown
Member

@hyunjung-choi hyunjung-choi commented Jul 9, 2025

PULL REQUEST

Description

  • Implement Kakao login

Summary by CodeRabbit

  • New Features

    • Added social login support for Kakao, Naver, and Google with secure token management and encrypted storage.
    • Integrated Kakao SDK authentication and added related UI flows including splash and login screens.
    • Introduced state-driven navigation and login UI with loading indicators and error feedback.
    • Added dependency injection modules for authentication and network components.
    • Implemented a comprehensive network client with error handling and automatic token refresh.
    • Added new domain, data, and network models for authentication and user data.
  • Improvements

    • Enhanced build configuration with secret management, build constants, and network/security permissions.
    • Refined user experience with reactive UI updates and improved error handling.
  • Bug Fixes

    • Unified error and result handling across modules.
  • Chores

    • Updated dependencies and build scripts for Kakao SDK, Koin, Timber, and modularization.
    • Added new core modules for common utilities and networking.
    • Cleaned up project structure and reorganized dependency declarations.

@hyunjung-choi hyunjung-choi self-assigned this Jul 9, 2025
@hyunjung-choi hyunjung-choi added the feat 새로운 기능 label Jul 9, 2025
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Jul 9, 2025

Walkthrough

This update introduces a comprehensive authentication infrastructure using social login (Kakao, Naver, Google) with secure token management and dependency injection. It adds new modules and classes for domain, data, and network layers, implements encrypted token storage, integrates the Kakao SDK, and refactors the login UI to use state-driven ViewModel logic and navigation. Gradle scripts and dependencies are updated to support these features.

Changes

File(s) / Path(s) Change Summary
app/build.gradle.kts, core/network/build.gradle.kts, core/data/build.gradle.kts, core/domain/build.gradle.kts, core/model/build.gradle.kts, feature/auth/build.gradle.kts, core/common/build.gradle.kts Updated build scripts: added secret management, dependencies for Kakao SDK, Koin, network, common, and domain modules; refactored plugin usage.
app/src/main/AndroidManifest.xml Added permissions, Kakao Auth activity, and application class attribute.
app/src/main/java/com/hyunjung/cherrydan/CherrydanApplication.kt New Application class for initializing Timber, Kakao SDK, and Koin modules.
app/src/main/java/com/hyunjung/cherrydan/MainActivity.kt Refactored to support key hash logging, conditional navigation, and login flow.
app/src/main/java/com/hyunjung/cherrydan/navigation/AppNavigation.kt New navigation graph for splash, login, and home screens.
build-logic/convention/src/main/kotlin/com/hyunjung/cherrydan/BuildTypes.kt Simplified BASE_URL config; removed dynamic retrieval logic.
build-logic/convention/src/main/kotlin/com/hyunjung/cherrydan/ComposeDependencies.kt UI layer now depends on domain module.
core/common/.gitignore Ignore build directory.
core/common/src/main/java/com/hyunjung/core/common/util/DataError.kt, core/common/src/main/java/com/hyunjung/core/common/util/Result.kt, core/presentation/ui/src/main/java/com/hyunjung/core/presentation/ui/DataErrorToText.kt, core/data/src/main/java/com/hyunjung/core/data/networking/HttpClientExt.kt Changed package imports from domain to common.
core/common/src/main/java/com/hyunjung/core/common/util/Error.kt New empty Error interface.
core/common/src/main/java/com/hyunjung/core/common/util/Result.kt Added onSuccess extension; updated Error reference.
core/data/src/main/java/com/hyunjung/core/data/di/DataModule.kt New Koin module binding AuthRepository to implementation.
core/data/src/main/java/com/hyunjung/core/data/repository/AuthRepositoryImpl.kt New implementation for social login and token management.
core/domain/src/main/java/com/hyunjung/core/domain/repository/AuthRepository.kt New interface for login via social providers.
core/domain/src/main/java/com/hyunjung/core/domain/util/Error.kt, core/model/src/main/java/com/hyunjung/core/model/MyClass.kt, feature/auth/src/main/java/com/hyunjung/feature/auth/login/LogInAction.kt Deleted obsolete or replaced files.
core/model/src/main/java/com/hyunjung/core/model/AuthTokens.kt, core/model/src/main/java/com/hyunjung/core/model/LoginResult.kt, core/model/src/main/java/com/hyunjung/core/model/SocialType.kt, core/model/src/main/java/com/hyunjung/core/model/User.kt Added new data classes and enum for user, tokens, login result, and social types.
core/network/src/main/java/com/hyunjung/core/network/datasource/AuthRemoteDataSource.kt, core/network/src/main/java/com/hyunjung/core/network/datasource/AuthRemoteDataSourceImpl.kt, core/network/src/main/java/com/hyunjung/core/network/datasource/KakaoAuthDataSource.kt, core/network/src/main/java/com/hyunjung/core/network/datasource/SocialAuthDataSource.kt New interfaces and implementations for social and remote authentication.
core/network/src/main/java/com/hyunjung/core/network/di/NetworkModule.kt New Koin module for providing network components, token manager, and HTTP client.
core/network/src/main/java/com/hyunjung/core/network/model/ApiResponse.kt, core/network/src/main/java/com/hyunjung/core/network/model/LoginResponse.kt, core/network/src/main/java/com/hyunjung/core/network/model/LoginResultResponse.kt, core/network/src/main/java/com/hyunjung/core/network/model/SocialLoginRequestResponse.kt, core/network/src/main/java/com/hyunjung/core/network/model/TokenResponse.kt Added serializable data models for API communication.
core/network/src/main/java/com/hyunjung/core/network/resource/AuthResource.kt New resource classes for REST API endpoints.
core/network/src/main/java/com/hyunjung/core/network/token/AuthTokenManager.kt, core/network/src/main/java/com/hyunjung/core/network/token/TokenManager.kt New token manager interface and encrypted implementation.
core/network/src/main/java/com/hyunjung/core/network/util/HttpClientExt.kt Utility extensions for HTTP client with error handling.
feature/auth/src/main/java/com/hyunjung/feature/auth/di/AuthViewModelModule.kt New Koin module for LogInViewModel.
feature/auth/src/main/java/com/hyunjung/feature/auth/login/LogInScreen.kt Refactored to use ViewModel, state-driven UI, and error handling.
feature/auth/src/main/java/com/hyunjung/feature/auth/login/LogInState.kt New data class for login state.
feature/auth/src/main/java/com/hyunjung/feature/auth/login/LogInViewModel.kt New ViewModel and UI state for login logic.
gradle/libs.versions.toml Added Kakao, Firebase, and Ktor resources dependencies.
settings.gradle.kts Added Kakao Maven repository and reordered module includes.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant MainActivity
    participant LogInViewModel
    participant AuthRepository
    participant SocialAuthDataSource
    participant AuthRemoteDataSource
    participant TokenManager

    User->>MainActivity: Launch app
    MainActivity->>MainActivity: Check isLoggedIn
    alt Not logged in
        MainActivity->>LogInViewModel: Show login screen
        User->>LogInViewModel: Click social login (Kakao/Naver/Google)
        LogInViewModel->>AuthRepository: login(context, socialType)
        AuthRepository->>SocialAuthDataSource: login(context)
        SocialAuthDataSource-->>AuthRepository: Result<AuthTokens, DataError>
        alt AuthTokens success
            AuthRepository->>AuthRemoteDataSource: loginWithSocial(socialType, accessToken)
            AuthRemoteDataSource-->>AuthRepository: Result<LoginResult, DataError>
            alt LoginResult success
                AuthRepository->>TokenManager: saveTokens(accessToken, refreshToken)
                AuthRepository-->>LogInViewModel: Result<LoginResult, DataError>
                LogInViewModel-->>MainActivity: Update UI state (Success)
                MainActivity->>MainActivity: Navigate to Home
            else LoginResult error
                AuthRepository-->>LogInViewModel: Error
            end
        else AuthTokens error
            AuthRepository-->>LogInViewModel: Error
        end
    else Already logged in
        MainActivity->>MainActivity: Navigate to Home
    end
Loading

Suggested labels

refactor

Poem

In fields of code where rabbits roam,
New login flows now call home.
With tokens locked and secrets tight,
Kakao hops in, day and night.
Koin and Timber join the crew—
Secure and swift, the garden grew!
🥕✨


🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@hyunjung-choi hyunjung-choi linked an issue Jul 9, 2025 that may be closed by this pull request
12 tasks
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 19

🧹 Nitpick comments (29)
core/presentation/ui/src/main/java/com/hyunjung/core/presentation/ui/DataErrorToText.kt (1)

35-37: Remove the else to keep the when exhaustive
Because DataError is a sealed hierarchy, the compiler can warn you when a new subtype is added—​but only if there is no catch-all branch.

-        else -> UiText.StringResource(
-            R.string.error_unknown
-        )
+        // No else: compiler forces exhaustive match and reveals missing cases
feature/auth/build.gradle.kts (1)

9-11: Scope Timber to debug builds
Shipping Timber in release APKs adds a useless dependency and potential log-leaks. Use debugImplementation.

-    implementation(libs.timber)
+    debugImplementation(libs.timber)
core/model/src/main/java/com/hyunjung/core/model/SocialType.kt (1)

3-5: Consider adding a fall-back enum value (e.g. UNKNOWN)

Defensive parsing of server payloads or deep-link parameters is easier when an unmapped value can be routed to UNKNOWN rather than crashing with IllegalArgumentException.
If you decide to add it later, it will be a binary-breaking change; adding it now avoids that risk.

core/model/src/main/java/com/hyunjung/core/model/LoginResult.kt (1)

3-6: Mark the data class as @Immutable for Compose stability

LoginResult is likely exposed via StateFlow/LiveData to a Compose UI.
Annotating with androidx.compose.runtime.Immutable lets the Compose runtime skip deep object comparison during recomposition, improving performance.

-import package ...
+import androidx.compose.runtime.Immutable
+
+@Immutable
 data class LoginResult(
     val user: User,
     val tokens: AuthTokens
 )
core/model/src/main/java/com/hyunjung/core/model/AuthTokens.kt (1)

3-6: Potential value-class optimisation

If you foresee extra token metadata, keep as-is.
Otherwise consider an @JvmInline value class AccessToken(val value: String) & RefreshToken for stronger type-safety and zero-cost wrappers.

core/model/src/main/java/com/hyunjung/core/model/User.kt (1)

3-8: Provide sane defaults for nullable strings

Down-stream UI often does text = user.nickname ?: "" repeatedly.
Supplying default "" here centralises that logic and removes null-checks across the codebase.

 data class User(
     val userId: Long,
-    val email: String?,
-    val nickname: String?,
-    val profileImage: String?
+    val email: String? = null,
+    val nickname: String? = null,
+    val profileImage: String? = null
 )
settings.gradle.kts (1)

20-21: Pin repository to HTTPS & restrict group

Good: HTTPS is already used.
Safer: restrict to only Kakao groups to reduce supply-chain risk.

maven {
    url = uri("https://devrepo.kakao.com/nexus/content/groups/public/")
    content { includeGroupByRegex("com\\.kakao.*") }
}
core/network/src/main/java/com/hyunjung/core/network/model/TokenResponse.kt (1)

1-12: LGTM! Consider security best practices for token handling.

The data class implementation is clean and follows Kotlin serialization best practices. The JSON mapping with @SerialName is appropriate for API integration.

Consider implementing the following security enhancements at the usage level:

  • Ensure tokens are never logged or exposed in debug outputs
  • Implement secure storage mechanisms for these tokens
  • Consider token expiration validation
core/network/src/main/java/com/hyunjung/core/network/datasource/SocialAuthDataSource.kt (1)

8-10: Well-designed interface with good error handling.

The interface follows clean architecture principles with proper async handling using suspend functions and Result types for error management.

Consider adding:

  • KDoc documentation explaining the contract
  • Generic type parameter bounds for better type safety
  • Alternative without Context dependency for improved testability
+/**
+ * Interface for social authentication data sources.
+ * Provides login functionality for various social platforms.
+ */
interface SocialAuthDataSource {
+    /**
+     * Performs social login authentication.
+     * @param context Android context required for social SDK operations
+     * @return Result containing AuthTokens on success or DataError on failure
+     */
    suspend fun login(context: Context): Result<AuthTokens, DataError>
}
feature/auth/src/main/java/com/hyunjung/feature/auth/login/LogInState.kt (1)

5-10: Well-designed state class with good default values.

The state class follows UI state management best practices with clear, descriptive properties and sensible defaults.

Consider adding state validation to ensure mutual exclusivity:

  • isLoading should typically be false when isLoggedIn is true
  • errorMessage should be null when isLoggedIn is true
data class LogInState(
    val isLoading: Boolean = false,
    val isLoggedIn: Boolean = false,
    val user: User? = null,
    val errorMessage: String? = null
+) {
+    init {
+        require(!(isLoggedIn && isLoading)) { "Cannot be both logged in and loading" }
+        require(!(isLoggedIn && errorMessage != null)) { "Cannot be logged in with error message" }
+    }
core/network/src/main/java/com/hyunjung/core/network/model/SocialLoginRequestResponse.kt (1)

6-10: Consider renaming the class for clarity.

The class name SocialLoginRequestResponse suggests it handles both request and response data, but it appears to be used only for request payloads. Consider renaming to SocialLoginRequest for better clarity.

@Serializable
-data class SocialLoginRequestResponse(
+data class SocialLoginRequest(
    val accessToken: String,
    val fcmToken: String? = null,
    val deviceType: String = "android"
)
core/network/src/main/java/com/hyunjung/core/network/token/TokenManager.kt (1)

3-8: Consider making token operations async for better performance.

Token operations typically involve I/O (SharedPreferences, encryption) and should be async to avoid blocking the main thread.

interface TokenManager {
-    fun getAccessToken(): String?
-    fun getRefreshToken(): String?
-    fun saveTokens(accessToken: String, refreshToken: String)
-    fun clearTokens()
+    suspend fun getAccessToken(): String?
+    suspend fun getRefreshToken(): String?
+    suspend fun saveTokens(accessToken: String, refreshToken: String)
+    suspend fun clearTokens()
}
app/src/main/java/com/hyunjung/cherrydan/CherrydanApplication.kt (1)

22-28: Consider removing sensitive key logging in production.

While the logging helps with debugging, consider removing or masking the Kakao app key in logs to prevent potential security issues.

// 카카오 앱키 확인
val kakaoAppKey = BuildConfig.KAKAO_NATIVE_APP_KEY
-Timber.d("Kakao App Key: $kakaoAppKey")
+Timber.d("Kakao App Key: ${if (kakaoAppKey.isNotEmpty()) "***configured***" else "missing"}")

if (kakaoAppKey.isEmpty()) {
    Timber.e("Kakao App Key가 설정되지 않았습니다!")
}
core/network/src/main/java/com/hyunjung/core/network/token/AuthTokenManager.kt (3)

26-31: Simplify the chained edit syntax.

The chained putString() calls can be simplified for better readability.

-        sharedPreferences.edit {
-            putString(ACCESS_TOKEN_KEY, accessToken)
-                .putString(REFRESH_TOKEN_KEY, refreshToken)
-        }
+        sharedPreferences.edit {
+            putString(ACCESS_TOKEN_KEY, accessToken)
+            putString(REFRESH_TOKEN_KEY, refreshToken)
+        }

42-46: Simplify the chained edit syntax.

The chained remove() calls can be simplified for better readability.

-        sharedPreferences.edit {
-            remove(ACCESS_TOKEN_KEY)
-                .remove(REFRESH_TOKEN_KEY)
-        }
+        sharedPreferences.edit {
+            remove(ACCESS_TOKEN_KEY)
+            remove(REFRESH_TOKEN_KEY)
+        }

48-51: Consider adding hasValidTokens() to TokenManager interface.

The hasValidTokens() method is useful but not declared in the TokenManager interface. Consider adding it to the interface for consistency and better abstraction.

Alternatively, document why this method is implementation-specific if it's intentionally not part of the interface contract.

app/src/main/java/com/hyunjung/cherrydan/navigation/AppNavigation.kt (2)

18-18: Consider using sealed classes for navigation routes.

String-based navigation routes are prone to typos and hard to maintain. Consider defining a sealed class for routes to improve type safety.

sealed class AppRoute(val route: String) {
    object Splash : AppRoute("splash")
    object Login : AppRoute("login")
    object Home : AppRoute("home")
}

Then use startDestination = AppRoute.Splash.route and AppRoute.Login.route instead of string literals.


56-61: Remove commented-out code.

The commented-out code should be removed to keep the codebase clean. If these are placeholders for future implementation, consider using TODO comments instead.

-//        authGraph(navController)
-//
-//        mainGraph(navController)
-//
-//        profileGraph(navController)
build-logic/convention/src/main/kotlin/com/hyunjung/cherrydan/BuildTypes.kt (1)

54-54: Clarify BASE_URL handling for multiple environments

  • File: build-logic/convention/src/main/kotlin/com/hyunjung/cherrydan/BuildTypes.kt
    Lines: 54 & 62

Current code:

buildConfigField("String", "BASE_URL", "\"https://cherrydan.com\"")

This hardcodes the same endpoint for both debug and release. Consider introducing environment-specific values:

• Per build type:

buildTypes {
    debug {
        buildConfigField("String", "BASE_URL", "\"https://dev.cherrydan.com\"")
    }
    release {
        buildConfigField("String", "BASE_URL", "\"https://cherrydan.com\"")
    }
}

• Or via product flavors:

flavorDimensions("environment")
productFlavors {
    dev {
        dimension "environment"
        buildConfigField("String", "BASE_URL", "\"https://dev.cherrydan.com\"")
    }
    staging {
        dimension "environment"
        buildConfigField("String", "BASE_URL", "\"https://staging.cherrydan.com\"")
    }
    prod {
        dimension "environment"
        buildConfigField("String", "BASE_URL", "\"https://cherrydan.com\"")
    }
}

Then reference BuildConfig.BASE_URL in your networking layer. If you only need one endpoint today, the current setup is fine; otherwise please update to support your required environments.

core/network/src/main/java/com/hyunjung/core/network/datasource/AuthRemoteDataSourceImpl.kt (1)

38-38: Consider making device type configurable.

The device type is hardcoded as "android". Consider making this configurable or obtaining it from the build configuration to support different platforms or testing scenarios.

Also applies to: 50-50, 62-62

core/network/src/main/java/com/hyunjung/core/network/datasource/KakaoAuthDataSource.kt (2)

22-22: Consider internationalization for log messages.

The Korean log messages might need to be internationalized if the app supports multiple languages, or consider using English for consistency with other modules.

Also applies to: 27-27, 30-30, 40-40, 61-61, 64-64, 71-71, 77-77


60-62: Review error mapping for cancelled login.

Mapping cancelled login to DataError.Network.UNAUTHORIZED might be confusing. Consider using a more specific error type like DataError.Network.CANCELLED or DataError.User.CANCELLED if available.

feature/auth/src/main/java/com/hyunjung/feature/auth/login/LogInViewModel.kt (1)

28-28: Consider Flow collection approach.

Using first() on the repository Flow might not be the best approach if the repository is designed to emit multiple values. Consider using collect or collectLatest for better Flow handling.

-            when (val result = authRepository.login(context, socialType).first()) {
+            authRepository.login(context, socialType).collect { result ->
                 when (result) {
                     is Result.Success -> _uiState.update { LoginUiState.Success(result.data) }
                     is Result.Error -> _uiState.update { LoginUiState.Error(result.error) }
                 }
+            }
core/network/src/main/java/com/hyunjung/core/network/di/NetworkModule.kt (1)

50-58: Remove commented code or document future implementation plans.

The commented code for Naver and Google auth sources should either be removed or have a clear implementation timeline documented. Keeping commented code with NotImplementedError can cause confusion.

Consider removing the commented code:

-    // single<SocialAuthDataSource>(named(SocialType.NAVER.name)) {
-    //     // NaverAuthDataSource는 아직 구현되지 않았습니다.
-    //     throw NotImplementedError("NaverAuthDataSource is not implemented yet.")
-    // }
     single<AuthRemoteDataSource> { AuthRemoteDataSourceImpl(get()) }
-    // single<SocialAuthDataSource>(named(SocialType.GOOGLE.name)) {
-    //     // GoogleAuthDataSource는 아직 구현되지 않았습니다.
-    //     throw NotImplementedError("GoogleAuthDataSource is not implemented yet.")
-    // }
app/src/main/java/com/hyunjung/cherrydan/MainActivity.kt (1)

65-66: Implement token validation and wire up login handlers.

Two issues need attention:

  1. The TODO indicates missing token validation logic
  2. Empty click handlers for social login buttons

The token validation should check for existing valid tokens to skip the login screen. The empty click handlers need to be connected to the actual login logic.

Would you like me to help implement the token validation logic or create an issue to track this?

Also applies to: 75-77

feature/auth/src/main/java/com/hyunjung/feature/auth/login/LogInScreen.kt (2)

43-43: Complete UI implementation for all LoginUiState variants.

The TODO indicates that UI needs to be implemented for different states. Currently, only the loading state affects the UI.

Would you like me to help implement UI handling for all LoginUiState variants (Initial, Loading, Success, Error) or create an issue to track this?


55-68: Combine LaunchedEffect blocks for better efficiency.

Having two LaunchedEffect blocks with the same key (uiState) is inefficient. Combine them into a single effect.

-    // 로그인 성공 시 네비게이션
-    LaunchedEffect(uiState) {
-        if (uiState is LoginUiState.Success) {
-            onLoginSuccess()
-        }
-    }
-
-    // 에러 메시지 표시
-    LaunchedEffect(uiState) {
-        val error = (uiState as? LoginUiState.Error)?.error
-        error?.toString()?.let {
-            snackbarHostState.showSnackbar(it)
-        }
-    }
+    LaunchedEffect(uiState) {
+        when (uiState) {
+            is LoginUiState.Success -> onLoginSuccess()
+            is LoginUiState.Error -> {
+                uiState.error.toString().let {
+                    snackbarHostState.showSnackbar(it)
+                }
+            }
+            else -> { /* Handle other states if needed */ }
+        }
+    }
app/build.gradle.kts (2)

9-12: Secrets-plugin duplication – drop the manual filename override unless you really need it

secrets { defaultPropertiesFileName = "secrets.properties" } is the default behaviour of the Google Secrets-Gradle-plugin already, so the block is a no-op. Unless you plan to change the filename later, you can safely delete the whole secrets {} stanza and trim a few lines.


25-33: Let the secrets-plugin inject KAKAO_NATIVE_APP_KEY instead of DIY file I/O

The secrets plugin already places all keys into project.properties. You can fetch it with project.property("KAKAO_NATIVE_APP_KEY") and drop the manual Properties.load(..) boilerplate (plus one I/O hit).

-        val secretsFile = file("${rootProject.projectDir}/secrets.properties")
-        val kakaoKey = if (secretsFile.exists()) {
-            val properties = Properties()
-            properties.load(secretsFile.inputStream())
-            properties.getProperty("KAKAO_NATIVE_APP_KEY", "")
-        } else {
-            ""
-        }
+        val kakaoKey: String = project.findProperty("KAKAO_NATIVE_APP_KEY") as? String ?: ""

This keeps behaviour identical while relying on the library built for the purpose.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1905a49 and e5a5876.

📒 Files selected for processing (49)
  • app/build.gradle.kts (2 hunks)
  • app/src/main/AndroidManifest.xml (2 hunks)
  • app/src/main/java/com/hyunjung/cherrydan/CherrydanApplication.kt (1 hunks)
  • app/src/main/java/com/hyunjung/cherrydan/MainActivity.kt (1 hunks)
  • app/src/main/java/com/hyunjung/cherrydan/navigation/AppNavigation.kt (1 hunks)
  • build-logic/convention/src/main/kotlin/com/hyunjung/cherrydan/BuildTypes.kt (2 hunks)
  • build-logic/convention/src/main/kotlin/com/hyunjung/cherrydan/ComposeDependencies.kt (1 hunks)
  • core/common/.gitignore (1 hunks)
  • core/common/build.gradle.kts (1 hunks)
  • core/common/src/main/java/com/hyunjung/core/common/util/DataError.kt (1 hunks)
  • core/common/src/main/java/com/hyunjung/core/common/util/Error.kt (1 hunks)
  • core/common/src/main/java/com/hyunjung/core/common/util/Result.kt (2 hunks)
  • core/data/build.gradle.kts (1 hunks)
  • core/data/src/main/java/com/hyunjung/core/data/di/DataModule.kt (1 hunks)
  • core/data/src/main/java/com/hyunjung/core/data/networking/HttpClientExt.kt (1 hunks)
  • core/data/src/main/java/com/hyunjung/core/data/repository/AuthRepositoryImpl.kt (1 hunks)
  • core/domain/build.gradle.kts (1 hunks)
  • core/domain/src/main/java/com/hyunjung/core/domain/repository/AuthRepository.kt (1 hunks)
  • core/domain/src/main/java/com/hyunjung/core/domain/util/Error.kt (0 hunks)
  • core/model/build.gradle.kts (1 hunks)
  • core/model/src/main/java/com/hyunjung/core/model/AuthTokens.kt (1 hunks)
  • core/model/src/main/java/com/hyunjung/core/model/LoginResult.kt (1 hunks)
  • core/model/src/main/java/com/hyunjung/core/model/MyClass.kt (0 hunks)
  • core/model/src/main/java/com/hyunjung/core/model/SocialType.kt (1 hunks)
  • core/model/src/main/java/com/hyunjung/core/model/User.kt (1 hunks)
  • core/network/build.gradle.kts (1 hunks)
  • core/network/src/main/java/com/hyunjung/core/network/datasource/AuthRemoteDataSource.kt (1 hunks)
  • core/network/src/main/java/com/hyunjung/core/network/datasource/AuthRemoteDataSourceImpl.kt (1 hunks)
  • core/network/src/main/java/com/hyunjung/core/network/datasource/KakaoAuthDataSource.kt (1 hunks)
  • core/network/src/main/java/com/hyunjung/core/network/datasource/SocialAuthDataSource.kt (1 hunks)
  • core/network/src/main/java/com/hyunjung/core/network/di/NetworkModule.kt (1 hunks)
  • core/network/src/main/java/com/hyunjung/core/network/model/ApiResponse.kt (1 hunks)
  • core/network/src/main/java/com/hyunjung/core/network/model/LoginResponse.kt (1 hunks)
  • core/network/src/main/java/com/hyunjung/core/network/model/LoginResultResponse.kt (1 hunks)
  • core/network/src/main/java/com/hyunjung/core/network/model/SocialLoginRequestResponse.kt (1 hunks)
  • core/network/src/main/java/com/hyunjung/core/network/model/TokenResponse.kt (1 hunks)
  • core/network/src/main/java/com/hyunjung/core/network/resource/AuthResource.kt (1 hunks)
  • core/network/src/main/java/com/hyunjung/core/network/token/AuthTokenManager.kt (1 hunks)
  • core/network/src/main/java/com/hyunjung/core/network/token/TokenManager.kt (1 hunks)
  • core/network/src/main/java/com/hyunjung/core/network/util/HttpClientExt.kt (1 hunks)
  • core/presentation/ui/src/main/java/com/hyunjung/core/presentation/ui/DataErrorToText.kt (1 hunks)
  • feature/auth/build.gradle.kts (1 hunks)
  • feature/auth/src/main/java/com/hyunjung/feature/auth/di/AuthViewModelModule.kt (1 hunks)
  • feature/auth/src/main/java/com/hyunjung/feature/auth/login/LogInAction.kt (0 hunks)
  • feature/auth/src/main/java/com/hyunjung/feature/auth/login/LogInScreen.kt (3 hunks)
  • feature/auth/src/main/java/com/hyunjung/feature/auth/login/LogInState.kt (1 hunks)
  • feature/auth/src/main/java/com/hyunjung/feature/auth/login/LogInViewModel.kt (1 hunks)
  • gradle/libs.versions.toml (5 hunks)
  • settings.gradle.kts (1 hunks)
💤 Files with no reviewable changes (3)
  • core/model/src/main/java/com/hyunjung/core/model/MyClass.kt
  • core/domain/src/main/java/com/hyunjung/core/domain/util/Error.kt
  • feature/auth/src/main/java/com/hyunjung/feature/auth/login/LogInAction.kt
🧰 Additional context used
🧬 Code Graph Analysis (4)
app/src/main/java/com/hyunjung/cherrydan/MainActivity.kt (5)
core/presentation/designsystem/src/main/java/com/hyunjung/core/presentation/designsystem/Theme.kt (1)
  • CherrydanTheme (24-42)
feature/home/src/main/java/com/hyunjung/feature/home/navigation/HomeNavigation.kt (1)
  • HomeNavigation (45-118)
feature/auth/src/main/java/com/hyunjung/feature/auth/splash/SplashScreen.kt (1)
  • SplashScreen (25-42)
feature/auth/src/main/java/com/hyunjung/feature/auth/login/LogInScreen.kt (1)
  • LogInScreenRoot (44-78)
app/src/main/java/com/hyunjung/cherrydan/navigation/AppNavigation.kt (1)
  • AppNavigation (12-62)
core/network/src/main/java/com/hyunjung/core/network/token/AuthTokenManager.kt (1)
core/network/src/main/java/com/hyunjung/core/network/token/TokenManager.kt (3)
  • getAccessToken (3-8)
  • getAccessToken (4-4)
  • getRefreshToken (5-5)
app/src/main/java/com/hyunjung/cherrydan/navigation/AppNavigation.kt (3)
feature/auth/src/main/java/com/hyunjung/feature/auth/splash/SplashScreen.kt (1)
  • SplashScreen (25-42)
feature/auth/src/main/java/com/hyunjung/feature/auth/login/LogInScreen.kt (1)
  • LogInScreenRoot (44-78)
feature/home/src/main/java/com/hyunjung/feature/home/navigation/HomeNavigation.kt (1)
  • HomeNavigation (45-118)
feature/auth/src/main/java/com/hyunjung/feature/auth/login/LogInScreen.kt (1)
core/presentation/designsystem/src/main/java/com/hyunjung/core/presentation/designsystem/component/CherrydanLogInButton.kt (2)
  • CherrydanLogInButton (70-132)
  • CherrydanLogInOutlinedButton (134-185)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (43)
core/common/.gitignore (1)

1-1: 👍 Module-level .gitignore is good housekeeping

Excluding the local /build output prevents accidental commits of heavy, generated artifacts.
No further action needed.

gradle/libs.versions.toml (3)

39-40: Pinning Kakao SDK to 2.21.4 – double-check transitive Play-Services conflicts

Kakao 2.21.4 still pulls com.google.android.gms:play-services-auth:20.7.0.
If your project already forces a different Play-Services BOM or version, you can run into Duplicate class or runtime NoClassDefFoundError.

Consider adding an explicit alignment or BOM import to keep Play-Services consistent.

Also applies to: 124-126


94-95: ktor-client-resources added – keep the version in lock-step with core Ktor

Any version skew between ktor-client-resources (declared with version.ref = "ktor") and the rest of the Ktor stack triggers ABI mismatches at runtime.

No action required as long as both refs stay on 2.3.8.

Also applies to: 172-173


53-54: Firebase Firestore KTX introduced – ensure the Gradle googleServices plugin is applied

The KTX artifact needs the Google services JSON processing step.
If the root or module build.gradle.kts does not already apply:

plugins {
    id("com.google.gms.google-services")
}

add it, otherwise runtime initialisation of Firestore will fail.

Also applies to: 127-128

core/common/src/main/java/com/hyunjung/core/common/util/DataError.kt (1)

1-1: No remaining references to the old DataError package

I’ve verified with ripgrep that there are zero imports of
com.hyunjung.core.domain.util.DataError. All files already import
com.hyunjung.core.common.util.DataError, so no further updates are needed.

core/data/src/main/java/com/hyunjung/core/data/networking/HttpClientExt.kt (1)

3-4: Imports updated correctly – nothing else changed

The file now references the new core.common location; signature & generic bounds remain intact.
Looks good.

core/presentation/ui/src/main/java/com/hyunjung/core/presentation/ui/DataErrorToText.kt (1)

3-3: Import relocation looks good
The switch to core.common keeps the presentation layer free of domain-specific deps.

core/data/build.gradle.kts (1)

15-17: Dependencies look correct
core.network and Koin are essential here and correctly added.

core/model/src/main/java/com/hyunjung/core/model/SocialType.kt (1)

3-5: Add Retrofit / Moshi annotation if server payloads differ

If the backend returns lowercase provider names ("kakao", "naver", …) you’ll need something like:

@SerializedName("kakao") KAKAO,

Adding it early prevents subtle deserialization bugs.

settings.gradle.kts (1)

18-21: Repository ordering may leak snapshot precedence

maven { url "https://devrepo.kakao.com/…" } is placed before JCenter/others, but after mavenCentral().
If a transitive dependency exists in both Kakao repo and Maven Central with different versions, Gradle will now prefer the Kakao one. Confirm that is intentional; otherwise place it after mavenCentral().

build-logic/convention/src/main/kotlin/com/hyunjung/cherrydan/ComposeDependencies.kt (1)

10-10: LGTM! Proper architectural dependency.

The addition of the core:domain dependency to the UI layer follows clean architecture principles, allowing UI components to access domain contracts and models.

feature/auth/src/main/java/com/hyunjung/feature/auth/di/AuthViewModelModule.kt (1)

7-9: LGTM! Standard Koin module implementation.

The dependency injection setup correctly registers the LogInViewModel with its AuthRepository dependency using standard Koin patterns.

core/network/src/main/java/com/hyunjung/core/network/model/LoginResultResponse.kt (1)

5-9: LGTM! Well-structured network response model.

The data class correctly uses @Serializable annotation and follows good practices for modeling login response data with appropriate types.

core/model/build.gradle.kts (1)

2-2: LGTM! Good build script consolidation.

The use of a plugin alias improves maintainability and ensures consistent build configuration across core modules.

core/network/src/main/java/com/hyunjung/core/network/model/ApiResponse.kt (1)

5-10: LGTM! Standard API response wrapper pattern.

The generic ApiResponse class follows established patterns for API response handling, providing consistent structure for status codes, messages, and typed results.

core/data/src/main/java/com/hyunjung/core/data/di/DataModule.kt (1)

9-17: DI configuration is complete – all dependencies are provided in NetworkModule.

Verified in core/network/src/main/java/com/hyunjung/core/network/di/NetworkModule.kt:

  • SocialAuthDataSource named binding for SocialType.KAKAO.name (line 46)
  • single<AuthRemoteDataSource>AuthRemoteDataSourceImpl (line 54)
  • single<TokenManager>AuthTokenManager (line 42)

No missing bindings; the Koin setup is correct.

core/network/src/main/java/com/hyunjung/core/network/model/SocialLoginRequestResponse.kt (1)

7-9: Well-structured request model with appropriate defaults.

The implementation correctly uses:

  • Required accessToken for authentication
  • Optional fcmToken for push notifications
  • Sensible default for deviceType
core/network/src/main/java/com/hyunjung/core/network/datasource/AuthRemoteDataSource.kt (1)

8-14: Excellent interface design for remote authentication.

The interface demonstrates good practices:

  • Uses suspend for async network operations
  • Proper error handling with Result type
  • Clear parameter naming and optional fcmToken
  • Appropriate return type with domain model
app/src/main/AndroidManifest.xml (2)

5-6: Appropriate network permissions for authentication features.

The added permissions are necessary for:

  • INTERNET: Network requests for authentication
  • ACCESS_NETWORK_STATE: Checking network availability

32-43: Kakao Scheme Placeholder Configuration Verified

The activity configuration correctly:

  • Uses the official Kakao SDK activity
  • Implements proper intent filters for authentication callbacks
  • Uses manifest placeholders for secure key management

The kakaoScheme placeholder is defined in app/build.gradle.kts (lines 34–35):

manifestPlaceholders["kakaoScheme"] = "kakao$kakaoKey"
manifestPlaceholders["kakaoKey"]    = kakaoKey

No further changes required.

app/src/main/java/com/hyunjung/cherrydan/CherrydanApplication.kt (3)

17-20: Good logging initialization with debug-only setup.

Proper conditional initialization of Timber debug tree for development builds only.


30-31: Proper Kakao SDK initialization.

Correct initialization with application context and build configuration key.


33-42: All referenced Koin modules are present and properly configured

Verified that each module used in CherrydanApplication is declared with valid bindings:

  • feature/auth/src/main/java/com/hyunjung/feature/auth/di/AuthViewModelModule.kt
  • core/data/src/main/java/com/hyunjung/core/data/di/DataModule.kt
  • core/network/src/main/java/com/hyunjung/core/network/di/NetworkModule.kt

No further changes needed.

core/common/src/main/java/com/hyunjung/core/common/util/Result.kt (3)

6-7: LGTM: Error type constraint properly updated for package move.

The fully qualified type constraint correctly references the new Error interface location after the move to core.common.util.


21-21: LGTM: Well-defined typealias for empty results.

The EmptyDataResult typealias provides a clear semantic meaning for Result<Unit, E>, improving code readability.


23-26: LGTM: Well-implemented onSuccess extension function.

The extension function follows functional programming best practices by:

  • Using inline for performance
  • Returning the original result for chaining
  • Only executing the action for successful results
core/network/src/main/java/com/hyunjung/core/network/model/LoginResponse.kt (1)

8-13: LGTM: Well-structured LoginResponse data class.

The data class properly uses @Serializable annotation and has a clear structure with code, message, and result fields following typical API response patterns.

build-logic/convention/src/main/kotlin/com/hyunjung/cherrydan/BuildTypes.kt (1)

52-55: LGTM: Clean build type configuration.

The simplified configureDebugBuildType function is cleaner without the Project parameter dependency and directly sets the BASE_URL.

core/network/build.gradle.kts (3)

4-9: LGTM! Good addition of secrets management.

The secrets plugin configuration is properly set up to handle sensitive configuration data like API keys and tokens.


12-12: Namespace update looks correct.

The namespace follows proper Android package naming conventions for the network module.


29-44: Review dependency changes and ensure compatibility.

The dependency restructuring looks appropriate for the authentication feature. The addition of Kakao SDK dependencies aligns with the PR objectives.

core/network/src/main/java/com/hyunjung/core/network/datasource/AuthRemoteDataSourceImpl.kt (1)

23-28: LGTM! Clean delegation pattern.

The main method properly delegates to provider-specific implementations based on the social type.

core/network/src/main/java/com/hyunjung/core/network/resource/AuthResource.kt (1)

5-24: LGTM! Well-structured resource hierarchy.

The resource structure is clean and follows REST conventions. The nested class approach with proper parent-child relationships makes the API endpoints type-safe and maintainable.

core/network/src/main/java/com/hyunjung/core/network/datasource/KakaoAuthDataSource.kt (2)

20-43: LGTM! Excellent coroutine integration and fallback handling.

The implementation properly converts the callback-based Kakao SDK to suspending functions using suspendCancellableCoroutine. The fallback from KakaoTalk app to account login is well-implemented.


45-52: Good defensive programming with continuation state checking.

The resumeIfActive method prevents potential crashes by checking if the continuation is still active before resuming.

feature/auth/src/main/java/com/hyunjung/feature/auth/login/LogInViewModel.kt (2)

18-24: LGTM! Clean ViewModel structure with proper state management.

The ViewModel follows Android architecture best practices with StateFlow for reactive UI updates and proper state encapsulation.


36-41: LGTM! Clean state definition with sealed interface.

The LoginUiState sealed interface provides a clean and type-safe way to represent different UI states.

core/network/src/main/java/com/hyunjung/core/network/di/NetworkModule.kt (1)

35-49: Well-structured network module implementation.

The module setup and HTTP client configuration are comprehensive and follow best practices:

  • Proper separation of concerns
  • Comprehensive HTTP client configuration with logging, auth, and error handling
  • Clean dependency injection setup

Also applies to: 61-99

core/data/src/main/java/com/hyunjung/core/data/repository/AuthRepositoryImpl.kt (1)

22-43: Well-implemented authentication flow.

The login implementation properly orchestrates the social login flow:

  • Correctly sequences social connection before backend authentication
  • Properly saves tokens on success
  • Maintains error information throughout the flow
app/src/main/java/com/hyunjung/cherrydan/MainActivity.kt (1)

87-90: Good integration with navigation architecture.

The MainActivity changes properly support the new authentication flow with appropriate navigation setup and lifecycle logging.

Also applies to: 97-99

core/network/src/main/java/com/hyunjung/core/network/util/HttpClientExt.kt (1)

22-34: Well-designed HTTP client extensions.

The extension functions provide a clean, type-safe API for HTTP operations:

  • Proper use of inline functions with reified types
  • Consistent error handling across all methods
  • Smart URL construction that handles various input formats

Also applies to: 50-61, 106-112

feature/auth/src/main/java/com/hyunjung/feature/auth/login/LogInScreen.kt (1)

44-78: Well-structured login screen with proper state management.

The refactored login screen properly integrates with the ViewModel and state management:

  • Clean separation of concerns between Root and Screen composables
  • Proper use of Koin for dependency injection
  • State-driven UI that responds to loading states

Also applies to: 80-136

app/build.gradle.kts (1)

101-105: Remove unnecessary Kotlin stdlib dependency check

Inspection of the POMs for com.kakao.sdk:v2-auth, v2-common, and v2-user (version 2.21.4) shows no org.jetbrains.kotlin:kotlin-stdlib or kotlin-reflect entries. The Kakao SDK modules rely on the consumer’s Kotlin stdlib version only, so there’s no risk of pulling in an outdated Kotlin artifact and no need for the suggested dependencyInsight check.

Likely an incorrect or invalid review comment.

Comment on lines +34 to +36
manifestPlaceholders["kakaoScheme"] = "kakao$kakaoKey"
manifestPlaceholders["kakaoKey"] = kakaoKey
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

Nil-safe placeholder – avoid producing an invalid scheme

If kakaoKey is empty the scheme becomes just "kakao", which will not match the redirect URI Kakao issues (kakao{APP_KEY}://oauth).
Guard against that to fail fast:

-        manifestPlaceholders["kakaoScheme"] = "kakao$kakaoKey"
-        manifestPlaceholders["kakaoKey"] = kakaoKey
+        require(kakaoKey.isNotBlank()) { "KAKAO_NATIVE_APP_KEY is missing" }
+        manifestPlaceholders["kakaoScheme"] = "kakao$kakaoKey"
+        manifestPlaceholders["kakaoKey"] = kakaoKey
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
manifestPlaceholders["kakaoScheme"] = "kakao$kakaoKey"
manifestPlaceholders["kakaoKey"] = kakaoKey
}
require(kakaoKey.isNotBlank()) { "KAKAO_NATIVE_APP_KEY is missing" }
manifestPlaceholders["kakaoScheme"] = "kakao$kakaoKey"
manifestPlaceholders["kakaoKey"] = kakaoKey
}
🤖 Prompt for AI Agents
In app/build.gradle.kts around lines 34 to 36, the kakaoScheme placeholder is
set without checking if kakaoKey is empty, which can produce an invalid scheme.
Add a guard to check if kakaoKey is not empty before setting kakaoScheme; if
kakaoKey is empty, fail fast by throwing an error or logging a clear message to
prevent generating an invalid redirect URI.

Comment on lines +19 to +24
buildConfigField(
"String",
"BASE_URL",
"\"https://cherrydan.com\""
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Hard-coded BASE_URL couples release & staging builds

A single, immutable URL baked into all variants prevents you from pointing staging / QA builds at a different backend without another PR. Consider turning it into a variant-specific value or loading it from gradle.properties / the secrets plugin.

-        buildConfigField(
-            "String",
-            "BASE_URL",
-            "\"https://cherrydan.com\""
-        )
+        // keeps release default but allows `./gradlew assembleStaging -PBASE_URL=https://staging.api`
+        val baseUrl = (project.findProperty("BASE_URL") ?: "https://cherrydan.com") as String
+        buildConfigField("String", "BASE_URL", "\"$baseUrl\"")

🏁 Script executed:

#!/bin/bash
# Locate gradle.properties to see if project-level properties are already used
find . -maxdepth 2 -type f -name gradle.properties

# Inspect the exact context of buildConfigField in the app module
grep -R "buildConfigField" -n app/build.gradle.kts || true

# Check for existing flavor or build type configurations
grep -R -E "productFlavors|flavorDimensions|buildTypes" -n app

Length of output: 249


Make BASE_URL configurable via project property
Hard-coding the same endpoint in all variants couples your builds to a single backend. Instead, read BASE_URL from a Gradle property (e.g. in gradle.properties or via -PBASE_URL) with a fallback.

Location: app/build.gradle.kts (around lines 19–24)
Steps:

  1. In your root gradle.properties, add:
    BASE_URL=https://cherrydan.com
  2. In app/build.gradle.kts, replace the hard-coded field with:
    val baseUrl: String = (project.findProperty("BASE_URL") as? String)
        ?: "https://cherrydan.com"
    buildConfigField("String", "BASE_URL", "\"$baseUrl\"")
  3. Override per build when needed:
    ./gradlew assembleRelease -PBASE_URL=https://staging.api
    ./gradlew assembleDebug   -PBASE_URL=https://local.api
    

Optional: If you require a dedicated “staging” variant, define a staging buildType under android.buildTypes and configure it (e.g., initWith(release)) so you can run ./gradlew assembleStaging directly.

🤖 Prompt for AI Agents
In app/build.gradle.kts around lines 19 to 24, the BASE_URL is hard-coded, which
limits flexibility. Modify the code to read BASE_URL from a Gradle project
property with a fallback to the default URL. First, add
BASE_URL=https://cherrydan.com in the root gradle.properties file. Then, in
app/build.gradle.kts, replace the hard-coded buildConfigField with a variable
that reads the property using project.findProperty("BASE_URL") as a String or
defaults to the original URL. Use this variable in buildConfigField to make
BASE_URL configurable per build.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

♻️ Duplicate comments (2)
core/model/src/main/java/com/hyunjung/core/model/AuthTokens.kt (1)

3-11: Good implementation of token validation!

The init block properly validates that both tokens are non-blank, addressing the previous review comment. The error messages are clear and descriptive.

feature/auth/src/main/java/com/hyunjung/feature/auth/login/LogInViewModel.kt (1)

27-36: Exception handling properly implemented!

The try-catch block has been added as suggested in the previous review, properly handling unexpected exceptions.

🧹 Nitpick comments (3)
core/network/build.gradle.kts (1)

23-25: buildFeatures.buildConfig = true is redundant when using the Secrets plugin

AGP automatically turns on BuildConfig generation for library modules once any buildConfigField is declared (or when the Secrets plugin injects fields). You can safely delete this block to trim boilerplate.

feature/auth/src/main/java/com/hyunjung/feature/auth/login/LogInViewModel.kt (2)

29-29: Consider using collect instead of first() for Flow handling.

Using .first() only captures the initial emission from the Flow. If the repository emits multiple values (e.g., for retry logic or state updates), they will be missed.

Consider using collect to handle all emissions:

-when (val result = authRepository.login(context, socialType).first()) {
+authRepository.login(context, socialType).collect { result ->
+    when (result) {
     is Result.Success -> _uiState.update { LoginUiState.Success(result.data) }
     is Result.Error -> _uiState.update { LoginUiState.Error(result.error) }
+    }
 }

41-44: Use data object for singleton sealed interface members.

For Kotlin 1.9+, data object is preferred over object for sealed interface members as it provides better toString(), equals(), and hashCode() implementations.

 sealed interface LoginUiState {
-    object Idle : LoginUiState
-    object Loading : LoginUiState
+    data object Idle : LoginUiState
+    data object Loading : LoginUiState
     data class Success(val loginResult: LoginResult) : LoginUiState
     data class Error(val error: DataError) : LoginUiState
 }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e5a5876 and 6151705.

📒 Files selected for processing (9)
  • app/src/main/java/com/hyunjung/cherrydan/MainActivity.kt (1 hunks)
  • core/model/src/main/java/com/hyunjung/core/model/AuthTokens.kt (1 hunks)
  • core/network/build.gradle.kts (1 hunks)
  • core/network/src/main/java/com/hyunjung/core/network/datasource/AuthRemoteDataSourceImpl.kt (1 hunks)
  • core/network/src/main/java/com/hyunjung/core/network/datasource/KakaoAuthDataSource.kt (1 hunks)
  • core/network/src/main/java/com/hyunjung/core/network/di/NetworkModule.kt (1 hunks)
  • core/network/src/main/java/com/hyunjung/core/network/model/LoginResponse.kt (1 hunks)
  • core/network/src/main/java/com/hyunjung/core/network/token/AuthTokenManager.kt (1 hunks)
  • feature/auth/src/main/java/com/hyunjung/feature/auth/login/LogInViewModel.kt (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (6)
  • core/network/src/main/java/com/hyunjung/core/network/model/LoginResponse.kt
  • core/network/src/main/java/com/hyunjung/core/network/token/AuthTokenManager.kt
  • core/network/src/main/java/com/hyunjung/core/network/di/NetworkModule.kt
  • core/network/src/main/java/com/hyunjung/core/network/datasource/KakaoAuthDataSource.kt
  • core/network/src/main/java/com/hyunjung/core/network/datasource/AuthRemoteDataSourceImpl.kt
  • app/src/main/java/com/hyunjung/cherrydan/MainActivity.kt
🔇 Additional comments (1)
core/network/build.gradle.kts (1)

28-44: Dependencies block looks solid

Nice job adding AndroidX Crypto, Koin, Timber, and Kakao SDKs alongside the new internal modules. Scope and versions align with their intended usage.

Comment on lines +6 to +7
alias(libs.plugins.mapsplatform.secrets.plugin)
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Avoid redundant use of the Secrets Gradle Plugin

You already apply mapsplatform.secrets.plugin, which is able to export properties directly as BuildConfig fields via defaultPropertiesFileName + buildConfigFrom DSL. Manually loading secrets.properties later duplicates this capability and increases maintenance overhead.

Quick win – let the plugin generate BASE_URL for you:

 plugins {
     alias(libs.plugins.hyunjung.cherrydan.android.library)
     alias(libs.plugins.hyunjung.cherrydan.jvm.ktor)
-    alias(libs.plugins.mapsplatform.secrets.plugin)
+    alias(libs.plugins.mapsplatform.secrets.plugin)
 }

 secrets {
     defaultPropertiesFileName = "secrets.properties"
+    // This will automatically expose BASE_URL to BuildConfig
+    buildConfigFieldNames += listOf("BASE_URL")
 }
 
 android {

This removes the need for the custom Properties() block below and keeps all secrets handling in one place.

🤖 Prompt for AI Agents
In core/network/build.gradle.kts around lines 6 to 7, the secrets.properties
file is manually loaded after applying the mapsplatform.secrets.plugin, which is
redundant. Remove the manual loading of secrets.properties and instead configure
the plugin to export the BASE_URL as a BuildConfig field using its
defaultPropertiesFileName and buildConfigFrom DSL. This centralizes secrets
management and eliminates duplicate code.

Comment on lines +16 to +21
defaultConfig {
val secrets = Properties().apply {
load(file("${rootProject.projectDir}/secrets.properties").inputStream())
}
buildConfigField("String", "BASE_URL", "\"${secrets["BASE_URL"]}\"")
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Gracefully handle missing or malformed secrets files

Properties().load(...) will throw if the file is absent (common in CI) and secrets["BASE_URL"] may be null, embedding the string "null" in your BuildConfig. Add a fallback or fail-fast check.

-        val secrets = Properties().apply {
-            load(file("${rootProject.projectDir}/secrets.properties").inputStream())
-        }
-        buildConfigField("String", "BASE_URL", "\"${secrets["BASE_URL"]}\"")
+        val secretsFile = rootProject.file("secrets.properties")
+        val baseUrl = Properties().run {
+            if (secretsFile.exists()) load(secretsFile.inputStream())
+            getProperty("BASE_URL") ?: error("BASE_URL not found in secrets.properties")
+        }
+        buildConfigField("String", "BASE_URL", "\"$baseUrl\"")

This prevents silent misconfiguration and avoids leaking "null" as a runtime URL.

🤖 Prompt for AI Agents
In core/network/build.gradle.kts around lines 16 to 21, the code loads
secrets.properties without handling missing or malformed files, which can cause
exceptions or embed "null" as the BASE_URL. Modify the code to catch exceptions
from loading the properties file and provide a fallback or fail-fast error if
the file is missing or BASE_URL is null. This ensures the build either uses a
valid URL or fails clearly instead of silently misconfiguring.

Comment on lines +25 to +37
fun login(context: Context, socialType: SocialType) {
viewModelScope.launch {
try {
_uiState.update { LoginUiState.Loading }
when (val result = authRepository.login(context, socialType).first()) {
is Result.Success -> _uiState.update { LoginUiState.Success(result.data) }
is Result.Error -> _uiState.update { LoginUiState.Error(result.error) }
}
} catch (e: Exception) {
_uiState.update { LoginUiState.Error(DataError.Network.UNKNOWN) }
}
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Consider removing Context dependency from ViewModel.

ViewModels should be Android framework agnostic. Passing Context to the repository violates this principle and could lead to memory leaks if the Context is retained.

Consider moving the Context-dependent logic to the repository implementation or using a use case that handles Context internally.


🏁 Script executed:

#!/bin/bash
# Description: Check if Context is used elsewhere in the repository pattern

# Search for Context usage in repository interfaces
echo "=== Searching for Context in repository interfaces ==="
fd -e kt -e java | xargs rg -l "interface.*Repository" | xargs rg -A 5 -B 5 "Context"

# Check AuthRepository interface specifically
echo -e "\n=== Checking AuthRepository interface ==="
fd -e kt "AuthRepository" | xargs cat

Length of output: 3204


Remove Android Context from ViewModel and Domain Layer

The Context parameter is still propagated through the domain (AuthRepository) and data (AuthRepositoryImpl) layers. To make your ViewModel and domain code framework-agnostic, remove all Context dependencies and localize them to the Android–specific data sources.

• feature/auth/src/main/java/com/hyunjung/feature/auth/login/LogInViewModel.kt
– Change
diff - fun login(context: Context, socialType: SocialType) { + fun login(socialType: SocialType) { viewModelScope.launch { - when (val result = authRepository.login(context, socialType).first()) { + when (val result = authRepository.login(socialType).first()) { …

• core/domain/repository/AuthRepository.kt
– Change
diff interface AuthRepository { - fun login(context: Context, socialType: SocialType): Flow<Result<LoginResult, DataError>> + fun login(socialType: SocialType): Flow<Result<LoginResult, DataError>> }

• core/data/repository/AuthRepositoryImpl.kt
– Remove context: Context from login(...) and connectSocialAccount(...) signatures.
– Move calls like kakao.login(context) into the Android-specific SocialAuthDataSource (e.g. pass Context only to its login(context)), then have AuthRepositoryImpl.login(socialType) invoke that data source without ever taking a Context itself.

This refactoring keeps your ViewModel and domain layer free of Android framework classes, prevents accidental Context leaks, and centralizes all platform-specific logic in the data layer.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
fun login(context: Context, socialType: SocialType) {
viewModelScope.launch {
try {
_uiState.update { LoginUiState.Loading }
when (val result = authRepository.login(context, socialType).first()) {
is Result.Success -> _uiState.update { LoginUiState.Success(result.data) }
is Result.Error -> _uiState.update { LoginUiState.Error(result.error) }
}
} catch (e: Exception) {
_uiState.update { LoginUiState.Error(DataError.Network.UNKNOWN) }
}
}
}
// feature/auth/src/main/java/com/hyunjung/feature/auth/login/LogInViewModel.kt
fun login(socialType: SocialType) {
viewModelScope.launch {
try {
_uiState.update { LoginUiState.Loading }
when (val result = authRepository.login(socialType).first()) {
is Result.Success -> _uiState.update { LoginUiState.Success(result.data) }
is Result.Error -> _uiState.update { LoginUiState.Error(result.error) }
}
} catch (e: Exception) {
_uiState.update { LoginUiState.Error(DataError.Network.UNKNOWN) }
}
}
}
// core/domain/repository/AuthRepository.kt
interface AuthRepository {
fun login(socialType: SocialType): Flow<Result<LoginResult, DataError>>
}
🤖 Prompt for AI Agents
In feature/auth/src/main/java/com/hyunjung/feature/auth/login/LogInViewModel.kt
lines 25-37, remove the Context parameter from the login function and all
related calls. Similarly, in core/domain/repository/AuthRepository.kt and
core/data/repository/AuthRepositoryImpl.kt, eliminate Context parameters from
login and connectSocialAccount methods. Move any Context-dependent calls, such
as kakao.login(context), into the Android-specific SocialAuthDataSource, passing
Context only there. Adjust AuthRepositoryImpl to call SocialAuthDataSource
without requiring Context, ensuring ViewModel and domain layers are free of
Android framework dependencies.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6151705 and c7943ee.

📒 Files selected for processing (1)
  • .github/workflows/android.yml (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
.github/workflows/android.yml (2)

23-25: Consider removing or parameterising the dummy API_KEY.

Hard-coding ci_dummy_key works, but if the build ever starts consuming API_KEY for real, CI and local builds will diverge.
Either feed the real value from ${{ secrets.API_KEY }} or document why the placeholder is safe.


31-34: Minor: add a newline at EOF to silence YAML-lint.

The linter warning is harmless but easy to satisfy:

-        run: ./gradlew build
+        run: ./gradlew build
+
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c7943ee and c29d3b7.

📒 Files selected for processing (1)
  • .github/workflows/android.yml (1 hunks)
🧰 Additional context used
🪛 YAMLlint (1.37.1)
.github/workflows/android.yml

[error] 34-34: no new line character at the end of file

(new-line-at-end-of-file)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (1)
.github/workflows/android.yml (1)

26-30: Fixed overwrite bug – good catch.

>> now appends the KAKAO_NATIVE_APP_KEY instead of clobbering the file, so both secrets survive.
Double-check that these secrets are defined in the repo settings; an empty string would still be written and go unnoticed at build time.

@hyunjung-choi hyunjung-choi merged commit 557a8cc into develop Jul 10, 2025
2 checks passed
@hyunjung-choi hyunjung-choi deleted the feature/kakao-login branch July 10, 2025 02:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feat 새로운 기능

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Kakao Login Implementation

1 participant