Skip to content

[FEAT/#10] 앱에 서버 연결하기 - #13

Open
noeyhoj wants to merge 9 commits into
developfrom
feature/connect-server
Open

[FEAT/#10] 앱에 서버 연결하기#13
noeyhoj wants to merge 9 commits into
developfrom
feature/connect-server

Conversation

@noeyhoj

@noeyhoj noeyhoj commented Jun 25, 2026

Copy link
Copy Markdown
Owner

📍 관련 이슈

📝 구현한 기능 목록

1. Retrofit을 사용해 supabase 서버와 연결함

Retrofit 라이브러리를 사용해 서버와 연결했다. 이 때 Network라는 것을 먼저 만들었다. Network에서는 어떤 url에 연결할 것인지, json 역직렬화를 어떻게 할지 설정한다.

그리고 API 코드를 생성했다. API는 서버와 통신할 때 CRUD 명령에 대한 설정을 하는 것이다. 내 코드에서는 GET()을 사용해 서버에서 정보를 불러오도록 하였다.

서버에서 받아온 json 데이터를 역직렬화하기 위해 DTO라는 것을 만들었다. DTO에는 Serializable 어노테이션을 붙여준다. 또한 파라미터에 SerialName 어노테이션을 붙여줌으로써 테이블 컬럼명과 코틀린 객체의 이름이 일치하지 않는 문제를 해결할 수 있다.

2. StateHolder를 ViewModel로 변환함

StateHolder를 ViewModel로 바꾼 이유는 서버와의 통신을 쉽게 하기 위함이다. 서버와 통신을 하기 위해서는 비동기 코드를 작성해야 한다. 이 때 코루틴을 사용한다.

하지만 StateHolder의 경우 비동기 처리를 위해 직접 코루틴 스코프를 관리해야한다. 반면 ViewModel은 내부에서 viewModelScope를 지원해주며, 관리까지 알아서 해준다는 이점이 있다.

3. Application 코드 생성함

Application은 앱에서 단 하나만 존재하며 전역 싱글톤으로 선언된다. 또한 Application은 앱이 시작할 때 실행되며, 앱이 종료되기 전까지 살아있다. 그래서 Application 코드 안에서 서버 연결과 같은 무겁고 초기에만 실행하면 되는 작업을 넣어서 실행한다는 것을 배웠다.

이 때 Activity에서 초기화하면 안되는 것인가? 라는 의문이 들었다. 되긴 하지만 Activity에서 초기화하면 안되는 이유가 명확했다. 첫 번째로 Activity는 화면을 그리는 단위다. 여기서 무거운 작업에 대한 실행을 하게 되면 UI가 그려지는게 늦어질 수 있다. 또한 Activity를 변경할 경우, 구성 변경이 일어날 경우 등 Activity가 초기화하고 다시 생성되면서 서버 연결을 재실행하게 된다. 이는 메모리, 배터리를 불필요하게 사용하게 되는 문제로 이어지게 된다.

📸 스크린샷 (UI 변경 시)

AS-IS (기존) TO-BE (변경 후)
before.mp4
server_connect.mp4

✅ 체크리스트

  • 스스로 코드를 한 번 리뷰했나요?
  • 빌드가 정상적으로 완료되나요?

Summary by CodeRabbit

  • New Features

    • 홈과 상세 화면이 실제 데이터 소스에서 목록/상세 정보를 불러오도록 바뀌었습니다.
    • 앱 시작 시 필요한 초기 설정이 자동으로 준비됩니다.
  • Bug Fixes

    • 색상 표시가 더 정확하게 반영되도록 수정했습니다.
    • 일부 누락된 정보는 기본값으로 안전하게 처리됩니다.
  • Refactor

    • 화면 상태 관리 방식이 정리되어 더 안정적인 화면 전환과 데이터 표시를 지원합니다.

@coderabbitai

coderabbitai Bot commented Jun 25, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

앱 시작 시 GundamApplicationAppContainer를 통해 Supabase 기반 저장소를 구성하고, GundamApi/DTO/Repository로 실제 서버 데이터를 조회하도록 추가했다. 홈과 상세 화면은 ViewModel과 Navigation으로 상태를 구독하도록 바뀌었고, 상세 색상 매핑도 GREY 기준으로 갱신했다.

Changes

서버 연동과 화면 전환

Layer / File(s) Summary
앱 시작점과 Supabase 클라이언트
app/src/main/AndroidManifest.xml, app/src/main/java/com/example/gundamdexapp/GundamApplication.kt, app/src/main/java/com/example/gundamdexapp/data/network/AppContainer.kt, app/src/main/java/com/example/gundamdexapp/data/network/SupabaseNetwork.kt, gradle/libs.versions.toml
AndroidManifest.GundamApplication을 사용하도록 바뀌고, AppContainerSupabaseNetwork가 Supabase 기반 저장소를 만들도록 추가되었다.
REST 계약과 DTO 변환
app/src/main/java/com/example/gundamdexapp/data/network/api/GundamApi.kt, app/src/main/java/com/example/gundamdexapp/data/network/dto/ArmamentDto.kt, app/src/main/java/com/example/gundamdexapp/data/network/dto/GundamDto.kt, app/src/main/java/com/example/gundamdexapp/domain/repository/GundamRepository.kt, app/src/main/java/com/example/gundamdexapp/data/network/repositoryImpl/GundamRepositoryImpl.kt
Supabase gundams 엔드포인트 계약, DTO 변환, GundamRepository 구현이 추가되어 목록과 상세 조회가 Result로 반환된다.
홈 화면 ViewModel 주입
app/src/main/java/com/example/gundamdexapp/MainActivity.kt, app/src/main/java/com/example/gundamdexapp/feature/home/..., app/src/main/java/com/example/gundamdexapp/feature/navigation/GundamdexNavigation.kt, app/src/main/java/com/example/gundamdexapp/feature/utils/SharedTransitionKey.kt
MainActivity가 홈 ViewModel을 생성해 navigation에 전달하고, 홈 라우트는 uiState를 구독하며 공유 전환 키에 id가 추가되었다.
상세 화면 ViewModel과 색상 매핑
app/src/main/java/com/example/gundamdexapp/feature/detail/...
상세 ViewModel이 저장소 결과를 UI 모델로 변환하고, 상세 라우트와 IndicatorColor 매핑이 GREY 표기에 맞게 갱신되었다.

Sequence Diagram(s)

sequenceDiagram
  participant MainActivity
  participant GundamdexHomeViewModel
  participant GundamRepositoryImpl
  participant GundamApi
  MainActivity->>GundamdexHomeViewModel: viewModels<GundamdexHomeViewModel>()
  GundamdexHomeViewModel->>GundamRepositoryImpl: getGundamList()
  GundamRepositoryImpl->>GundamApi: getGundams()
  GundamApi-->>GundamRepositoryImpl: Response<List<GundamDto>>
  GundamRepositoryImpl-->>GundamdexHomeViewModel: Result<List<GundamInfo>>
Loading
sequenceDiagram
  participant DetailRoute
  participant GundamdexDetailViewModel
  participant GundamRepositoryImpl
  participant GundamApi
  DetailRoute->>GundamdexDetailViewModel: Factory(id)
  GundamdexDetailViewModel->>GundamRepositoryImpl: getGundamDetail(id)
  GundamRepositoryImpl->>GundamApi: getGundamDetail(idFilter)
  GundamApi-->>GundamRepositoryImpl: Response<List<GundamDto>>
  GundamRepositoryImpl-->>GundamdexDetailViewModel: Result<List<GundamInfo>>
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • noeyhoj/Gundamdex#3: IndicatorColorIndicatorColorMapper의 색상 매핑 변경이 같은 상세 화면 렌더링 경로를 수정한다.
  • noeyhoj/Gundamdex#8: GundamdexNavigation의 상세 진입 흐름과 id 기반 화면 전환 구성이 겹친다.
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed 제목이 서버 연결 구현이라는 주요 변경을 간결하게 요약하고 있어 변경 내용과 잘 맞습니다.
Description check ✅ Passed 필수 섹션인 관련 이슈, 구현한 기능 목록, 스크린샷, 체크리스트가 모두 포함되어 템플릿을 대부분 충족합니다.
Linked Issues check ✅ Passed 실제 서버 연동용 네트워크, DTO, 리포지토리, ViewModel, Application 추가로 #10의 핵심 요구를 충족합니다.
Out of Scope Changes check ✅ Passed 네트워크 연동과 상태관리 전환을 위한 변경들로 보이며, 명백히 무관한 범위 밖 수정은 보이지 않습니다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/connect-server

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🧹 Nitpick comments (6)
app/src/main/java/com/example/gundamdexapp/MainActivity.kt (1)

18-19: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

사용되지 않는 gundamRepository 변수입니다.

application.appContainer.gundamRepository를 조회하지만 이후 코드에서 전혀 사용되지 않습니다. GundamdexHomeViewModel.FactoryAPPLICATION_KEY를 통해 저장소를 직접 획득하므로 이 두 줄은 죽은 코드입니다. 제거를 권장합니다.

♻️ 제안 수정
-        val application = application as GundamApplication
-        val gundamRepository = application.appContainer.gundamRepository
-
         val gundamdexHomeViewModel by viewModels<GundamdexHomeViewModel> { GundamdexHomeViewModel.Factory }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/main/java/com/example/gundamdexapp/MainActivity.kt` around lines 18 -
19, The `MainActivity` setup contains dead code:
`application.appContainer.gundamRepository` is retrieved into `gundamRepository`
but never used because `GundamdexHomeViewModel.Factory` already resolves the
repository via `APPLICATION_KEY`. Remove the unused `gundamRepository`
assignment from `MainActivity` and keep the factory initialization focused on
the application reference only.
app/src/main/java/com/example/gundamdexapp/feature/navigation/GundamdexNavigation.kt (1)

29-29: 🚀 Performance & Scalability | 🔵 Trivial

collectAsState 대신 collectAsStateWithLifecycle 를 사용하세요.

libs.versions.toml 에서 androidx-lifecycle-runtime-compose 의존성이 이미 존재합니다. collectAsStateWithLifecycle 를 사용하면 Composable 이 생명주기 STARTED 상태가 아닐 때 불필요한 상태 수집을 중단하여 성능을 최적화할 수 있습니다.

- val uiState by gundamdexHomeViewModel.uiState.collectAsState()
+ val uiState by gundamdexHomeViewModel.uiState.collectAsStateWithLifecycle()
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@app/src/main/java/com/example/gundamdexapp/feature/navigation/GundamdexNavigation.kt`
at line 29, The state collection in GundamdexNavigation should use
lifecycle-aware collection instead of collectAsState. Update the uiState
collection on GundamdexHomeViewModel to use collectAsStateWithLifecycle so the
Composable only collects while the lifecycle is STARTED. Make sure the existing
androidx.lifecycle.runtime.compose import/dependency is used and the change is
applied at the uiState collection site in GundamdexNavigation.
app/src/main/java/com/example/gundamdexapp/data/network/repositoryImpl/GundamRepositoryImpl.kt (1)

10-38: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

중복된 응답 처리 로직 추출 권장

getGundamListgetGundamDetailResponseResult 변환 로직(성공 시 toDomain() 매핑, 실패 시 코드 포함 예외, catch 처리)이 거의 동일합니다. 공통 헬퍼로 추출하면 중복이 제거되고 에러 메시지 일관성도 유지됩니다.

♻️ 공통 헬퍼 추출 예시
+    private inline fun <T, R> Response<List<T>>.toDomainResult(
+        transform: (T) -> R,
+    ): Result<List<R>> =
+        if (isSuccessful) {
+            Result.success((body() ?: emptyList()).map(transform))
+        } else {
+            Result.failure(Exception("서버 통신 실패 : ${code()}"))
+        }
+
-    override suspend fun getGundamList(): Result<List<GundamInfo>> = try {
-        val response = api.getGundams()
-
-        if (response.isSuccessful) {
-            val dtoList = response.body() ?: emptyList()
-
-            val domainList = dtoList.map { it.toDomain() }
-            Result.success(domainList)
-        } else {
-            Result.failure(Exception("서버 통신 실패 : ${response.code()}"))
-        }
-    } catch (e: Exception) {
-        Result.failure(e)
-    }
+    override suspend fun getGundamList(): Result<List<GundamInfo>> = try {
+        api.getGundams().toDomainResult { it.toDomain() }
+    } catch (e: Exception) {
+        Result.failure(e)
+    }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@app/src/main/java/com/example/gundamdexapp/data/network/repositoryImpl/GundamRepositoryImpl.kt`
around lines 10 - 38, `GundamRepositoryImpl`의 `getGundamList`와
`getGundamDetail`에서 `Response`를 `Result`로 바꾸는 로직이 중복되어 있습니다. 공통 처리용 private 헬퍼를
추출해 `api.getGundams()`와 `api.getGundamDetail()`의 성공 시 `toDomain()` 매핑, 실패 시 코드
포함 예외 생성, `catch`에서 `Result.failure` 반환을 한곳에서 처리하도록 정리하세요. 이렇게 하면 두 메서드는 API 호출과
파라미터(idFilter)만 남기고, 에러 메시지 형식도 `GundamRepositoryImpl` 전체에서 일관되게 유지할 수 있습니다.
gradle/libs.versions.toml (1)

30-52: 📐 Maintainability & Code Quality | 🔵 Trivial

Supabase BOM 의존성명 구체화 권장

postgrest-ktplatform(libs.bom)을 통해 버전이 관리되므로 빌드 오류는 없습니다. 다만, libs.versions.tomlbom 별칭이 모호하여 supabase-bom 등으로 구체화하면 가독성이 향상됩니다.

BOM 설정 확인
app/build.gradle.kts
implementation(platform(libs.bom))
implementation(libs.postgrest.kt)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@gradle/libs.versions.toml` around lines 30 - 52, The `bom` alias in
`libs.versions.toml` is too generic and should be renamed to a more descriptive
Supabase-specific name. Update the version catalog entry for the Supabase BOM
and any references that use `libs.bom` so the identifier clearly indicates it is
the Supabase BOM, improving readability without changing dependency management
behavior.
app/src/main/java/com/example/gundamdexapp/domain/repository/GundamRepository.kt (1)

7-7: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

상세 조회가 단건임에도 List를 반환하여 도메인 추상화가 전송 계층 형태에 종속됩니다.

getGundamDetail은 단일 항목 조회인데 Result<List<GundamInfo>>를 반환하고, 소비자(GundamdexDetailViewModel)는 getOrNull()?.firstOrNull()로 매번 첫 요소만 꺼내 씁니다. Supabase가 배열을 돌려주는 것은 구현 세부사항이므로, 도메인 인터페이스는 Result<GundamInfo>(또는 nullable)로 단건 의미를 표현해 변환 책임을 구현체에 두는 편이 클린 아키텍처에 부합합니다.

♻️ 제안 수정
 interface GundamRepository {
     suspend fun getGundamList(): Result<List<GundamInfo>>
-    suspend fun getGundamDetail(id: String): Result<List<GundamInfo>>
+    suspend fun getGundamDetail(id: String): Result<GundamInfo>
 }

GundamRepositoryImpl.getGundamDetailGundamdexDetailViewModel도 함께 수정해야 합니다. 도메인 로직과 UI 로직 분리를 강조하는 경로 지침에 근거합니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@app/src/main/java/com/example/gundamdexapp/domain/repository/GundamRepository.kt`
at line 7, `GundamRepository.getGundamDetail` is modeling a single-detail lookup
as `Result<List<GundamInfo>>`, which leaks the transport shape into the domain
API. Change the repository contract to return a single `GundamInfo` result (or
nullable) and move any list-to-single conversion into
`GundamRepositoryImpl.getGundamDetail`. Then update `GundamdexDetailViewModel`
to consume the single item directly instead of calling
`getOrNull()?.firstOrNull()`.

Source: Path instructions

app/src/main/java/com/example/gundamdexapp/data/network/dto/GundamDto.kt (1)

65-66: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

MISSING_URL fallback에 외부 CDN URL을 하드코딩하고 있습니다.

앱과 무관한 외부 서비스(BladeNSoul) CDN URL을 placeholder로 사용하면 해당 리소스가 변경/삭제될 경우 이미지가 깨질 수 있습니다. 앱 번들 내 로컬 placeholder 리소스나 자체 호스팅 자산을 사용하는 편이 안정적입니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/main/java/com/example/gundamdexapp/data/network/dto/GundamDto.kt`
around lines 65 - 66, The MISSING_URL fallback in GundamDto should not hardcode
an external BladeNSoul CDN URL. Replace it with a stable app-owned placeholder
source, such as a bundled drawable/resource or a self-hosted asset, and update
the constant in GundamDto so any missing image paths resolve to that local
placeholder instead of the external URL.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/src/main/java/com/example/gundamdexapp/data/network/dto/ArmamentDto.kt`:
- Around line 13-19: `ArmamentDto`의 `indicatorColor`가 non-null이라
`indicator_color`가 누락되거나 null일 때 `toDomain()` 전에 역직렬화가 실패합니다. `GundamDto`와
`details` 처리처럼 `indicatorColor`를 nullable로 바꾸고 `toDomain()`에서 기본값으로 fallback하도록
수정해, `ArmamentDto` 및 `ArmamentDto.toDomain()` 경로가 서버 응답 결측에 안전하도록 만드세요.

In
`@app/src/main/java/com/example/gundamdexapp/data/network/repositoryImpl/GundamRepositoryImpl.kt`:
- Around line 25-35: `GundamRepositoryImpl.getGundamDetail` is returning an
empty list as a successful result, but the current flow later turns that into
`null` and triggers `IllegalArgumentException` in `setUiState`. Update the
repository/UI contract so empty detail responses are handled explicitly as a
normal state (for example, a failure result or a dedicated empty-content UI
state) instead of relying on `throw` in the UI layer. Check the
`getGundamDetail` repository method and the `setUiState` logic that consumes its
result, and make the state transition consistent with the intended behavior.

In `@app/src/main/java/com/example/gundamdexapp/data/network/SupabaseNetwork.kt`:
- Around line 14-20: The current HttpLoggingInterceptor setup in SupabaseNetwork
is logging full headers at BODY level, which can expose apikey and Authorization
values in release builds. Update the logging configuration in the
loggingInterceptor/okHttpClient setup so BODY logging is enabled only for debug
builds and disabled or removed for release builds, using the build-type check at
the point where the OkHttpClient.Builder is configured.

In
`@app/src/main/java/com/example/gundamdexapp/feature/detail/GundamdexDetailViewModel.kt`:
- Around line 29-34: The GundamdexDetailViewModel init flow currently lets a
null detail result from getGundamDetail(id) reach setUiState, which throws and
can crash the app. Update GundamdexDetailViewModel so setUiState and the init
launch path handle missing data gracefully by mapping null or failed fetches
into GundamdexDetailUiState error/empty state instead of throwing
IllegalArgumentException. Keep the behavior localized around getGundamDetail,
viewModelScope.launch, and setUiState so the UI can render a fallback state when
the detail is unavailable.

In
`@app/src/main/java/com/example/gundamdexapp/feature/detail/uimodel/IndicatorColor.kt`:
- Around line 10-15: `IndicatorColor.dataToIndicatorColor` is throwing on any
server value outside the current hardcoded set, which can crash
`GundamdexDetailViewModel` during DTO-to-UI mapping. Update
`dataToIndicatorColor` to normalize the input (for example, case-insensitive
handling and accepting `"gray"` as well as `"grey"`), and replace the exception
path with a safe fallback color in `IndicatorColor` for unknown or new server
values.

In
`@app/src/main/java/com/example/gundamdexapp/feature/home/GundamdexHomeViewModel.kt`:
- Around line 26-43: `GundamdexHomeViewModel.getGundamList()` currently unwraps
the repository `Result` with `getOrThrow()` inside `viewModelScope.launch`, so
any Supabase/network failure will crash the coroutine and app. Update the flow
to handle failures explicitly in `getGundamList()` by branching with
`onSuccess`/`onFailure` (or equivalent try/catch), and use
`GundamdexHomeUiState`/`GundamdexHomeUiModel` to represent loading and error
states so the UI can react safely instead of propagating the exception.

---

Nitpick comments:
In `@app/src/main/java/com/example/gundamdexapp/data/network/dto/GundamDto.kt`:
- Around line 65-66: The MISSING_URL fallback in GundamDto should not hardcode
an external BladeNSoul CDN URL. Replace it with a stable app-owned placeholder
source, such as a bundled drawable/resource or a self-hosted asset, and update
the constant in GundamDto so any missing image paths resolve to that local
placeholder instead of the external URL.

In
`@app/src/main/java/com/example/gundamdexapp/data/network/repositoryImpl/GundamRepositoryImpl.kt`:
- Around line 10-38: `GundamRepositoryImpl`의 `getGundamList`와
`getGundamDetail`에서 `Response`를 `Result`로 바꾸는 로직이 중복되어 있습니다. 공통 처리용 private 헬퍼를
추출해 `api.getGundams()`와 `api.getGundamDetail()`의 성공 시 `toDomain()` 매핑, 실패 시 코드
포함 예외 생성, `catch`에서 `Result.failure` 반환을 한곳에서 처리하도록 정리하세요. 이렇게 하면 두 메서드는 API 호출과
파라미터(idFilter)만 남기고, 에러 메시지 형식도 `GundamRepositoryImpl` 전체에서 일관되게 유지할 수 있습니다.

In
`@app/src/main/java/com/example/gundamdexapp/domain/repository/GundamRepository.kt`:
- Line 7: `GundamRepository.getGundamDetail` is modeling a single-detail lookup
as `Result<List<GundamInfo>>`, which leaks the transport shape into the domain
API. Change the repository contract to return a single `GundamInfo` result (or
nullable) and move any list-to-single conversion into
`GundamRepositoryImpl.getGundamDetail`. Then update `GundamdexDetailViewModel`
to consume the single item directly instead of calling
`getOrNull()?.firstOrNull()`.

In
`@app/src/main/java/com/example/gundamdexapp/feature/navigation/GundamdexNavigation.kt`:
- Line 29: The state collection in GundamdexNavigation should use
lifecycle-aware collection instead of collectAsState. Update the uiState
collection on GundamdexHomeViewModel to use collectAsStateWithLifecycle so the
Composable only collects while the lifecycle is STARTED. Make sure the existing
androidx.lifecycle.runtime.compose import/dependency is used and the change is
applied at the uiState collection site in GundamdexNavigation.

In `@app/src/main/java/com/example/gundamdexapp/MainActivity.kt`:
- Around line 18-19: The `MainActivity` setup contains dead code:
`application.appContainer.gundamRepository` is retrieved into `gundamRepository`
but never used because `GundamdexHomeViewModel.Factory` already resolves the
repository via `APPLICATION_KEY`. Remove the unused `gundamRepository`
assignment from `MainActivity` and keep the factory initialization focused on
the application reference only.

In `@gradle/libs.versions.toml`:
- Around line 30-52: The `bom` alias in `libs.versions.toml` is too generic and
should be renamed to a more descriptive Supabase-specific name. Update the
version catalog entry for the Supabase BOM and any references that use
`libs.bom` so the identifier clearly indicates it is the Supabase BOM, improving
readability without changing dependency management behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 92b65ed3-e3cd-491e-96f2-b7829ce51299

📥 Commits

Reviewing files that changed from the base of the PR and between 43deaec and 888defb.

⛔ Files ignored due to path filters (1)
  • app/build.gradle.kts is excluded by !**/*.gradle.kts
📒 Files selected for processing (19)
  • app/src/main/AndroidManifest.xml
  • app/src/main/java/com/example/gundamdexapp/GundamApplication.kt
  • app/src/main/java/com/example/gundamdexapp/MainActivity.kt
  • app/src/main/java/com/example/gundamdexapp/data/network/AppContainer.kt
  • app/src/main/java/com/example/gundamdexapp/data/network/SupabaseNetwork.kt
  • app/src/main/java/com/example/gundamdexapp/data/network/api/GundamApi.kt
  • app/src/main/java/com/example/gundamdexapp/data/network/dto/ArmamentDto.kt
  • app/src/main/java/com/example/gundamdexapp/data/network/dto/GundamDto.kt
  • app/src/main/java/com/example/gundamdexapp/data/network/repositoryImpl/GundamRepositoryImpl.kt
  • app/src/main/java/com/example/gundamdexapp/domain/repository/GundamRepository.kt
  • app/src/main/java/com/example/gundamdexapp/feature/detail/GundamdexDetailStateHolder.kt
  • app/src/main/java/com/example/gundamdexapp/feature/detail/GundamdexDetailViewModel.kt
  • app/src/main/java/com/example/gundamdexapp/feature/detail/mapper/IndicatorColorMapper.kt
  • app/src/main/java/com/example/gundamdexapp/feature/detail/uimodel/IndicatorColor.kt
  • app/src/main/java/com/example/gundamdexapp/feature/home/GundamdexHomeStatHolder.kt
  • app/src/main/java/com/example/gundamdexapp/feature/home/GundamdexHomeViewModel.kt
  • app/src/main/java/com/example/gundamdexapp/feature/navigation/GundamdexNavigation.kt
  • app/src/main/java/com/example/gundamdexapp/feature/utils/SharedTransitionKey.kt
  • gradle/libs.versions.toml
💤 Files with no reviewable changes (2)
  • app/src/main/java/com/example/gundamdexapp/feature/home/GundamdexHomeStatHolder.kt
  • app/src/main/java/com/example/gundamdexapp/feature/detail/GundamdexDetailStateHolder.kt

Comment on lines +13 to +19
@SerialName("indicator_color")
val indicatorColor: String,
) {
fun toDomain(): Armament = Armament(
name = this.name,
details = this.details ?: "",
indicatorColor = this.indicatorColor,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

indicatorColor 누락/null 시 역직렬화가 실패할 수 있습니다.

indicatorColor가 non-null이고 기본값도 없어, 서버 응답에서 indicator_color가 누락되거나 null이면 kotlinx.serialization이 예외를 던져 목록 조회 전체가 실패합니다. GundamDto의 다른 필드(및 details)처럼 nullable + fallback로 방어하는 것이 일관적입니다.

🛡️ 제안 수정
     `@SerialName`("indicator_color")
-    val indicatorColor: String,
+    val indicatorColor: String? = null,
 ) {
     fun toDomain(): Armament = Armament(
         name = this.name,
         details = this.details ?: "",
-        indicatorColor = this.indicatorColor,
+        indicatorColor = this.indicatorColor ?: "",
     )
 }
📝 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
@SerialName("indicator_color")
val indicatorColor: String,
) {
fun toDomain(): Armament = Armament(
name = this.name,
details = this.details ?: "",
indicatorColor = this.indicatorColor,
`@SerialName`("indicator_color")
val indicatorColor: String? = null,
) {
fun toDomain(): Armament = Armament(
name = this.name,
details = this.details ?: "",
indicatorColor = this.indicatorColor ?: "",
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/main/java/com/example/gundamdexapp/data/network/dto/ArmamentDto.kt`
around lines 13 - 19, `ArmamentDto`의 `indicatorColor`가 non-null이라
`indicator_color`가 누락되거나 null일 때 `toDomain()` 전에 역직렬화가 실패합니다. `GundamDto`와
`details` 처리처럼 `indicatorColor`를 nullable로 바꾸고 `toDomain()`에서 기본값으로 fallback하도록
수정해, `ArmamentDto` 및 `ArmamentDto.toDomain()` 경로가 서버 응답 결측에 안전하도록 만드세요.

Comment on lines +25 to +35
override suspend fun getGundamDetail(id: String): Result<List<GundamInfo>> = try {
val response = api.getGundamDetail(idFilter = "eq.$id")

if (response.isSuccessful) {
val gundamDto = response.body() ?: emptyList()

val domainList = gundamDto.map { it.toDomain() }
Result.success(domainList)
} else {
Result.failure(Exception("서버 통신 실패 : ${response.code()}"))
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# 상세 ViewModel에서 getGundamDetail 결과 처리 방식 확인
rg -nP 'getGundamDetail|\.first\(|\[0\]|getOrNull' --type=kotlin -C3 app/src/main/java/com/example/gundamdexapp/feature/detail

Repository: noeyhoj/Gundamdex

Length of output: 1572


빈 결과 처리 로직 상세 설명

getGundamDetail 구현은 빈 리스트에 대해 firstOrNull() 을 사용하므로 NoSuchElementException 은 발생하지 않습니다. 다만, 빈 데이터를 null 로 전달받아 setUiState 내부에서 IllegalArgumentException 를 던지는 방식이 적용되어 있습니다.

상세 조회 API 가 존재하지 않는 경우 빈 리스트를 성공 응답으로 반환할 때, 이를 IllegalArgumentException 로 처리하는 것이 의도한 동작인지 확인해 주세요. UI 레이어에서 throw 를 사용하여 제어 흐름을 처리하는 것보다 정상적인 UiState 상태 전환 (예: 에러 상태 또는 빈 콘텐츠) 으로 처리하는 것이 클린 아키텍처 관점에서 권장됩니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@app/src/main/java/com/example/gundamdexapp/data/network/repositoryImpl/GundamRepositoryImpl.kt`
around lines 25 - 35, `GundamRepositoryImpl.getGundamDetail` is returning an
empty list as a successful result, but the current flow later turns that into
`null` and triggers `IllegalArgumentException` in `setUiState`. Update the
repository/UI contract so empty detail responses are handled explicitly as a
normal state (for example, a failure result or a dedicated empty-content UI
state) instead of relying on `throw` in the UI layer. Check the
`getGundamDetail` repository method and the `setUiState` logic that consumes its
result, and make the state transition consistent with the intended behavior.

Comment on lines +14 to +20
private val loggingInterceptor = HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
}

private val okHttpClient = OkHttpClient.Builder()
.addInterceptor(loggingInterceptor)
.build()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major

🧩 Analysis chain

🌐 Web query:

Does OkHttp HttpLoggingInterceptor Level.BODY log request headers like Authorization?

💡 Result:

Yes, the OkHttp HttpLoggingInterceptor at Level.BODY will log request headers, including sensitive ones like Authorization, by default [1][2][3]. Because the BODY and HEADERS levels log all request and response headers, they pose a risk of leaking sensitive information such as authentication tokens, cookies, or other private data [2][4][5]. OkHttp explicitly warns that these levels should only be used in controlled or non-production environments to avoid accidental exposure [2][4]. To prevent sensitive headers from being logged, you must explicitly redact them using the redactHeader method [2][4][5]: logging.redactHeader("Authorization"); logging.redactHeader("Cookie"); Once redacted, the interceptor will replace the header's value in the logs with a placeholder (typically "██") rather than printing the actual sensitive data [2][5].

Citations:


릴리스 빌드에서 인증 헤더가 Logcat에 노출될 위험이 있습니다.

HttpLoggingInterceptor.Level.BODY는 요청 및 응답 헤더 전체를 기록합니다. 코드에서 전송하는 apikeyAuthorization 헤더가 로그에 그대로 노출되며, 이는 보안 위험입니다.

해당 로깅 레벨을 디버그 빌드에서만 적용하고 릴리스 빌드에서는 logs 를 끄도록 수정해야 합니다.

🔒 제안 수정
+import com.example.gundamdexapp.BuildConfig
 import okhttp3.logging.HttpLoggingInterceptor
 import okhttp3.OkHttpClient

 object SupabaseNetwork {
     private val loggingInterceptor = HttpLoggingInterceptor().apply {
-        level = HttpLoggingInterceptor.Level.BODY
+        level = if (BuildConfig.DEBUG) {
+            HttpLoggingInterceptor.Level.BODY
+        } else {
+            HttpLoggingInterceptor.Level.NONE
+        }
+        
+        // 디버그 모드에서도 민감 헤더는 암호화하여 노출 방지 (선택적)
+        redactHeader("Authorization")
+        redactHeader("apikey")
     }

     private val okHttpClient = OkHttpClient.Builder()
         .addInterceptor(loggingInterceptor)
📝 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
private val loggingInterceptor = HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
}
private val okHttpClient = OkHttpClient.Builder()
.addInterceptor(loggingInterceptor)
.build()
import com.example.gundamdexapp.BuildConfig
import okhttp3.logging.HttpLoggingInterceptor
import okhttp3.OkHttpClient
object SupabaseNetwork {
private val loggingInterceptor = HttpLoggingInterceptor().apply {
level = if (BuildConfig.DEBUG) {
HttpLoggingInterceptor.Level.BODY
} else {
HttpLoggingInterceptor.Level.NONE
}
// 디버그 모드에서도 민감 헤더는 암호화하여 노출 방지 (선택적)
redactHeader("Authorization")
redactHeader("apikey")
}
private val okHttpClient = OkHttpClient.Builder()
.addInterceptor(loggingInterceptor)
.build()
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/main/java/com/example/gundamdexapp/data/network/SupabaseNetwork.kt`
around lines 14 - 20, The current HttpLoggingInterceptor setup in
SupabaseNetwork is logging full headers at BODY level, which can expose apikey
and Authorization values in release builds. Update the logging configuration in
the loggingInterceptor/okHttpClient setup so BODY logging is enabled only for
debug builds and disabled or removed for release builds, using the build-type
check at the point where the OkHttpClient.Builder is configured.

Comment on lines +29 to +34
init {
viewModelScope.launch {
val gundamInfo = getGundamDetail(id)
setUiState(gundamInfo)
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

상세 정보가 없을 때 예외를 던져 앱이 크래시됩니다.

init에서 viewModelScope.launchgetGundamDetail을 호출하고, 결과가 null이면 setUiStateIllegalArgumentException을 던집니다. 이 예외는 코루틴에서 처리되지 않아 앱이 종료됩니다. 실제 서버에서 해당 id의 데이터가 없거나 호출이 실패(getOrNull()null 반환)하는 경우는 정상적인 시나리오이므로, 예외 대신 GundamdexDetailUiState에 에러/빈 상태를 두어 UI에서 처리하도록 권장합니다.

Also applies to: 79-79

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@app/src/main/java/com/example/gundamdexapp/feature/detail/GundamdexDetailViewModel.kt`
around lines 29 - 34, The GundamdexDetailViewModel init flow currently lets a
null detail result from getGundamDetail(id) reach setUiState, which throws and
can crash the app. Update GundamdexDetailViewModel so setUiState and the init
launch path handle missing data gracefully by mapping null or failed fetches
into GundamdexDetailUiState error/empty state instead of throwing
IllegalArgumentException. Keep the behavior localized around getGundamDetail,
viewModelScope.launch, and setUiState so the UI can render a fallback state when
the detail is unavailable.

Comment on lines 10 to 15
fun dataToIndicatorColor(value: String): IndicatorColor = when (value) {
"red" -> RED
"blue" -> BLUE
"gray" -> GRAY
"grey" -> GREY
else -> throw IllegalArgumentException("잘못된 indicator color 입력 값을 전달받았습니다.")
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

알 수 없는 서버 값에 대해 예외를 던져 크래시될 수 있습니다.

dataToIndicatorColor"red"/"blue"/"grey" 외의 값에 대해 IllegalArgumentException을 던집니다. 이번 PR로 실제 서버 응답을 매핑하게 되는데(ArmamentDto.indicatorColor는 자유 형식 String), 서버가 "gray"(미국식 표기)나 대소문자가 다른 값, 신규 색상을 보내면 GundamdexDetailViewModel의 매핑 시점에서 예외가 전파되어 앱이 종료됩니다.

대소문자 정규화 및 기본값(fallback) 처리를 권장합니다.

🛡️ 예시 방향
-        fun dataToIndicatorColor(value: String): IndicatorColor = when (value) {
-            "red" -> RED
-            "blue" -> BLUE
-            "grey" -> GREY
-            else -> throw IllegalArgumentException("잘못된 indicator color 입력 값을 전달받았습니다.")
-        }
+        fun dataToIndicatorColor(value: String): IndicatorColor = when (value.trim().lowercase()) {
+            "red" -> RED
+            "blue" -> BLUE
+            "grey", "gray" -> GREY
+            else -> GREY // 알 수 없는 값은 기본 색상으로 처리
+        }
📝 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 dataToIndicatorColor(value: String): IndicatorColor = when (value) {
"red" -> RED
"blue" -> BLUE
"gray" -> GRAY
"grey" -> GREY
else -> throw IllegalArgumentException("잘못된 indicator color 입력 값을 전달받았습니다.")
}
fun dataToIndicatorColor(value: String): IndicatorColor = when (value.trim().lowercase()) {
"red" -> RED
"blue" -> BLUE
"grey", "gray" -> GREY
else -> GREY // 알 수 없는 값은 기본 색상으로 처리
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@app/src/main/java/com/example/gundamdexapp/feature/detail/uimodel/IndicatorColor.kt`
around lines 10 - 15, `IndicatorColor.dataToIndicatorColor` is throwing on any
server value outside the current hardcoded set, which can crash
`GundamdexDetailViewModel` during DTO-to-UI mapping. Update
`dataToIndicatorColor` to normalize the input (for example, case-insensitive
handling and accepting `"gray"` as well as `"grey"`), and replace the exception
path with a safe fallback color in `IndicatorColor` for unknown or new server
values.

Comment on lines +26 to +43
fun getGundamList() {
viewModelScope.launch {
_uiState.update {
it.copy(
gundamInfoList = gundamRepository.getGundamList()
.getOrThrow().map { gundamInfo ->
GundamdexHomeUiModel(
id = gundamInfo.id,
modelNumber = gundamInfo.modelNumber,
name = gundamInfo.name,
series = gundamInfo.series,
imageUrl = gundamInfo.imageUrl,
)
},
)
}
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

서버 호출 실패에 대한 예외 처리가 없어 앱이 크래시됩니다.

getGundamList()viewModelScope.launch 내부에서 Result.getOrThrow()로 풀고 있습니다. 이번 PR로 실제 Supabase 서버와 통신하게 되므로 네트워크 오류·타임아웃·역직렬화 실패가 흔하게 발생할 수 있는데, 이때 예외가 코루틴에서 처리되지 않고 전파되어 앱이 종료됩니다.

ResultonSuccess/onFailure로 분기하거나, GundamdexHomeUiState에 로딩/에러 상태를 추가해 사용자에게 노출하는 방식을 권장합니다.

🛡️ 예시 방향
     fun getGundamList() {
         viewModelScope.launch {
-            _uiState.update {
-                it.copy(
-                    gundamInfoList = gundamRepository.getGundamList()
-                        .getOrThrow().map { gundamInfo ->
-                            GundamdexHomeUiModel(
-                                id = gundamInfo.id,
-                                modelNumber = gundamInfo.modelNumber,
-                                name = gundamInfo.name,
-                                series = gundamInfo.series,
-                                imageUrl = gundamInfo.imageUrl,
-                            )
-                        },
-                )
-            }
+            gundamRepository.getGundamList()
+                .onSuccess { list ->
+                    _uiState.update { state ->
+                        state.copy(
+                            gundamInfoList = list.map { gundamInfo ->
+                                GundamdexHomeUiModel(
+                                    id = gundamInfo.id,
+                                    modelNumber = gundamInfo.modelNumber,
+                                    name = gundamInfo.name,
+                                    series = gundamInfo.series,
+                                    imageUrl = gundamInfo.imageUrl,
+                                )
+                            },
+                        )
+                    }
+                }
+                .onFailure { /* 에러 상태로 갱신 */ }
         }
     }
📝 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 getGundamList() {
viewModelScope.launch {
_uiState.update {
it.copy(
gundamInfoList = gundamRepository.getGundamList()
.getOrThrow().map { gundamInfo ->
GundamdexHomeUiModel(
id = gundamInfo.id,
modelNumber = gundamInfo.modelNumber,
name = gundamInfo.name,
series = gundamInfo.series,
imageUrl = gundamInfo.imageUrl,
)
},
)
}
}
}
fun getGundamList() {
viewModelScope.launch {
gundamRepository.getGundamList()
.onSuccess { list ->
_uiState.update { state ->
state.copy(
gundamInfoList = list.map { gundamInfo ->
GundamdexHomeUiModel(
id = gundamInfo.id,
modelNumber = gundamInfo.modelNumber,
name = gundamInfo.name,
series = gundamInfo.series,
imageUrl = gundamInfo.imageUrl,
)
},
)
}
}
.onFailure { /* 에러 상태로 갱신 */ }
}
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@app/src/main/java/com/example/gundamdexapp/feature/home/GundamdexHomeViewModel.kt`
around lines 26 - 43, `GundamdexHomeViewModel.getGundamList()` currently unwraps
the repository `Result` with `getOrThrow()` inside `viewModelScope.launch`, so
any Supabase/network failure will crash the coroutine and app. Update the flow
to handle failures explicitly in `getGundamList()` by branching with
`onSuccess`/`onFailure` (or equivalent try/catch), and use
`GundamdexHomeUiState`/`GundamdexHomeUiModel` to represent loading and error
states so the UI can react safely instead of propagating the exception.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEAT] 서버와 연결 구현

1 participant