Conversation
feat: 서재 UI/UX 개편 (3) - Room 모듈 구현
feat: 서재UI/UX 개편 (1) : 서재모듈 구현
Walkthrough이번 변경은 서재(라이브러리) 기능의 전면적인 UI/UX 개편 및 데이터 구조 리팩터링을 포함합니다. Jetpack Compose 기반의 라이브러리 화면, 필터/정렬/페이징 기능, Room 기반 로컬 캐싱, 도메인/데이터/코어 계층의 대대적 구조 개선, 관련 모델 및 API의 전면 교체, 네비게이터 확장, 기존 뷰 및 어댑터/모델/로직 제거가 이루어졌습니다. Changes
Sequence Diagram(s)sequenceDiagram
participant UI as LibraryScreen (Compose)
participant VM as LibraryViewModel
participant Repo as LibraryRepository
participant FilterRepo as FilterRepository
participant Remote as LibraryRemoteDataSource
participant Local as LibraryLocalDataSource
participant DB as Room DB
UI->>VM: 사용자 입력(필터/정렬/관심/탭/검색)
VM->>FilterRepo: updateFilter(...)
FilterRepo-->>VM: filterFlow emit
VM->>Repo: libraryFlow (paging)
Repo->>Remote: getUserNovels(..., filter)
Remote-->>Repo: UserNovelsEntity
Repo->>Local: insertNovels(...)
Local->>DB: insert
Repo-->>VM: PagingData<NovelEntity>
VM-->>UI: novels.collectAsLazyPagingItems()
UI-->>User: 라이브러리 리스트/그리드/필터/정렬 UI 표시
Estimated code review effort🎯 5 (Critical) | ⏱️ ~90+ minutes
Assessment against linked issues
Assessment against linked issues: Out-of-scope changes
Possibly related PRs
Suggested labels
Suggested reviewers
Poem
Note 🔌 MCP (Model Context Protocol) integration is now available in Early Access!Pro users can now connect to remote MCP servers under the Integrations page to get reviews and chat conversations that understand additional development context. ✨ Finishing Touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (6)
core/database/src/main/AndroidManifest.xml (1)
1-4: AndroidManifest.xml 파일이 최소한으로만 구성되었습니다.이 최소한의 매니페스트 파일은 라이브러리 모듈에 충분하지만, 필요에 따라 namespace 속성을 추가하는 것이 좋습니다.
<?xml version="1.0" encoding="utf-8"?> -<manifest> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.into.websoso.core.database"> </manifest>core/network/src/main/java/com/into/websoso/core/network/authenticator/AuthorizationAuthenticator.kt (2)
70-72: 메소드 호출 이름 변경이 적절합니다
renewTokens()에서createTokens()로의 메소드 호출 변경이AccountRepository의 인터페이스 변경과 일관성을 유지하도록 잘 수정되었습니다. 다만, 이 메소드를 포함하는 함수 이름은 여전히renewToken()입니다. 일관성을 위해 이 함수 이름도createToken()으로 변경하는 것을 고려해 보세요.- private suspend fun renewToken(): String? = + private suspend fun createToken(): String? = accountRepository.get().createTokens().fold( onSuccess = { accountRepository.get().accessToken() }, onFailure = {
31-50: authenticate 메소드 내 참조 업데이트 필요
renewToken()함수 이름을 변경하게 되면, 이 함수를 호출하는 authenticate 메소드 내부의 참조도 업데이트해야 합니다.runBlocking(dispatcher) { mutex.withLock { when (response.isRefreshNeeded()) { - true -> throttle { renewToken() } + true -> throttle { createToken() } false -> accountRepository.get().accessToken() } } } ?: return nullcore/database/src/main/java/com/into/websoso/core/database/entity/NovelEntity.kt (1)
6-15: 엔티티 클래스 구현 개선 제안
NovelEntity구조는 소설 데이터를 저장하기 위한 기본 구조를 잘 갖추고 있습니다. 다음 개선 사항을 고려해 보세요:
- KDoc 문서화 추가 - 엔티티의 목적과 각 필드의 의미를 설명
userNovelId가 자동 생성되어야 한다면@PrimaryKey(autoGenerate = true)사용 고려userNovelId와novelId의 관계 명확히 하기- 모든 필드가 null이 될 수 없는지 검토 (nullable 타입 고려)
- 명확한 컬럼명 지정을 위해
@ColumnInfo어노테이션 사용 고려예시:
/** * 사용자의 소설 라이브러리를 나타내는 Room 엔티티 */ @Entity(tableName = "novels") internal data class NovelEntity( @PrimaryKey(autoGenerate = true) val userNovelId: Long = 0, @ColumnInfo(name = "novel_id") val novelId: Long, val author: String, val title: String, @ColumnInfo(name = "image_url") val novelImage: String, @ColumnInfo(name = "rating") val novelRating: Float, )core/database/src/main/java/com/into/websoso/core/database/WebsosoDatabase.kt (1)
8-12: 스키마 내보내기 활성화를 고려하세요.
exportSchema = false설정은 개발 중에는 편리할 수 있지만, 프로덕션 앱에서는 권장되지 않습니다. 스키마 내보내기를 활성화하면 데이터베이스 마이그레이션 시 버전 간 변경사항을 추적하고 관리하는 데 도움이 됩니다.@Database( entities = [NovelEntity::class], version = 1, - exportSchema = false, + exportSchema = true, )core/database/src/main/java/com/into/websoso/core/database/datasource/library/NovelDao.kt (1)
46-52: 모듈 분리 고려DAO 인터페이스와 DAO 모듈이 같은 파일에 정의되어 있습니다. 관심사 분리 원칙에 따라 이를 별도의 파일로 분리하는 것이 더 좋은 구조입니다.
- @Module - @InstallIn(SingletonComponent::class) - internal object NovelDaoModule { - @Provides - @Singleton - internal fun provideNovelDao(database: WebsosoDatabase): NovelDao = database.novelDao() - }별도의 파일 (예:
di/NovelDaoModule.kt)에 다음과 같이 정의하세요:package com.into.websoso.core.database.di import com.into.websoso.core.database.WebsosoDatabase import com.into.websoso.core.database.datasource.library.NovelDao import dagger.Module import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) internal object NovelDaoModule { @Provides @Singleton internal fun provideNovelDao(database: WebsosoDatabase): NovelDao = database.novelDao() }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (20)
app/src/main/java/com/into/websoso/ui/splash/SplashViewModel.kt(1 hunks)core/database/.gitignore(1 hunks)core/database/build.gradle.kts(1 hunks)core/database/proguard-rules.pro(1 hunks)core/database/src/main/AndroidManifest.xml(1 hunks)core/database/src/main/java/com/into/websoso/core/database/WebsosoDatabase.kt(1 hunks)core/database/src/main/java/com/into/websoso/core/database/datasource/library/DefaultLibraryDataSource.kt(1 hunks)core/database/src/main/java/com/into/websoso/core/database/datasource/library/NovelDao.kt(1 hunks)core/database/src/main/java/com/into/websoso/core/database/di/DatabaseModule.kt(1 hunks)core/database/src/main/java/com/into/websoso/core/database/entity/NovelEntity.kt(1 hunks)core/datastore/src/main/java/com/into/websoso/core/datastore/datasource/account/DefaultAccountDataSource.kt(1 hunks)core/network/src/main/java/com/into/websoso/core/network/authenticator/AuthorizationAuthenticator.kt(1 hunks)data/account/src/main/java/com/into/websoso/data/account/AccountRepository.kt(2 hunks)data/account/src/main/java/com/into/websoso/data/account/datasource/AccountLocalDataSource.kt(1 hunks)data/library/src/main/java/com/into/websoso/data/library/datasource/LibraryLocalDataSource.kt(1 hunks)feature/library/src/main/java/com/into/websoso/feature/library/LibraryViewModel.kt(1 hunks)feature/signin/src/main/java/com/into/websoso/feature/signin/SignInScreen.kt(2 hunks)feature/signin/src/main/java/com/into/websoso/feature/signin/SignInViewModel.kt(1 hunks)gradle/libs.versions.toml(2 hunks)settings.gradle.kts(1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
core/database/build.gradle.kts (2)
build-logic/src/main/kotlin/com/into/websoso/ProjectExtensions.kt (1)
setNamespace(9-13)build-logic/src/main/kotlin/com/into/websoso/WebsosoDependenciesExtensions.kt (2)
implementation(70-76)kapt(62-68)
🔇 Additional comments (21)
gradle/libs.versions.toml (2)
98-100: Room 라이브러리 의존성 추가가 적절합니다.Room 데이터베이스 사용을 위한 필요한 의존성들(runtime, ktx, compiler)이 모두 올바르게 추가되었습니다. 이는 새로운 core/database 모듈에서 Room 데이터베이스를 사용하기 위한 적절한 설정입니다.
34-34:Details
✅ Verification successful
Room 버전이 적절한지 확인하세요.
Room 라이브러리 버전 2.7.1이 추가되었습니다. 이 버전이 현재 프로젝트에 적합하고 안정적인지 확인하는 것이 좋습니다.
🌐 Web query:
Android Room 2.7.1 버전의 안정성과 호환성💡 Result:
Android Room 2.7.1은 현재 안드로이드 애플리케이션 개발을 위한 안정적이고 기능이 풍부한 데이터베이스 솔루션으로, 2025년 4월 23일에 출시되었습니다[5]. 이 버전은 기존의 Android Room 라이브러리를 개선하고 확장하여 더욱 견고한 로컬 데이터베이스 관리를 제공합니다.
안정성 개선
Room 2.7.1은 이전 버전에서 발견된 여러 문제들을 해결하여 안정성을 크게 향상시켰습니다. 2.7.0 릴리스 과정에서 다음과 같은 주요 버그 수정이 이루어졌습니다:
- 데이터베이스 잠금 관련 오류(
SQLException: Error code: 5, message: database is locked) 해결을 위해 초기 데이터베이스 연결에서busy_timeout설정 문제 수정[2]- 데이터베이스가 닫혔을 때
RoomDatabase.inTransaction()이 불필요하게 데이터베이스를 열지 않도록 개선[2]- Kotlin 인라인/값 클래스를 처리할 때 발생하던
IllegalArgumentException: not a valid name크래시 문제 해결[2]- Kotlin 2.1.x와 KSP1에서 네이티브 소스 세트(iOS 등) 처리 시 KSP 프로세서 크래시 문제 수정[2]
호환성 및 기능 확장
Kotlin 멀티플랫폼 지원
Room 2.7.0-alpha01부터 도입된 가장 큰 변화 중 하나는 Kotlin 멀티플랫폼(KMP) 지원입니다[2][9]. 이 기능은 2.7.1에서도 유지되며, 다음 플랫폼을 지원합니다:
- Android
- iOS
- JVM (데스크톱)
- 네이티브 Mac
- 네이티브 Linux
이 멀티플랫폼 지원으로 개발자는 다양한 플랫폼에서 동일한 데이터베이스 코드를 재사용할 수 있게 되었습니다.
Kotlin 코드 생성 개선
KSP(Kotlin Symbol Processing)를 통한 처리 시 Kotlin 코드 생성이 기본적으로 활성화되었습니다[2]. 이는 Kotlin 프로젝트에서의 개발 경험을 향상시킵니다. KAPT나 Java 전용 프로젝트의 경우 Room은 계속해서 Java 소스를 생성합니다.
API 변경 및 추가 기능
- 리플렉션 사용을 피하기 위해 람다 파라미터를 사용하는
Room.databaseBuilder()오버로드 추가[2]- JVM 아티팩트에 Proguard 규칙 포함으로, 리플렉션을 사용하는 Room의 기본 초기화에 필요한 생성된 데이터베이스 구현의 기본 생성자가 제거되지 않도록 보장[2]
설정 및 사용법
Room 2.7.1을 프로젝트에 추가하려면 다음과 같이 의존성을 설정해야 합니다:
Kotlin 프로젝트
dependencies { val room_version = "2.7.1" implementation("androidx.room:room-runtime:$room_version") // Kotlin 소스를 사용하는 경우 KSP 사용 ksp("androidx.room:room-compiler:$room_version") // 선택적 의존성 implementation("androidx.room:room-ktx:$room_version") implementation("androidx.room:room-rxjava3:$room_version") implementation("androidx.room:room-paging:$room_version") // 테스트 헬퍼 testImplementation("androidx.room:room-testing:$room_version") } ```[3] ### Groovy 프로젝트 ```groovy dependencies { def room_version = "2.7.1" implementation "androidx.room:room-runtime:$room_version" // Kotlin 소스를 사용하는 경우 KSP 사용 ksp "androidx.room:room-compiler:$room_version" // Java만 사용하는 프로젝트의 경우 annotationProcessor "androidx.room:room-compiler:$room_version" } ```[3] ## Room의 장점 Room 2.7.1은 다음과 같은 핵심 장점을 제공합니다: - **컴파일 타임 SQL 쿼리 검증**: 실행 시간 오류를 줄여줍니다[6] - **간결한 데이터베이스 작업**: 더 직관적인 데이터베이스 인터페이스[6] - **LiveData와의 통합**: UI와 데이터베이스 상태의 동기화 간소화[6] - **자동 마이그레이션 도구**: 데이터베이스 스키마 변경 시 지원[6] - **테스트 지원**: `room-testing` Maven 아티팩트를 통한 마이그레이션 테스트 지원[1] ## 데이터베이스 마이그레이션 Room 2.7.1은 이전 버전과 마찬가지로 데이터베이스 마이그레이션에 강력한 지원을 제공합니다. 이는 앱이 발전함에 따라 데이터베이스 스키마를 안전하게 업데이트할 수 있게 해줍니다[1]. 마이그레이션은 복잡할 수 있으며 잘못 정의된 경우 앱이 비정상 종료될 수 있으므로, Room은 마이그레이션 테스트를 위한 도구를 제공합니다. 이를 위해서는 먼저 데이터베이스 스키마를 JSON 파일로 내보내야 합니다[1]. 결론적으로, Android Room 2.7.1은 안정성과 호환성 측면에서 큰 개선을 이루었으며, 특히 Kotlin 멀티플랫폼 지원을 통해 다양한 플랫폼에서의 개발 경험을 향상시켰습니다. 이는 모바일 애플리케이션 개발자들에게 더 견고하고 유연한 데이터베이스 솔루션을 제공합니다. Citations: - 1: https://developer.android.com/training/data-storage/room/migrating-db-versions - 2: https://developer.android.com/jetpack/androidx/releases/room - 3: https://developer.android.com/training/data-storage/room - 4: https://developer.android.com/jetpack/androidx/versions - 5: https://mvnrepository.com/artifact/androidx.room/room-compiler/2.7.1 - 6: https://velog.io/@jiwon30931/AndroidKotlin-Room%EC%9D%98-%EA%B8%B0%EB%B3%B8%EA%B3%BC-%EC%98%88%EC%A0%9C - 7: https://blog.codingcat.kr/121 - 8: https://mvnrepository.com/artifact/androidx.room/room-common/2.6.1 - 9: https://velog.io/@jmseb3/Temp-Title - 10: https://gogumac.github.io/android/room/android-jetpack-room/ --- </details> **Room 라이브러리 2.7.1 안정성 및 호환성 검증 완료** Room 2.7.1(2025-04-23 릴리스)는 다음과 같은 주요 안정성 개선 및 기능 확장을 포함하고 있어 프로젝트에 안전하게 적용할 수 있습니다: - 데이터베이스 잠금 오류(`database is locked`) 및 `inTransaction()` 개선 - Kotlin 인라인/값 클래스 처리 크래시 수정 - Kotlin 멀티플랫폼(KMP) 지원 유지 (Android, iOS, JVM, Mac, Linux) - KSP 기반 코드 생성 활성화 및 JVM 아티팩트 Proguard 규칙 포함 위 사항들을 종합하면 2.7.1 버전은 안정적이고 호환성이 검증된 버전이므로 그대로 반영하셔도 됩니다. </details> <details> <summary>core/database/.gitignore (1)</summary> `1-1`: **적절한 .gitignore 설정입니다.** 새로 추가된 core/database 모듈의 빌드 결과물을 버전 관리에서 제외하는 것은 표준적인 관행입니다. 다른 모듈의 .gitignore와 일관성 있게 구성되었습니다. </details> <details> <summary>settings.gradle.kts (1)</summary> `31-31`: **core:database 모듈 추가가 적절합니다.** settings.gradle.kts 파일에 새로운 core:database 모듈이 다른 core 모듈들과 함께 올바르게 추가되었습니다. 이 추가는 Room 데이터베이스 기능을 위한 모듈 구성에 필요합니다. </details> <details> <summary>feature/signin/src/main/java/com/into/websoso/feature/signin/SignInViewModel.kt (1)</summary> `57-59`: **메서드 이름 변경이 일관성 있게 적용되었습니다.** `saveTokens`에서 `createAccount`로의 메서드 이름 변경은 토큰 관리 관련 메서드 명칭 및 흐름의 일관성을 개선하는 리팩토링의 일부로 보입니다. 이 변경은 기존 기능을 유지하면서 더 명확한 의미를 전달합니다. </details> <details> <summary>core/database/build.gradle.kts (1)</summary> `1-20`: **코드가 잘 구성되어 있습니다!** Room 데이터베이스 모듈 설정이 깔끔하게 이루어져 있습니다. 필요한 의존성(Room-ktx, Room-runtime, Room-compiler)이 모두 포함되어 있고, 데이터 레이어와의 연결도 적절히 구성되어 있습니다. 주석을 통한 의존성 그룹화도 코드 가독성을 높이는 좋은 방식입니다. </details> <details> <summary>app/src/main/java/com/into/websoso/ui/splash/SplashViewModel.kt (1)</summary> `59-61`: **메소드 이름 변경이 적절합니다** `renewTokens()`에서 `createTokens()`로의 메소드 이름 변경이 `AccountRepository`의 인터페이스 변경과 일관성을 유지하도록 잘 수정되었습니다. 토큰 관리 관련 메소드의 명명 규칙을 전체적으로 통일하는 좋은 접근입니다. </details> <details> <summary>feature/signin/src/main/java/com/into/websoso/feature/signin/SignInScreen.kt (1)</summary> `86-91`: **NavigationBar에 대한 패딩 처리가 추가되어 UI 개선됨** `WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding()`를 사용하여 시스템 네비게이션 바에 의해 UI가 가려지지 않도록 패딩을 적용한 것은 좋은 사용자 경험 개선입니다. 이를 통해 다양한 기기에서 일관된 UI를 제공할 수 있게 되었습니다. 또한 배경 색상을 명시적으로 설정하여 디자인 일관성을 유지하는 것도 좋습니다. </details> <details> <summary>core/database/proguard-rules.pro (1)</summary> `1-21`: **표준 ProGuard 템플릿 파일 확인** 새 데이터베이스 모듈에 대한 기본 ProGuard 규칙 파일이 올바르게 추가되었습니다. 현재는 모든 규칙이 주석 처리되어 있어 활성화된 규칙이 없습니다. 향후 Room 라이브러리와 관련된 특정 ProGuard 규칙이 필요할 수 있으니 참고하세요. 특히 엔티티 클래스와 DAO에 대한 규칙을 고려해볼 수 있습니다. </details> <details> <summary>core/database/src/main/java/com/into/websoso/core/database/WebsosoDatabase.kt (1)</summary> `13-15`: **좋은 구현입니다!** Room 데이터베이스 클래스가 적절하게 정의되어 있으며, internal 접근 제한자를 사용하여 모듈 내부로 캡슐화된 것이 좋습니다. </details> <details> <summary>core/database/src/main/java/com/into/websoso/core/database/datasource/library/NovelDao.kt (1)</summary> `16-44`: **DAO 인터페이스 구현이 잘 되어 있습니다.** 소설 엔티티에 대한 기본적인 CRUD 작업이 적절하게 정의되어 있습니다. Flow를 반환하는 쿼리를 사용하여 반응형 프로그래밍 패턴을 따르고 있는 점이 좋습니다. </details> <details> <summary>core/database/src/main/java/com/into/websoso/core/database/datasource/library/DefaultLibraryDataSource.kt (1)</summary> `21-27`: **모듈 구현이 적절합니다.** LibraryDataSourceModule이 적절하게 구현되어 있으며, 인터페이스 바인딩에 @Singleton 스코프를 사용한 것이 좋습니다. </details> <details> <summary>core/datastore/src/main/java/com/into/websoso/core/datastore/datasource/account/DefaultAccountDataSource.kt (3)</summary> `23-33`: **메소드 이름 개선으로 데이터 조회 의도가 명확해졌습니다.** `accessToken()`과 `refreshToken()`에서 `selectAccessToken()`과 `selectRefreshToken()`으로 메소드 이름이 변경되어 데이터를 조회하는 목적이 더 명확해졌습니다. 이는 SQL 쿼리와 유사한 네이밍 패턴을 따르는 것으로, 데이터 접근 계층에서 일관된 용어를 사용하는 좋은 방식입니다. --- `35-45`: **메소드 이름 개선으로 데이터 업데이트 의도가 명확해졌습니다.** `saveAccessToken()`과 `saveRefreshToken()`에서 `updateAccessToken()`과 `updateRefreshToken()`으로 메소드 이름이 변경되어 데이터를 업데이트하는 목적이 더 명확해졌습니다. 새로운 이름은 CRUD 작업에서 표준 용어를 따르고 있어 코드 가독성이 향상되었습니다. --- `47-52`: **메소드 이름 개선으로 데이터 삭제 의도가 명확해졌습니다.** `clearTokens()`에서 `deleteTokens()`로 메소드 이름이 변경되어 토큰을 삭제하는 목적이 더 명확해졌습니다. 이는 데이터 액세스 계층에서 일반적으로 사용되는 CRUD 용어와 일치하며, 코드베이스 전체에서 일관된 메소드 네이밍을 유지하는 데 도움이 됩니다. </details> <details> <summary>data/account/src/main/java/com/into/websoso/data/account/datasource/AccountLocalDataSource.kt (1)</summary> `4-12`: **인터페이스 메소드 이름이 CRUD 표준에 맞게 개선되었습니다.** `AccountLocalDataSource` 인터페이스의 메소드 이름이 데이터 액세스 작업의 의도를 더 명확하게 표현하도록 변경되었습니다: - 데이터 조회: `accessToken()`, `refreshToken()` → `selectAccessToken()`, `selectRefreshToken()` - 데이터 업데이트: `saveAccessToken()`, `saveRefreshToken()` → `updateAccessToken()`, `updateRefreshToken()` - 데이터 삭제: `clearTokens()` → `deleteTokens()` 이러한 변경은 CRUD 작업에 대한 표준 네이밍 패턴을 따르며, 구현체인 `DefaultAccountDataSource`와 일관성을 유지하고 있습니다. </details> <details> <summary>data/account/src/main/java/com/into/websoso/data/account/AccountRepository.kt (5)</summary> `21-23`: **액세스 토큰과 리프레시 토큰 조회 메소드가 인터페이스 변경에 맞게 업데이트되었습니다.** 리포지토리의 토큰 조회 메소드들이 `AccountLocalDataSource` 인터페이스의 변경된 메소드 이름을 올바르게 참조하도록 업데이트되었습니다. 메소드 내부 구현은 변경되지 않았지만, 호출하는 메소드 이름이 `selectAccessToken()`과 `selectRefreshToken()`으로 변경되어 일관성이 유지되었습니다. --- `25-38`: **계정 생성 메소드 이름과 내부 구현이 개선되었습니다.** `saveTokens()`에서 `createAccount()`로 메소드 이름이 변경되어 토큰만 저장하는 것이 아니라 계정을 생성하는 의도가 더 명확해졌습니다. 또한 내부적으로 호출하는 메소드도 `updateAccessToken()`과 `updateRefreshToken()`으로 변경되어 `AccountLocalDataSource` 인터페이스의 변경사항과 일관성이 유지되었습니다. --- `40-44`: **계정 삭제 메소드의 내부 구현이 인터페이스 변경에 맞게 업데이트되었습니다.** 계정 삭제 시 토큰을 삭제하는 로직이 `clearTokens()`에서 `deleteTokens()`로 변경되어 `AccountLocalDataSource` 인터페이스의 변경사항과 일관성이 유지되었습니다. --- `46-52`: **토큰 갱신 메소드 이름과 내부 구현이 개선되었습니다.** `renewTokens()`에서 `createTokens()`로 메소드 이름이 변경되어 토큰을 갱신하는 것이 아니라 새로운 토큰을 생성하는 의도가 더 명확해졌습니다. 또한 내부적으로 호출하는 메소드도 `updateAccessToken()`과 `updateRefreshToken()`으로 변경되어 `AccountLocalDataSource` 인터페이스의 변경사항과 일관성이 유지되었습니다. --- `54-63`: **토큰 삭제 메소드 이름과 내부 구현이 개선되었습니다.** `clearTokens(deviceIdentifier: String)`에서 `deleteTokens(deviceIdentifier: String)`으로 메소드 이름이 변경되어 토큰을 삭제하는 의도가 더 명확해졌습니다. 또한 내부적으로 호출하는 메소드도 `deleteTokens()`로 변경되어 `AccountLocalDataSource` 인터페이스의 변경사항과 일관성이 유지되었습니다. </details> </blockquote></details> </details> <!-- This is an auto-generated comment by CodeRabbit for review status -->
| internal fun provideDatabase( | ||
| @ApplicationContext context: Context, | ||
| ): WebsosoDatabase = | ||
| Room | ||
| .databaseBuilder( | ||
| context, | ||
| WebsosoDatabase::class.java, | ||
| "websoso.db", | ||
| ).build() |
There was a problem hiding this comment.
🛠️ Refactor suggestion
데이터베이스 마이그레이션 전략 추가 필요
데이터베이스 구축 시 마이그레이션 전략이 정의되어 있지 않습니다. 향후 스키마 변경 시 데이터 손실을 방지하기 위해 마이그레이션 전략을 추가하는 것이 좋습니다. 또한 데이터베이스 이름을 상수로 추출하는 것을 고려하세요.
@Module
@InstallIn(SingletonComponent::class)
internal object DatabaseModule {
+ private const val DATABASE_NAME = "websoso.db"
@Provides
@Singleton
internal fun provideDatabase(
@ApplicationContext context: Context,
): WebsosoDatabase =
Room
.databaseBuilder(
context,
WebsosoDatabase::class.java,
- "websoso.db",
+ DATABASE_NAME,
)
+ .addMigrations() // 필요한 마이그레이션 추가
.build()
}📝 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.
| internal fun provideDatabase( | |
| @ApplicationContext context: Context, | |
| ): WebsosoDatabase = | |
| Room | |
| .databaseBuilder( | |
| context, | |
| WebsosoDatabase::class.java, | |
| "websoso.db", | |
| ).build() | |
| @Module | |
| @InstallIn(SingletonComponent::class) | |
| internal object DatabaseModule { | |
| private const val DATABASE_NAME = "websoso.db" | |
| @Provides | |
| @Singleton | |
| internal fun provideDatabase( | |
| @ApplicationContext context: Context, | |
| ): WebsosoDatabase = | |
| Room | |
| .databaseBuilder( | |
| context, | |
| WebsosoDatabase::class.java, | |
| DATABASE_NAME, | |
| ) | |
| .addMigrations() // 필요한 마이그레이션 추가 | |
| .build() | |
| } |
🤖 Prompt for AI Agents
In
core/database/src/main/java/com/into/websoso/core/database/di/DatabaseModule.kt
around lines 18 to 26, the databaseBuilder call lacks a migration strategy,
risking data loss on schema changes. Add a proper migration strategy using
addMigrations() with defined Migration objects to handle schema updates safely.
Also, extract the database name "websoso.db" into a constant for better
maintainability.
feat: 서재UI/UX 개편 (14) - Room 동기화 및 최적화
There was a problem hiding this comment.
Actionable comments posted: 21
♻️ Duplicate comments (1)
core/database/src/main/java/com/into/websoso/core/database/datasource/library/DefaultLibraryLocalDataSource.kt (1)
24-33: 동시 삽입 시sortIndex충돌 가능성
selectNovelsCount()로 오프셋을 구해 인덱스를 계산하는 방식은 다중-스레드에서 경쟁 조건이 발생하면 중복sortIndex가 저장될 수 있습니다. 트랜잭션으로도 완벽히 방지되지 않으니, DB 측에서sortIndex에 UNIQUE + AUTOINCREMENT 대체 컬럼을 두거나INSERT … SELECT MAX(sortIndex)+1형태로 원자적으로 계산하도록 개선해 주세요.
🧹 Nitpick comments (14)
app/src/main/java/com/into/websoso/ui/createFeed/CreateFeedViewModel.kt (1)
84-87: editFeed에 legacy 문자열 전달—명칭과 설계 개선 제안
- 명명:
legacyFeed는 의미가 다소 모호합니다.legacyContent혹은previousContent로 바꾸면 의도가 더 분명합니다. 호출부(Activity)와 저장소 파라미터도 일관되게 맞춰주세요.- 설계: 로컬 캐시 교체가 “문자열 동일성”에 의존한다면 충돌/중복(동일 내용의 다른 피드)에서 오동작 가능성이 있습니다. 교체 기준을
feedId로 옮기고, 콘텐츠는 변경 데이터로만 활용하는 구조를 권장합니다. 이는 동시성·정합성 측면에서 더 안전합니다.적용 원하시면 저장소 계층의 업데이트 기준을
feedId중심으로 리팩토링하는 패치를 제안드릴 수 있습니다.Also applies to: 95-97
app/src/main/java/com/into/websoso/ui/main/feed/FeedViewModel.kt (1)
241-241: removedFeed 조회 시 널 대응 전략을 명확히 해주세요.
removedFeed가 null이면novelId=null,content=""로 서버 호출이 이뤄집니다. UI에는 해당 피드가 이미 없을 가능성이 높아 조용히 반환하는 편이 합리적일 수 있습니다. 최소한 로그/추적을 남기거나 early-return 로직을 고려해 주세요.- val removedFeed = feedUiState.feeds.find { it.id == feedId } + val removedFeed = feedUiState.feeds.find { it.id == feedId } + if (removedFeed == null) { + // TODO: log/trace if needed + _feedUiState.value = feedUiState.copy(loading = false) + return + }app/src/main/java/com/into/websoso/ui/main/myPage/myActivity/MyActivityViewModel.kt (1)
110-110: removedFeed 널 처리와 호출 의도 명확화
removedFeed가 null인 경우에도 서버 삭제를 시도합니다. 의도가 “UI에 없더라도 서버 상태를 정합화”라면 주석으로 의도를 남기고, 아니라면 early-return으로 낭비 호출을 줄이세요.- val removedFeed = uiState.value?.activities?.find { it.feedId == feedId } + // UI 목록에 없으면 조용히 반환(또는 로그만 남김) + val removedFeed = uiState.value?.activities?.find { it.feedId == feedId } ?: run { + _uiState.value = _uiState.value?.copy(isLoading = false) + return@launch + }app/src/main/java/com/into/websoso/ui/activityDetail/ActivityDetailViewModel.kt (1)
161-167: 삭제 대상 조회 및 호출 로직 안정화
removedFeed가 null이면 의미 있는 서버 호출이 아닐 수 있습니다. early-return 또는 주석으로 의도 명시 권장.- 동일 패턴을 여러 ViewModel에서 사용하므로, 공통 유틸(예:
removeFeedFrom(list, feedId))로의 추출을 고려하면 유지보수성이 좋아집니다.- val removedFeed = uiState.value?.activities?.find { it.feedId == feedId } + val removedFeed = uiState.value?.activities?.find { it.feedId == feedId } ?: run { + _uiState.value = uiState.value?.copy(isLoading = false) + return@launch + }app/src/main/java/com/into/websoso/ui/novelFeed/NovelFeedViewModel.kt (1)
164-171: 변경사항 타당성 좋음 + 널 처리 보완 제안
- 코루틴 내부에서
removedFeed를 조회하는 위치는 적절합니다.- 다만
removedFeed가 null일 때는 서버 호출을 생략하고 로딩 해제만 하는 편이 UX/네트워크 관점에서 유리할 수 있습니다.- val removedFeed = feedUiState.feeds.find { it.id == feedId } + val removedFeed = feedUiState.feeds.find { it.id == feedId } ?: run { + _feedUiState.value = feedUiState.copy(loading = false) + return@launch + }app/src/main/java/com/into/websoso/ui/accountInfo/AccountInfoActivity.kt (1)
103-121: 하드코딩 문자열/버튼 색상 리소스 사용 개선 및 성공 피드백 필요
- 다국어/접근성 위해 문자열("캐시 삭제", "정말…", "확인", "취소")을 string 리소스로 이동하세요.
- 버튼 텍스트 색상을 하드코딩 black 대신 테마 속성(Material colorOnSurface 등) 사용 또는 스타일로 위임하세요. 다크모드/하이콘트라스트 대응을 위해 수동 setTextColor는 지양.
- 캐시 삭제 완료/실패 피드백 스낵바/토스트 또는 UiEffect를 통한 결과 이벤트 제공이 UX에 필요합니다.
원하시면 MaterialAlertDialogBuilder 기반으로 리소스/테마를 적용한 구현과 ViewModel UiEffect(예: CacheCleared/CacheClearFailed)까지 포함한 패치를 제안드리겠습니다.
app/src/main/java/com/into/websoso/ui/accountInfo/AccountInfoViewModel.kt (1)
70-75: clearCache 결과 피드백/에러 처리 누락삭제 실패 시 조용히 무시됩니다. UX/디버깅을 위해 성공/실패를 UiEffect로 노출하거나 runCatching으로 로깅이 필요합니다. 또한 앱 상태와 동기화를 위해 삭제 완료 후 관련 화면 갱신 트리거가 있으면 좋습니다.
예시:
runCatching { libraryRepository.deleteAllNovels() filterRepository.deleteLibraryFilter() }.onSuccess { _uiEffect.send(UiEffect.CacheCleared) } .onFailure { _uiEffect.send(UiEffect.CacheClearFailed) }data/library/src/main/java/com/into/websoso/data/filter/repository/MyLibraryFilterRepository.kt (2)
22-24: 인터페이스에 노출되지 않은 삭제 함수
deleteLibraryFilter()는 구현체에만 존재하고FilterRepository에는 정의되어 있지 않아, DI 로 얻은FilterRepository타입으로는 호출할 수 없습니다. 실제 사용처가 구현체를 직접 의존하지 않는지 확인하거나, 필요하다면 인터페이스로 승격해 주세요.
33-42: 동일 필터 재저장 방지 권장
savedFilter와updatedFilter가 동일할 때도 항상updateLibraryFilter()를 호출하여 DataStore 쓰기가 발생합니다. 값이 변하지 않으면 조기 return 하여 I/O를 줄이는 것이 좋습니다.if (savedFilter == updatedFilter) returndata/library/src/main/java/com/into/websoso/data/library/paging/LibraryRemoteMediator.kt (1)
40-44: 중복 삽입 방지 로직 부재
insertNovels()전에 기존 ID 중복 여부를 검증하지 않으면 중복 레코드가 생길 수 있습니다. DAO 레벨의ON CONFLICT REPLACE또는 필터링 로직 추가를 검토해 주세요.app/src/main/java/com/into/websoso/ui/novelRating/NovelRatingViewModel.kt (1)
34-37: SavedStateHandle 키 하드코딩
"NOVEL","FEEDS"등의 문자열 키를 ViewModel 내부에 하드코딩하면 재사용성이 떨어집니다. companion object 상수로 추출하거나 Safe Args 사용을 권장합니다.app/src/main/java/com/into/websoso/ui/main/library/LibraryFragment.kt (1)
34-47: ComposeView를 직접 반환하여 XML 레이어 제거 고려XML을 한 번 더 inflate 한 뒤
ComposeView를 찾아 설정하고 있습니다.
onCreateView에서ComposeView(requireContext()).apply { … }형태로 바로 반환하면 레이아웃 계층이 단순해지고 성능도 약간 향상됩니다. 큰 변경 없이도 적용 가능하니 검토해 주세요.core/database/src/main/java/com/into/websoso/core/database/datasource/library/dao/NovelDao.kt (1)
27-33: 조회 컬럼에 인덱스 추가 권장
userNovelId,novelId로 단건 조회가 빈번히 일어납니다.
Room 엔티티에 두 컬럼에 대해@Index를 지정하거나 복합 PK로 설정해 두면 조회-성능이 개선되고 전체 테이블 스캔을 피할 수 있습니다.data/library/src/main/java/com/into/websoso/data/library/repository/MyLibraryRepository.kt (1)
32-50: Pager 결과를 ViewModel 스코프에cachedIn()하는 방안 검토필터가 동일한 동안에도 다수 UI 구성요소가 Flow를 구독하면 페이징 스트림이 중복 생성될 수 있습니다.
Pager(...).flow.cachedIn(viewModelScope)형태로 캐싱하면 동일 스코프 내 재사용이 가능해 네트워크·DB 부하를 줄일 수 있습니다.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (36)
app/src/main/java/com/into/websoso/data/di/RepositoryModule.kt(0 hunks)app/src/main/java/com/into/websoso/data/model/NovelRatingEntity.kt(1 hunks)app/src/main/java/com/into/websoso/data/repository/FeedRepository.kt(5 hunks)app/src/main/java/com/into/websoso/data/repository/NovelRepository.kt(3 hunks)app/src/main/java/com/into/websoso/data/repository/UserNovelRepository.kt(2 hunks)app/src/main/java/com/into/websoso/ui/accountInfo/AccountInfoActivity.kt(3 hunks)app/src/main/java/com/into/websoso/ui/accountInfo/AccountInfoViewModel.kt(3 hunks)app/src/main/java/com/into/websoso/ui/activityDetail/ActivityDetailViewModel.kt(1 hunks)app/src/main/java/com/into/websoso/ui/createFeed/CreateFeedActivity.kt(1 hunks)app/src/main/java/com/into/websoso/ui/createFeed/CreateFeedViewModel.kt(1 hunks)app/src/main/java/com/into/websoso/ui/feedDetail/FeedDetailViewModel.kt(1 hunks)app/src/main/java/com/into/websoso/ui/main/feed/FeedViewModel.kt(1 hunks)app/src/main/java/com/into/websoso/ui/main/library/LibraryFragment.kt(1 hunks)app/src/main/java/com/into/websoso/ui/main/myPage/myActivity/MyActivityViewModel.kt(1 hunks)app/src/main/java/com/into/websoso/ui/novelDetail/NovelDetailActivity.kt(2 hunks)app/src/main/java/com/into/websoso/ui/novelDetail/model/NovelDetailModel.kt(5 hunks)app/src/main/java/com/into/websoso/ui/novelFeed/NovelFeedViewModel.kt(1 hunks)app/src/main/java/com/into/websoso/ui/novelRating/NovelRatingActivity.kt(5 hunks)app/src/main/java/com/into/websoso/ui/novelRating/NovelRatingViewModel.kt(4 hunks)app/src/main/java/com/into/websoso/ui/withdraw/second/WithdrawSecondViewModel.kt(3 hunks)app/src/main/res/layout/activity_account_info.xml(1 hunks)core/database/src/main/java/com/into/websoso/core/database/datasource/library/DefaultLibraryLocalDataSource.kt(1 hunks)core/database/src/main/java/com/into/websoso/core/database/datasource/library/dao/NovelDao.kt(1 hunks)core/database/src/main/java/com/into/websoso/core/database/datasource/library/entity/InDatabaseNovelEntity.kt(1 hunks)core/database/src/main/java/com/into/websoso/core/database/datasource/library/mapper/NovelMapper.kt(1 hunks)core/database/src/main/java/com/into/websoso/core/database/datasource/library/paging/MapValuePagingSource.kt(1 hunks)data/library/src/main/java/com/into/websoso/data/filter/repository/MyLibraryFilterRepository.kt(1 hunks)data/library/src/main/java/com/into/websoso/data/library/LibraryRepository.kt(1 hunks)data/library/src/main/java/com/into/websoso/data/library/datasource/LibraryLocalDataSource.kt(1 hunks)data/library/src/main/java/com/into/websoso/data/library/paging/LibraryRemoteMediator.kt(1 hunks)data/library/src/main/java/com/into/websoso/data/library/repository/MyLibraryRepository.kt(1 hunks)feature/library/src/main/java/com/into/websoso/feature/library/LibraryScreen.kt(1 hunks)feature/library/src/main/java/com/into/websoso/feature/library/LibraryViewModel.kt(1 hunks)feature/library/src/main/java/com/into/websoso/feature/library/model/NovelUiModel.kt(1 hunks)feature/library/src/main/java/com/into/websoso/feature/library/model/RatingStarUiModel.kt(1 hunks)feature/library/src/main/java/com/into/websoso/feature/library/model/ReadStatusUiModel.kt(1 hunks)
💤 Files with no reviewable changes (1)
- app/src/main/java/com/into/websoso/data/di/RepositoryModule.kt
✅ Files skipped from review due to trivial changes (2)
- app/src/main/java/com/into/websoso/ui/novelDetail/model/NovelDetailModel.kt
- app/src/main/res/layout/activity_account_info.xml
🚧 Files skipped from review as they are similar to previous changes (7)
- feature/library/src/main/java/com/into/websoso/feature/library/model/RatingStarUiModel.kt
- core/database/src/main/java/com/into/websoso/core/database/datasource/library/mapper/NovelMapper.kt
- feature/library/src/main/java/com/into/websoso/feature/library/model/ReadStatusUiModel.kt
- core/database/src/main/java/com/into/websoso/core/database/datasource/library/paging/MapValuePagingSource.kt
- core/database/src/main/java/com/into/websoso/core/database/datasource/library/entity/InDatabaseNovelEntity.kt
- feature/library/src/main/java/com/into/websoso/feature/library/model/NovelUiModel.kt
- feature/library/src/main/java/com/into/websoso/feature/library/LibraryViewModel.kt
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-01T03:33:19.721Z
Learnt from: s9hn
PR: Team-WSS/WSS-Android#734
File: core/database/src/main/java/com/into/websoso/core/database/datasource/library/paging/MapValuePagingSource.kt:34-34
Timestamp: 2025-08-01T03:33:19.721Z
Learning: MapValuePagingSource와 같은 PagingSource 래퍼 클래스는 Singleton을 사용하면 안 된다. PagingSource는 상태를 가지며 각 페이징 요청마다 새로운 인스턴스가 필요하기 때문이다.
Applied to files:
data/library/src/main/java/com/into/websoso/data/library/LibraryRepository.kt
🧬 Code Graph Analysis (2)
data/library/src/main/java/com/into/websoso/data/library/paging/LibraryRemoteMediator.kt (5)
core/database/src/main/java/com/into/websoso/core/database/datasource/library/dao/NovelDao.kt (1)
deleteAllNovels(39-40)data/library/src/main/java/com/into/websoso/data/library/repository/MyLibraryRepository.kt (1)
deleteAllNovels(52-54)core/database/src/main/java/com/into/websoso/core/database/datasource/library/DefaultLibraryLocalDataSource.kt (2)
deleteAllNovels(54-56)insertNovels(24-33)data/library/src/main/java/com/into/websoso/data/library/datasource/LibraryLocalDataSource.kt (3)
deleteAllNovels(21-21)insertNovels(6-24)insertNovels(7-7)data/library/src/main/java/com/into/websoso/data/library/paging/LibraryPagingSource.kt (1)
getNovels(10-44)
data/library/src/main/java/com/into/websoso/data/library/repository/MyLibraryRepository.kt (1)
data/library/src/main/java/com/into/websoso/data/library/repository/UserLibraryRepository.kt (1)
getUserNovels(50-70)
🔇 Additional comments (16)
app/src/main/java/com/into/websoso/ui/createFeed/CreateFeedActivity.kt (1)
107-110: EditFeedModel.feedContent는 non-nullable입니다
EditFeedModel의feedContent는 기본값String = ""으로 선언된 non-nullable 타입이므로, null 안전성 검사를 위한 추가 처리(requireNotNull등)는 필요하지 않습니다.app/src/main/java/com/into/websoso/ui/main/myPage/myActivity/MyActivityViewModel.kt (1)
113-117: 모든 saveRemovedFeed 호출부에content파라미터 반영 확인됨
다음 파일의saveRemovedFeed호출에서content인자가 모두 포함된 것을 확인했습니다. 추가 수정은 필요 없습니다.
- app/src/main/java/com/into/websoso/ui/novelFeed/NovelFeedViewModel.kt
- app/src/main/java/com/into/websoso/ui/feedDetail/FeedDetailViewModel.kt
- app/src/main/java/com/into/websoso/ui/main/myPage/myActivity/MyActivityViewModel.kt
- app/src/main/java/com/into/websoso/ui/main/feed/FeedViewModel.kt
- app/src/main/java/com/into/websoso/ui/activityDetail/ActivityDetailViewModel.kt
app/src/main/java/com/into/websoso/data/model/NovelRatingEntity.kt (1)
6-11: 조치 필요: NovelRatingEntity 생성자 필드 위치 변경으로 인한 위치 인자 호출 리스크 점검
Kotlin 데이터 클래스 생성자를 위치 인자로 호출할 경우, 중간에 필드를 삽입하면 기존 호출부가 조용히 잘못 매핑될 수 있습니다. 현재novelImage: String?와novelRating: Float가readStatus: String?뒤에 추가되면서 아래와 같은 위험이 있습니다.• 잘못 매핑 예시
기존 호출부
NovelRatingEntity(1L, 2L, "제목", "읽음", 4.5f)
→4.5f가 의도치 않게novelRating가 아닌userNovelId등에 매핑될 수 있음권장 대응
- 신규 필드를 생성자 맨 뒤로 이동
- 또는 companion object factory/빌더 패턴 도입
- 호출부 전역에 Named Argument 사용 강제
추가 검증
- 위치 인자 호출 여부 점검(이름 없는 인자 호출이 있는지)
- 예:
rg -n "NovelRatingEntity\\(" --type kotlin --type java \ | grep -v "=" \ || echo "모든 호출부가 Named Argument를 사용 중입니다."리팩터 예시
data class NovelRatingEntity( val userNovelId: Long? = null, val novelId: Long? = null, val novelTitle: String? = null, val readStatus: String?, - val novelImage: String? = null, - val novelRating: Float = 0.0f, + // 신규 필드를 생성자 끝으로 이동하여 위치 인자 리스크 최소화 + val novelImage: String? = null, + val novelRating: Float = 0.0f, )app/src/main/java/com/into/websoso/ui/novelFeed/NovelFeedViewModel.kt (1)
167-171: 레포지토리 saveRemovedFeed 호출 파라미터 검증 완료
모든 ViewModel에서 feedId, novelId, content 파라미터가 FeedRepository의 함수 시그니처(feedId: Long, novelId: Long?, content: String)와 일치함을 확인했습니다.app/src/main/java/com/into/websoso/ui/accountInfo/AccountInfoActivity.kt (2)
3-5: 필요한 import 추가 적절AlertDialog, BUTTON 상수, ContextCompat.getColor, color 리소스 import 추가는 변경에 부합합니다.
Also applies to: 11-13
50-50: onCreate에 캐시 삭제 핸들러 연결 OK클릭 리스너 초기화 흐름에 잘 편입되었습니다.
app/src/main/java/com/into/websoso/ui/accountInfo/AccountInfoViewModel.kt (3)
6-8: DI 의존성 추가 적절MyLibraryRepository, MyLibraryFilterRepository 주입 추가는 변경 목적과 일치합니다.
29-31: 생성자 주입 필드 확장 OK레포지토리 의존성 확장은 책임 범위와 일치합니다.
59-63: 로그아웃 실패 케이스에서도 로컬 정리 보장 필요현재 토큰 삭제 성공 시에만 약관 체크/FCM 토큰/라이브러리/필터를 정리합니다. 원격 실패(onFailure)에서도 내장 데이터는 정리하고 로그인 화면으로 가는 정책이라면, 로컬 정리는 성공/실패와 무관하게 보장되어야 합니다.
간단한 개선 예:
- accountRepository - .deleteTokens(userDeviceIdentifier) - .onSuccess { - userRepository.removeTermsAgreementChecked() - pushMessageRepository.clearFCMToken() - libraryRepository.deleteAllNovels() - filterRepository.deleteLibraryFilter() - _uiEffect.send(NavigateToLogin) - }.onFailure { - _uiEffect.send(NavigateToLogin) - } + runCatching { accountRepository.deleteTokens(userDeviceIdentifier) } + .onFailure { /* 로그만 남기고 진행 */ } + .also { + userRepository.removeTermsAgreementChecked() + pushMessageRepository.clearFCMToken() + libraryRepository.deleteAllNovels() + filterRepository.deleteLibraryFilter() + } + _uiEffect.send(NavigateToLogin)⛔ Skipped due to learnings
Learnt from: s9hn PR: Team-WSS/WSS-Android#687 File: app/src/main/java/com/into/websoso/ui/accountInfo/AccountInfoViewModel.kt:0-0 Timestamp: 2025-05-15T17:11:19.839Z Learning: 로그아웃 성공 케이스에서만 FCM 토큰을 제거해야 합니다. 로그아웃 실패 시에는 사용자가 여전히 로그인 상태이므로 FCM 토큰을 유지해야 합니다.app/src/main/java/com/into/websoso/data/repository/NovelRepository.kt (2)
3-3: 로컬 데이터소스 의존성 추가 OK로컬 캐시 동기화를 위한 의존성 추가는 합리적입니다.
20-20: 생성자 주입 확장 OKLibraryLocalDataSource 주입은 저장소 책임과 일치합니다.
app/src/main/java/com/into/websoso/ui/novelRating/NovelRatingActivity.kt (4)
41-41: 모델 import 추가 OKNovelDetailModel 직렬화 전달 변경에 맞는 import입니다.
96-97: novelId 의존 제거에 따른 저장 경로 점검 필요updateUserNovelRating가 내부적으로 novelId를 SavedStateHandle/모델에서 안전하게 획득하는지 확인하세요. 그렇지 않으면 저장 API 호출이 실패합니다.
282-284: 리로드 처리 OK네트워크 오류 시 재시도 경로가 isInterest만으로 명확합니다. ViewModel가 나머지 파라미터를 SavedStateHandle에서 재사용하는지 함께 확인하면 좋습니다.
76-78: 확인: NovelRatingViewModel이 SavedStateHandle로 NOVEL, FEEDS, IS_INTEREST를 안전하게 읽어 초기 상태를 구성합니다.
- app/src/main/java/com/into/websoso/ui/novelRating/NovelRatingViewModel.kt
• 생성자에서savedStateHandle["NOVEL"],savedStateHandle["FEEDS"],savedStateHandle["IS_INTEREST"]를 통해 값 소비 확인data/library/src/main/java/com/into/websoso/data/library/datasource/LibraryLocalDataSource.kt (1)
7-23: 확장된 CRUD 인터페이스 확인메서드 시그니처와 반환 타입이 명확하게 정의되어 있어 기존 리뷰에서 지적된 문제점이 해결되었습니다. 별다른 이슈 없이 사용 가능해 보입니다.
| val updatedNovel = NovelEntity( | ||
| userNovelId = novelRatingEntity.userNovelId | ||
| ?: libraryLocalDataSource.selectAllNovelsCount().toLong(), | ||
| novelId = novelRatingEntity.novelId!!, | ||
| title = novelRatingEntity.novelTitle.orEmpty(), | ||
| novelImage = novelRatingEntity.novelImage.orEmpty(), | ||
| novelRating = novelRatingEntity.novelRating, | ||
| readStatus = novelRatingEntity.readStatus.orEmpty(), | ||
| isInterest = isInterested ?: false, | ||
| userNovelRating = novelRatingEntity.userNovelRating, | ||
| attractivePoints = novelRatingEntity.charmPoints, | ||
| startDate = novelRatingEntity.startDate.orEmpty(), | ||
| endDate = novelRatingEntity.endDate.orEmpty(), | ||
| keywords = novelRatingEntity.userKeywords.map { it.keywordName }, | ||
| myFeeds = feeds, | ||
| ) |
There was a problem hiding this comment.
🛠️ Refactor suggestion
userNovelId 계산 방식의 충돌 가능성
selectAllNovelsCount() 기반 오프셋으로 userNovelId를 생성하면, 삭제 이력이 있거나 동시 삽입 시 중복될 수 있습니다. 서버에서 내려준 ID를 사용하거나, Room @PrimaryKey(autoGenerate = true)로 위임하는 편이 안전합니다.
🤖 Prompt for AI Agents
In app/src/main/java/com/into/websoso/data/repository/UserNovelRepository.kt
between lines 29 and 44, the userNovelId is assigned using
selectAllNovelsCount() which can cause conflicts due to deletions or concurrent
inserts. To fix this, avoid manually generating userNovelId this way; instead,
use the ID provided by the server if available or delegate ID generation to Room
by marking userNovelId as @PrimaryKey(autoGenerate = true) and letting Room
handle it automatically.
| override suspend fun insertNovel(novel: NovelEntity) { | ||
| novelDao.apply { | ||
| val novelIndex = selectNovelByUserNovelId(novel.userNovelId)?.sortIndex ?: 0 | ||
| insertNovel(novel.toNovelDatabase(novelIndex)) | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
새 Novel 삽입 시 인덱스 계산 로직 확인 필요
존재하지 않는 경우 sortIndex = 0으로 삽입되면 리스트 맨 앞에 중복 인덱스가 생길 위험이 있습니다.
마지막 인덱스를 조회해 lastIndex + 1을 부여하거나 UNIQUE 제약을 두어 충돌을 방지해 주세요.
🤖 Prompt for AI Agents
In
core/database/src/main/java/com/into/websoso/core/database/datasource/library/DefaultLibraryLocalDataSource.kt
around lines 36 to 40, the current logic assigns sortIndex as 0 if no existing
novel is found, which risks duplicate indices at the front of the list. Modify
the code to query the highest existing sortIndex and assign the new novel a
sortIndex of lastIndex + 1 to ensure uniqueness. Alternatively, add a UNIQUE
constraint on sortIndex in the database schema to prevent collisions.
| interface LibraryRepository { | ||
| val libraryFlow: Flow<PagingData<NovelEntity>> | ||
|
|
||
| companion object { | ||
| const val PAGE_SIZE = 20 | ||
| } | ||
| } |
There was a problem hiding this comment.
💡 Verification agent
❓ Verification inconclusive
인터페이스 전환은 타당. 다만 PAGE_SIZE 상수의 위치 재검토 권장
PAGE_SIZE를 인터페이스 companion에 두면 구현별로 다른 페이지 크기를 쓰기 어렵고, 상수 결합도가 커집니다. PagingConfig 전용 오브젝트/모듈(예: LibraryPagingConfig)로 분리하거나 DI로 주입받아 구현체가 자율적으로 설정 가능하도록 하는 것을 권장합니다. 테스트에서도 주입이 수월해집니다.
아래 스크립트로 PagingSource/RemoteMediator가 싱글톤(객체 선언)으로 구현되지 않았는지 확인해 주세요. 과거 이슈에 따라 싱글톤 사용은 지양되어야 합니다.
🏁 Script executed:
#!/bin/bash
# PagingSource/RemoteMediator가 object로 선언되었는지 점검
rg -n "object\\s+\\w*PagingSource"
rg -n "object\\s+\\w*RemoteMediator"
# PagingSource 래퍼의 싱글톤 키워드/주석 탐색
rg -n "Singleton|@Singleton" -A 2 | rg -n "PagingSource|RemoteMediator|Paging"Length of output: 152
PAGE_SIZE 상수 위치 재검토 필요
LibraryRepository 인터페이스의 companion object에 정의된 PAGE_SIZE 상수는 구현체별로 다른 페이지 크기 설정을 어렵게 하고, 테스트 시에도 주입이 불편합니다. 아래와 같이 PagingConfig 전용 객체(예: LibraryPagingConfig)로 분리하거나 DI로 주입받아 구현체가 자율적으로 설정할 수 있도록 변경을 권장합니다.
- 대상 파일 및 위치
- data/library/src/main/java/com/into/websoso/data/library/LibraryRepository.kt
- companion object 내
const val PAGE_SIZE = 20
- companion object 내
- data/library/src/main/java/com/into/websoso/data/library/LibraryRepository.kt
- 참고: PagingSource/RemoteMediator가
object선언이나@Singleton어노테이션으로 싱글톤화된 부분은 확인되지 않아 해당 이슈는 없습니다.
🤖 Prompt for AI Agents
In data/library/src/main/java/com/into/websoso/data/library/LibraryRepository.kt
around lines 7 to 13, the PAGE_SIZE constant is defined inside the companion
object of the LibraryRepository interface, which limits flexibility for
different implementations and testing. To fix this, remove the PAGE_SIZE
constant from the companion object and instead create a separate PagingConfig
object or class (e.g., LibraryPagingConfig) that holds the page size
configuration. Then, inject this configuration into the implementations of
LibraryRepository so each can set its own page size independently.
| val lastUserNovelId = when (loadType) { | ||
| REFRESH -> { | ||
| deleteAllNovels() | ||
| DEFAULT_LAST_USER_NOVEL_ID | ||
| } | ||
|
|
There was a problem hiding this comment.
🛠️ Refactor suggestion
REFRESH 시 캐시 선삭제로 인한 빈 화면 위험
네트워크 오류가 발생하면 캐시를 이미 지운 상태로 남아 사용자에게 빈 목록이 노출될 수 있습니다.
성공적으로 새 데이터를 받아온 뒤에 삭제·삽입을 하나의 트랜잭션으로 처리하거나, 실패 시 이전 캐시를 보존하는 방법을 고려해 주세요.
🤖 Prompt for AI Agents
In
data/library/src/main/java/com/into/websoso/data/library/paging/LibraryRemoteMediator.kt
around lines 26 to 31, the current code deletes all cached novels before
fetching new data during a REFRESH load, which risks showing an empty screen if
the network request fails. To fix this, modify the logic to perform cache
deletion and insertion within a single transaction after successfully fetching
new data, or implement a rollback mechanism to preserve the previous cache if
the network request fails, ensuring the user does not see an empty list.
| val bottomSheetState = rememberModalBottomSheetState( | ||
| skipPartiallyExpanded = true, | ||
| confirmValueChange = { false }, | ||
| ) |
There was a problem hiding this comment.
BottomSheet가 절대 열리지 않는 설정입니다
confirmValueChange = { false } 로 두면 모든 상태 전이가 거부되어 show()/hide() 호출도 실패합니다. 필터 시트가 나타나지 않아 UX가 완전히 막히므로 아래처럼 허용 조건을 지정하세요.
- confirmValueChange = { false }
+ confirmValueChange = { it != SheetValue.Hidden } // Hidden 제외 전이 허용📝 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.
| val bottomSheetState = rememberModalBottomSheetState( | |
| skipPartiallyExpanded = true, | |
| confirmValueChange = { false }, | |
| ) | |
| val bottomSheetState = rememberModalBottomSheetState( | |
| skipPartiallyExpanded = true, | |
| confirmValueChange = { it != SheetValue.Hidden }, // Hidden 제외 전이 허용 | |
| ) |
🤖 Prompt for AI Agents
In
feature/library/src/main/java/com/into/websoso/feature/library/LibraryScreen.kt
around lines 65 to 68, the bottom sheet state is configured with
confirmValueChange always returning false, which blocks all state transitions
and prevents the bottom sheet from opening or closing. Modify confirmValueChange
to allow state changes by returning true or by specifying conditions that permit
opening and closing, so the filter sheet can appear and improve UX.
There was a problem hiding this comment.
@s9hn 피드백 감사합니다! 제가 잘못 이해했네요. confirmValueChange = { false }로 설정해도 정상 동작한다는 점 기억하겠습니다.
✏️ Learnings added
Learnt from: s9hn
PR: Team-WSS/WSS-Android#705
File: feature/library/src/main/java/com/into/websoso/feature/library/LibraryScreen.kt:65-68
Timestamp: 2025-08-09T09:44:15.246Z
Learning: In the LibraryScreen.kt file, the bottom sheet configuration with `rememberModalBottomSheetState(skipPartiallyExpanded = true, confirmValueChange = { false })` works correctly and does not prevent the bottom sheet from opening or closing, despite `confirmValueChange` returning false.
Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.
🧠 Learnings used
Learnt from: s9hn
PR: Team-WSS/WSS-Android#728
File: feature/library/src/main/java/com/into/websoso/feature/library/LibraryScreen.kt:77-84
Timestamp: 2025-07-24T09:02:01.000Z
Learning: `bottomSheetState.show()`는 바텀시트 애니메이션이 완료될 때까지 suspend하는 함수이므로, `invokeOnCompletion`에서 실행되는 코드는 바텀시트가 완전히 표시된 후에 실행된다.
| latestEffect.collectAsEventWithLifecycle { | ||
| if (uiState.isGrid) { | ||
| gridState.scrollToItem(SCROLL_POSITION_TOP) | ||
| } else { | ||
| listState.scrollToItem(SCROLL_POSITION_TOP) | ||
| } | ||
| } |
There was a problem hiding this comment.
suspend 함수를 일반 컨텍스트에서 호출하여 컴파일 오류 발생
LazyListState.scrollToItem() / LazyGridState.scrollToItem() 는 suspend 함수입니다. 현재 collectAsEventWithLifecycle의 람다는 suspend 컨텍스트가 아니므로 컴파일되지 않습니다.
latestEffect.collectAsEventWithLifecycle {
- if (uiState.isGrid) {
- gridState.scrollToItem(SCROLL_POSITION_TOP)
- } else {
- listState.scrollToItem(SCROLL_POSITION_TOP)
- }
+ scope.launch {
+ if (uiState.isGrid) {
+ gridState.animateScrollToItem(SCROLL_POSITION_TOP)
+ } else {
+ listState.animateScrollToItem(SCROLL_POSITION_TOP)
+ }
+ }
}코루틴으로 래핑하고 animateScrollToItem을 사용하면 UX도 부드럽게 개선됩니다.
📝 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.
| latestEffect.collectAsEventWithLifecycle { | |
| if (uiState.isGrid) { | |
| gridState.scrollToItem(SCROLL_POSITION_TOP) | |
| } else { | |
| listState.scrollToItem(SCROLL_POSITION_TOP) | |
| } | |
| } | |
| latestEffect.collectAsEventWithLifecycle { | |
| scope.launch { | |
| if (uiState.isGrid) { | |
| gridState.animateScrollToItem(SCROLL_POSITION_TOP) | |
| } else { | |
| listState.animateScrollToItem(SCROLL_POSITION_TOP) | |
| } | |
| } | |
| } |
🤖 Prompt for AI Agents
In
feature/library/src/main/java/com/into/websoso/feature/library/LibraryScreen.kt
around lines 70 to 76, the suspend functions scrollToItem are called inside a
non-suspend lambda passed to collectAsEventWithLifecycle, causing a compile
error. To fix this, wrap the scroll calls inside a coroutine scope launch block
and replace scrollToItem with animateScrollToItem to enable smooth scrolling and
proper suspend function usage.
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (3)
core/common/src/main/AndroidManifest.xml (1)
2-4: 의도적으로 빈 Manifest라면 주석으로 의도를 문서화 권장빈 Manifest(merge 전용)임을 간단 주석으로 남기면 추후 정리 과정에서의 오해(불필요한 추가/삭제)를 줄일 수 있습니다.
<?xml version="1.0" encoding="utf-8"?> -<manifest> +<manifest> + <!-- Intentionally empty: this library doesn't contribute manifest entries; merged at build time --> </manifest>app/src/main/java/com/into/websoso/data/repository/FeedRepository.kt (2)
134-152: 멀티스레드 환경에서_cachedFeeds동시성 문제 가능성
_cachedFeeds는MutableList로, 여러 코루틴에서fetchFeeds/saveRemovedFeed등을 동시에 호출하면 경합 조건이 발생할 수 있습니다.
Mutex나 스레드-세이프 컬렉션으로 보호하거나, UI 전용 캐시라면 단일 스레드(메인 디스패처)에서만 접근하도록 보장해주세요.
215-218:FILE_NAME_FEED상수가 사용되지 않습니다현재 모든 호출부에서
"feed.json"리터럴을 직접 넣고 있어 상수가 dead code 상태입니다.
상수를 실제로 사용하거나 제거해 주세요.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (15)
app/src/main/java/com/into/websoso/data/repository/FeedRepository.kt(4 hunks)app/src/main/java/com/into/websoso/data/repository/NovelRepository.kt(3 hunks)app/src/main/java/com/into/websoso/data/repository/UserNovelRepository.kt(2 hunks)app/src/main/java/com/into/websoso/ui/createFeed/CreateFeedActivity.kt(3 hunks)app/src/main/java/com/into/websoso/ui/createFeed/CreateFeedViewModel.kt(2 hunks)app/src/main/java/com/into/websoso/ui/feedDetail/FeedDetailViewModel.kt(1 hunks)app/src/main/java/com/into/websoso/ui/main/feed/FeedFragment.kt(1 hunks)app/src/main/java/com/into/websoso/ui/main/feed/FeedViewModel.kt(1 hunks)app/src/main/java/com/into/websoso/ui/novelDetail/NovelDetailActivity.kt(2 hunks)core/auth-kakao/src/main/AndroidManifest.xml(1 hunks)core/common/src/main/AndroidManifest.xml(1 hunks)core/datastore/src/main/AndroidManifest.xml(1 hunks)core/network/src/main/AndroidManifest.xml(1 hunks)data/account/src/main/AndroidManifest.xml(1 hunks)feature/signin/src/main/AndroidManifest.xml(1 hunks)
✅ Files skipped from review due to trivial changes (1)
- app/src/main/java/com/into/websoso/ui/main/feed/FeedFragment.kt
🚧 Files skipped from review as they are similar to previous changes (7)
- app/src/main/java/com/into/websoso/ui/createFeed/CreateFeedActivity.kt
- app/src/main/java/com/into/websoso/ui/main/feed/FeedViewModel.kt
- app/src/main/java/com/into/websoso/ui/feedDetail/FeedDetailViewModel.kt
- app/src/main/java/com/into/websoso/ui/novelDetail/NovelDetailActivity.kt
- app/src/main/java/com/into/websoso/ui/createFeed/CreateFeedViewModel.kt
- app/src/main/java/com/into/websoso/data/repository/NovelRepository.kt
- app/src/main/java/com/into/websoso/data/repository/UserNovelRepository.kt
🔇 Additional comments (8)
core/datastore/src/main/AndroidManifest.xml (1)
2-2: 일관성 있는 네임스페이스 정리 좋습니다 — 빌드 설정만 검증해주세요파일 내 android: 접두 속성이 없으므로 xmlns 생략은 문제 없습니다. 다만 AGP 8+로의 전환 및 각 모듈의 android { namespace } 설정 여부를 확인해 주세요. 향후 android: 속성을 추가하면 네임스페이스 선언이 필요합니다.
위 코멘트에 첨부한 스크립트로 namespace 선언과 android: 사용 여부를 점검해 주세요.
core/network/src/main/AndroidManifest.xml (1)
2-2: 네임스페이스 생략은 현재 구조에서 안전 — 향후 변경 시 주의현재 문서에 android: 접두 속성이 없으므로 xmlns 생략은 유효합니다. 다만 manifest에 요소/속성이 추가될 경우(예: android:exported 등) 루트에 xmlns:android 재도입이 필요합니다. 또한 android { namespace } 구성이 완료되었는지 확인 부탁드립니다.
상단 스크립트로 빠르게 검증 가능합니다.
data/account/src/main/AndroidManifest.xml (1)
2-2: 정리된 빈 매니페스트 유지 OK — namespace/AGP 버전 점검 권장현재 상태로는 문제가 없으나, 차후 이 파일에 android: 접두 속성 추가 시 루트에 xmlns:android 선언이 필요합니다. 모듈의 android { namespace } 선언 및 AGP 설정을 함께 확인해 주세요.
위 공통 스크립트로 검증해 주시면 됩니다.
feature/signin/src/main/AndroidManifest.xml (1)
2-2: xmlns 제거 방향성 동의 — 변경 파급만 최소 확인파일 자체는 안전합니다. 빌드 설정(android { namespace })이 준비되어 있고, 해당 매니페스트 내부에 android: 접두 사용이 없는지 확인만 부탁드립니다. 향후 속성 추가 시에는 xmlns 재도입이 필요합니다.
공통 검증 스크립트로 확인해 주세요.
core/common/src/main/AndroidManifest.xml (2)
2-2: 모든 모듈 AndroidManifest.xml 점검 완료:android:접두사를 사용하는 모든 Manifest 파일에xmlns:android="http://schemas.android.com/apk/res/android"선언이 존재함을 확인했습니다. 병합 혹은 빌드 실패 가능성은 없습니다.
2-2: 패키지 및 네임스페이스 검증 완료
스크립트 실행 결과 AndroidManifest.xml에package속성이 없는 모듈과 Gradlenamespace가 누락된 모듈이 발견되지 않았습니다. 추가 조치가 필요 없습니다.app/src/main/java/com/into/websoso/data/repository/FeedRepository.kt (2)
75-83: 로컬 캐시 갱신 시 트랜잭션 보장이 없습니다
insertNovel(updatedNovel)이 실패하면(예: DB I/O 예외) 원격은 성공하고 로컬만 불일치 상태가 됩니다.
Room트랜잭션 또는libraryLocalDataSource내부에서 예외를 래핑해 동일한Result체인으로 묶어주세요.
97-111: 상수 미사용 및 파트 이름 불일치
partName = "feed"/fileName = "feed.json"— 이미 companion object 에 상수가 정의돼 있으므로 재사용하세요.saveFeed()에서는PART_NAME_FEED를 쓰지만saveEditedFeed()는 리터럴 사용으로 불일치가 발생합니다.
[ suggest_nitpick ]- feedRequestDto = multiPartMapper.formatToMultipart<FeedRequestDto>( + feedRequestDto = multiPartMapper.formatToMultipart<FeedRequestDto>( target = …, - partName = "feed", - fileName = "feed.json", + partName = PART_NAME_FEED, + fileName = FILE_NAME_FEED, )
| runCatching { | ||
| feedApi.postFeed( | ||
| feedRequestDto = multiPartMapper.formatToMultipart<FeedRequestDto>( | ||
| target = FeedRequestDto( | ||
| relevantCategories = relevantCategories, | ||
| feedContent = feedContent, | ||
| novelId = novelId, | ||
| isSpoiler = isSpoiler, | ||
| isPublic = isPublic, | ||
| ), | ||
| partName = PART_NAME_FEED, | ||
| fileName = "feed.json", | ||
| ), | ||
| partName = PART_NAME_FEED, | ||
| fileName = "feed.json", | ||
| ), | ||
| images = images.map { multiPartMapper.formatToMultipart(it) }, | ||
| ) | ||
| images = images.map { multiPartMapper.formatToMultipart(it) }, | ||
| ) | ||
| }.onSuccess { |
There was a problem hiding this comment.
예외가 호출자까지 전파되지 않아 오류 인지가 어렵습니다
runCatching { … }.onSuccess { … } 패턴만 사용하고 onFailure·getOrThrow 등을 호출하지 않아 API 호출 실패 시 아무런 로그·예외도 전달되지 않습니다.
UI 단에서 실패-성공 플로우를 구분하려면 Result 반환(또는 예외 전파)이 필요합니다.
-suspend fun saveFeed(...): Unit {
- runCatching {
- feedApi.postFeed(...)
- }.onSuccess { … }
+/** Result<Unit> 등을 반환하여 호출자가 성공/실패를 알 수 있게 합니다. */
+suspend fun saveFeed(...): Result<Unit> =
+ runCatching {
+ feedApi.postFeed(...)
+ /* 성공 후 로컬 갱신 */
+ ...
+ }Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In app/src/main/java/com/into/websoso/data/repository/FeedRepository.kt around
lines 59 to 74, the runCatching block handles API calls but does not propagate
exceptions or return a Result, causing failures to be silent. Modify the code to
either return the Result from runCatching to the caller or explicitly handle
onFailure by logging or rethrowing the exception, ensuring that errors are
visible and can be handled properly in the UI layer.
| legacyFeed: String, | ||
| novelId: Long?, |
There was a problem hiding this comment.
🛠️ Refactor suggestion
동일 콘텐츠 중복 시 모든 항목이 교체됩니다
myFeeds.map { if (it == legacyFeed) editedFeed else it } 는 같은 내용의 피드가 여러 개일 때 전부 수정됩니다.
feedId 를 키로 비교하거나 indexOfFirst 로 특정 위치만 교체하도록 수정이 필요합니다.
Also applies to: 119-124
🤖 Prompt for AI Agents
In app/src/main/java/com/into/websoso/data/repository/FeedRepository.kt around
lines 90-91 and also 119-124, the code replaces all entries matching legacyFeed
in myFeeds, causing multiple identical feeds to be replaced. Modify the logic to
identify the specific feed to replace by comparing feedId or using indexOfFirst
to find the exact position, then replace only that single item instead of all
matching ones.
| @@ -1,4 +1,4 @@ | |||
| <?xml version="1.0" encoding="utf-8"?> | |||
| <manifest xmlns:android="http://schemas.android.com/apk/res/android"> | |||
| <manifest> | |||
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
xmlns:android 제거 OK — AGP/namespace 구성만 확인 부탁
빈 매니페스트에서 android: 접두 속성을 사용하지 않는 한 xmlns 생략은 유효합니다. 다만, 모듈의 build.gradle(.kts)에서 android { namespace }가 설정되어 있고, 이 파일 내에 android: 접두 속성이 없는지 확인해 주세요. 향후 android: 속성을 추가할 경우 네임스페이스를 다시 선언해야 합니다.
아래 스크립트로 빠르게 점검할 수 있습니다.
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "[1] 각 모듈의 android { namespace } 선언 확인"
fd -a --extension kts build.gradle | xargs -I{} rg -n "android\s*\{\s*namespace\s*=" "{}" || true
echo
echo "[2] 대상 매니페스트에서 android: 접두 사용 여부 점검 (있다면 xmlns 필요)"
for f in \
core/auth-kakao/src/main/AndroidManifest.xml \
core/datastore/src/main/AndroidManifest.xml \
core/network/src/main/AndroidManifest.xml \
data/account/src/main/AndroidManifest.xml \
feature/signin/src/main/AndroidManifest.xml
do
echo ">> $f"
rg -n "android:" "$f" || echo " (android: 접두 사용 없음)"
doneLength of output: 2540
android:namespace 미설정으로 xmlns:android 제거 불가 — build.gradle(.kts)에 namespace 선언 추가 필요
현재 아래 모든 모듈에서 android { namespace = "..." } 설정이 누락되어 있어, Manifest에서 xmlns:android를 제거하면 컴파일 오류가 발생합니다.
각 모듈의 build.gradle 또는 build.gradle.kts에 올바른 namespace를 선언하거나, 선언 전까지는 xmlns:android 속성을 유지해 주세요.
· core/auth-kakao/build.gradle(.kts)
· core/datastore/build.gradle(.kts)
· core/network/build.gradle(.kts)
· data/account/build.gradle(.kts)
· feature/signin/build.gradle(.kts)
🤖 Prompt for AI Agents
In core/auth-kakao/src/main/AndroidManifest.xml at line 2, the xmlns:android
attribute cannot be removed because the android namespace is not set in the
module's build.gradle or build.gradle.kts file. To fix this, add the android
namespace declaration (e.g., android { namespace = "your.namespace.here" }) in
core/auth-kakao/build.gradle or build.gradle.kts. Until this is done, keep the
xmlns:android attribute in the manifest to avoid build errors.
📌𝘐𝘴𝘴𝘶𝘦𝘴
📎𝘞𝘰𝘳𝘬 𝘋𝘦𝘴𝘤𝘳𝘪𝘱𝘵𝘪𝘰𝘯
New Features
개선 및 리팩터링
📷𝘚𝘤𝘳𝘦𝘦𝘯𝘴𝘩𝘰𝘵
KakaoTalk_Video_2025-08-09-19-17-35.mp4
💬𝘛𝘰 𝘙𝘦𝘷𝘪𝘦𝘸𝘦𝘳𝘴
드디어 3개월동안 진행된 서재 리뉴얼 작업이 마무리되는군요..
Summary by CodeRabbit
New Features
UI/UX 개선
버그 수정 및 안정성
기타