feat: 온보딩 프로필 이미지 등록 및 UX 개선#207
Hidden character warning
Conversation
- S3 업로드용 OkHttpClient 분리 및 qualifier 추가 - StorageRepository에 presigned URL 발급 및 S3 업로드 로직 추가 - SignupRequestModel/Dto에 profileImageUrl 필드 추가 - 온보딩 프로필 화면에서 이미지 선택 상태를 ViewModel로 끌어올림 - /contents/search 요청은 Authorization 헤더 제외 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- 온보딩 순서 변경: 약관 동의 → 프로필 설정 순으로 변경 - 닉네임 자음/모음 단독 입력 시 토스트 없이 확인/다음 버튼 비활성화 - 닉네임 포맷 검증 정규식에 한글 자모(자음·모음) 중간 입력 허용 - 프로필 화면 키보드 위로 버튼 올라오도록 imePadding 적용 - 약관 목록 스크롤 추가 - SearchApi genre 파라미터를 List<String>으로 변경, mediaType/cursor 타입 업데이트 - SearchContentsResponseDto Meta 필드 추가 (page, size, totalElements, totalPages) - StorageApi presigned URL 응답 BaseResponse 래핑 제거 - S3 OkHttpClient에 로깅 인터셉터 추가 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Warning Review limit reached
More reviews will be available in 46 minutes and 26 seconds. Learn how PR review limits work. Your organization has run out of usage credits. Purchase more in the billing tab. ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (4)
📝 WalkthroughWalkthrough온보딩 프로필 이미지 업로드 기능을 S3와 연동하고, 닉네임 검증 로직을 강화하며, 자동 로그인 상태 관리를 명시적인 상태 머신으로 재구성합니다. 검색 API 계약도 다중 장르 필터링을 지원하도록 확장합니다. Changes온보딩 프로필 업로드 플로우
검색 기능 업데이트 및 자동 로그인 상태
지원 기능 및 API 업데이트
Sequence Diagram(s)sequenceDiagram
participant User as 사용자
participant OnboardingScreen as 온보딩 화면
participant ViewModel as OnboardingViewModel
participant Storage as StorageRepository
participant S3 as S3 저장소
participant API as SignupApi
User->>OnboardingScreen: 프로필 이미지 선택
OnboardingScreen->>ViewModel: updateProfileImage(uri)
ViewModel->>ViewModel: uploadProfileImageIfNeeded()
ViewModel->>Storage: getPresignedUrl(pathType, extension)
Storage->>API: presigned URL 요청
API-->>Storage: presignedUrl 응답
Storage->>S3: PUT 요청 (imageBytes)
S3-->>Storage: 업로드 완료
Storage-->>ViewModel: 업로드 URL 반환
User->>OnboardingScreen: 닉네임 입력 및 확인
OnboardingScreen->>ViewModel: 닉네임 중복 확인
ViewModel->>API: signup(profileImageUrl 포함)
API-->>ViewModel: 회원가입 완료
sequenceDiagram
participant App as 앱 시작
participant SplashViewModel
participant PreferencesManager as 저장소
participant SplashScreen as 스플래시 화면
App->>SplashViewModel: checkAutoLogin()
SplashViewModel->>PreferencesManager: ACCESS_TOKEN 조회
PreferencesManager-->>SplashViewModel: 토큰 값
alt 토큰 존재
SplashViewModel->>SplashViewModel: state = NavigateToHome
else 토큰 없음
SplashViewModel->>SplashViewModel: state = NavigateToLogin
end
SplashScreen->>SplashScreen: 애니메이션 시작
SplashScreen->>SplashScreen: 애니메이션 완료 (isAnimationFinished = true)
SplashScreen->>SplashScreen: isAnimationFinished && autoLoginState 조건 확인
SplashScreen->>App: 홈 또는 로그인 화면 네비게이션
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (3)
app/src/main/java/com/flint/presentation/onboarding/navigation/OnboardingNavigation.kt (1)
61-61: ⚡ Quick win오해를 부르는 파라미터 연결: 약관 다음 단계가 프로필인데 이름은
navigateToOnboardingContent입니다.새 온보딩 순서(약관 → 프로필 → 콘텐츠)에 맞춰 동작 자체는 올바르나,
OnboardingTermsRoute의 콜백 파라미터 이름이 실제 목적지(프로필)와 어긋나 유지보수 시 혼동을 유발합니다.OnboardingTermsScreen.kt의 선언부와 함께 이름을 정정하는 것을 권장합니다.♻️ 제안: 파라미터 이름 정정
- navigateToOnboardingContent = navController::navigateToOnboardingProfile, + navigateToOnboardingProfile = navController::navigateToOnboardingProfile,
OnboardingTermsRoute/OnboardingTermsScreen.kt의 파라미터 선언과 호출부도 함께navigateToOnboardingProfile(또는onNext)로 변경하세요.🤖 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/flint/presentation/onboarding/navigation/OnboardingNavigation.kt` at line 61, The parameter name navigateToOnboardingContent is misleading because the Terms screen actually navigates to the profile; update the parameter name in OnboardingTermsRoute and OnboardingTermsScreen to navigateToOnboardingProfile (or a neutral onNext) and adjust all call sites (e.g., where you pass navController::navigateToOnboardingProfile) to use the new name so declaration and invocation match (refer to symbols: OnboardingTermsRoute, OnboardingTermsScreen, navigateToOnboardingContent, navigateToOnboardingProfile).app/src/main/java/com/flint/presentation/onboarding/OnboardingTermsScreen.kt (1)
165-193: ⚡ Quick win스크롤 Column에
weight(1f)추가를 권장합니다.Loading/Failure 분기는
.weight(1f)로 남은 공간을 채우는 반면 이 스크롤 분기에는 높이 제약이 없어, 약관이 길 때 스크롤 영역 경계가 의도와 다르게 잡히거나 하단 버튼과의 레이아웃이 불안정해질 수 있습니다. 다른 분기와 동일하게 제약을 맞추는 것이 안전합니다.♻️ 제안
Column( modifier = Modifier + .weight(1f) .verticalScroll(rememberScrollState()) .padding(horizontal = 16.dp), ) {🤖 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/flint/presentation/onboarding/OnboardingTermsScreen.kt` around lines 165 - 193, The scrollable Column in the else branch of OnboardingTermsScreen lacks a height constraint and should be given Modifier.weight(1f) so it matches the Loading/Failure branches; update the Column's modifier (the one using .verticalScroll(rememberScrollState()).padding(...)) to include .weight(1f) (e.g., Modifier.weight(1f).verticalScroll(...).padding(...)) so the scroll area is constrained and layout with the bottom buttons remains stable; keep the existing verticalScroll and padding and adjust checkedStates/expandedStates handling unchanged.app/src/main/java/com/flint/data/di/interceptor/TokenInterceptor.kt (1)
26-29: 💤 Low value검색 경로 매칭을 정확 일치로 좁히는 편이 안전합니다.
endsWith("/contents/search")는/api/v2/contents/search같은 다른 버전 경로뿐 아니라 의도치 않게 끝부분만 일치하는 경로에도 모두 매칭됩니다. 인증 헤더 누락 대상을 명확히 하려면 정확한 경로(예: 정규화된 전체 path 비교 또는 상수화)로 좁히는 것을 고려해 보세요.🤖 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/flint/data/di/interceptor/TokenInterceptor.kt` around lines 26 - 29, The current isSearchRequest check in TokenInterceptor.kt uses originalRequest.url.encodedPath.endsWith("/contents/search"), which can falsely match other paths; change it to an exact path comparison or constant-based match: compute the normalized full path from originalRequest.url (or use a predefined constant like CONTENTS_SEARCH_PATH) and compare equality (e.g., path == "/api/v2/contents/search") when setting isSearchRequest, then keep the existing logic that skips adding the Authorization header via requestBuilder.header("Authorization", "Bearer $accessToken") only when accessToken is non-empty and isSearchRequest is true.
🤖 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/flint/data/dto/search/SearchContentsResponseDto.kt`:
- Around line 39-40: The property totalElements in SearchContentsResponseDto
(inside the Meta/data class) is declared as String? while page/size/totalPages
are Int?; change totalElements to Int? to match types and avoid Json decoding
errors, and if the backend may emit numbers as strings ensure the serializer
configuration or a custom `@Serializable` converter is used for the totalElements
property to accept numeric strings (update the Meta class property named
totalElements accordingly).
In `@app/src/main/java/com/flint/presentation/onboarding/OnboardingViewModel.kt`:
- Around line 229-237: The signup flow silently proceeds when
uploadProfileImageIfNeeded() returns null after a user selected an image; update
OnboardingViewModel so that after calling uploadProfileImageIfNeeded() you check
if the user had a selected image (e.g., _uiState.value.profileImageUri or
equivalent) and the returned profileImageUrl is null, then emit a UI event
(toast/snackbar) or trigger a retry/confirmation flow instead of building
SignupRequestModel and continuing; modify the code around
uploadProfileImageIfNeeded() and the signup creation (where SignupRequestModel
is constructed) to either abort/signup-block on failure or present a retry
dialog/notification to the user so the missing upload isn’t silent.
- Around line 251-278: The profile image MIME/type lookup and byte reading in
uploadProfileImageIfNeeded() are performed on the main thread; wrap the blocking
calls (context.contentResolver.getType(uri) and
context.contentResolver.openInputStream(uri)?.use { it.readBytes() }) inside
withContext(Dispatchers.IO) so the I/O runs on Dispatchers.IO, and add the
necessary imports (kotlinx.coroutines.Dispatchers,
kotlinx.coroutines.withContext); preserve the existing error handling and return
behavior while moving only the blocking I/O into the IO context.
In `@app/src/main/java/com/flint/presentation/splash/SplashViewModel.kt`:
- Around line 33-40: Handle DataStore read failures in checkAutoLogin by
wrapping the preferencesManager.getString(ACCESS_TOKEN).first() call in a
try/catch inside the existing viewModelScope.launch; on any exception catch
(e.g., IOException) log/report it and set _autoLoginState.value =
AutoLoginState.NavigateToLogin so the splash doesn't hang, preserving the
existing NavigateToHome behavior when a non-empty token is read.
---
Nitpick comments:
In `@app/src/main/java/com/flint/data/di/interceptor/TokenInterceptor.kt`:
- Around line 26-29: The current isSearchRequest check in TokenInterceptor.kt
uses originalRequest.url.encodedPath.endsWith("/contents/search"), which can
falsely match other paths; change it to an exact path comparison or
constant-based match: compute the normalized full path from originalRequest.url
(or use a predefined constant like CONTENTS_SEARCH_PATH) and compare equality
(e.g., path == "/api/v2/contents/search") when setting isSearchRequest, then
keep the existing logic that skips adding the Authorization header via
requestBuilder.header("Authorization", "Bearer $accessToken") only when
accessToken is non-empty and isSearchRequest is true.
In
`@app/src/main/java/com/flint/presentation/onboarding/navigation/OnboardingNavigation.kt`:
- Line 61: The parameter name navigateToOnboardingContent is misleading because
the Terms screen actually navigates to the profile; update the parameter name in
OnboardingTermsRoute and OnboardingTermsScreen to navigateToOnboardingProfile
(or a neutral onNext) and adjust all call sites (e.g., where you pass
navController::navigateToOnboardingProfile) to use the new name so declaration
and invocation match (refer to symbols: OnboardingTermsRoute,
OnboardingTermsScreen, navigateToOnboardingContent,
navigateToOnboardingProfile).
In
`@app/src/main/java/com/flint/presentation/onboarding/OnboardingTermsScreen.kt`:
- Around line 165-193: The scrollable Column in the else branch of
OnboardingTermsScreen lacks a height constraint and should be given
Modifier.weight(1f) so it matches the Loading/Failure branches; update the
Column's modifier (the one using
.verticalScroll(rememberScrollState()).padding(...)) to include .weight(1f)
(e.g., Modifier.weight(1f).verticalScroll(...).padding(...)) so the scroll area
is constrained and layout with the bottom buttons remains stable; keep the
existing verticalScroll and padding and adjust checkedStates/expandedStates
handling unchanged.
🪄 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: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 9eb55517-fc78-4835-9691-20f049f1a35d
📒 Files selected for processing (21)
app/src/main/java/com/flint/core/designsystem/component/toast/ShowToast.ktapp/src/main/java/com/flint/core/navigation/Route.ktapp/src/main/java/com/flint/data/api/AuthApi.ktapp/src/main/java/com/flint/data/api/SearchApi.ktapp/src/main/java/com/flint/data/api/StorageApi.ktapp/src/main/java/com/flint/data/di/NetworkModule.ktapp/src/main/java/com/flint/data/di/interceptor/TokenInterceptor.ktapp/src/main/java/com/flint/data/di/qualifier/S3OkHttpClient.ktapp/src/main/java/com/flint/data/dto/auth/request/SignupRequestDto.ktapp/src/main/java/com/flint/data/dto/search/SearchContentsResponseDto.ktapp/src/main/java/com/flint/domain/mapper/auth/SignupMapper.ktapp/src/main/java/com/flint/domain/model/auth/SignupModel.ktapp/src/main/java/com/flint/domain/repository/SearchRepository.ktapp/src/main/java/com/flint/domain/repository/StorageRepository.ktapp/src/main/java/com/flint/presentation/onboarding/OnboardingProfileScreen.ktapp/src/main/java/com/flint/presentation/onboarding/OnboardingTermsScreen.ktapp/src/main/java/com/flint/presentation/onboarding/OnboardingUiState.ktapp/src/main/java/com/flint/presentation/onboarding/OnboardingViewModel.ktapp/src/main/java/com/flint/presentation/onboarding/navigation/OnboardingNavigation.ktapp/src/main/java/com/flint/presentation/splash/SplashScreen.ktapp/src/main/java/com/flint/presentation/splash/SplashViewModel.kt
| @SerialName("totalElements") | ||
| val totalElements: String? = null, |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
Does kotlinx.serialization Json decode a JSON number into a Kotlin String property or throw by default?
💡 Result:
By default, kotlinx.serialization’s Json decoder expects a JSON string token (quoted) for properties of Kotlin type String; if the input provides a JSON number instead, decoding fails (throws a SerializationException/JsonDecodingException), rather than silently converting the number to a String. Evidence: - The Json configuration option that would allow coercing certain wrong types is coerceInputValues, and it is false by default. The docs state it only coerces specific cases (e.g., JSON null into a non-nullable property, unknown enum members) and do not mention number-to-string coercion [1]. - Json decoding is token/type-driven: the core Decoder API exposes decodeString() as “Decodes a string value. Corresponding kind is PrimitiveKind.STRING”, i.e., the deserializer expects the underlying input element to be a string kind [2]. - The kotlinx.serialization issue discussions show that when a decoder calls decodeString() but the underlying JSON token is numeric, decodeString() throws an exception (example shows “Expected string literal with quotes” after decoding a numeric token) [3]. So the answer to your question is: it does not decode a JSON number into a Kotlin String property by default; it throws by default.
Citations:
- 1: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/coerce-input-values.html
- 2: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-decoder/decode-string.html
- 3: Unexpected SerializationException in custom serializer Kotlin/kotlinx.serialization#1041
totalElements 타입을 Int?로 통일하세요
Meta에서page/size/totalPages는Int?인데totalElements만String?입니다.- kotlinx.serialization
Json은 기본적으로String프로퍼티에 대해 JSON number를 문자열로 자동 변환하지 않으며, 타입이 맞지 않으면 디코딩 예외가 발생합니다(백엔드가 숫자로 내리는지/문자열로 내리는지와 현재Json설정을 확인 필요).
🐛 제안 수정
`@SerialName`("totalElements")
- val totalElements: String? = null,
+ val totalElements: Int? = null,📝 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.
| @SerialName("totalElements") | |
| val totalElements: String? = null, | |
| `@SerialName`("totalElements") | |
| val totalElements: Int? = null, |
🤖 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/flint/data/dto/search/SearchContentsResponseDto.kt`
around lines 39 - 40, The property totalElements in SearchContentsResponseDto
(inside the Meta/data class) is declared as String? while page/size/totalPages
are Int?; change totalElements to Int? to match types and avoid Json decoding
errors, and if the backend may emit numbers as strings ensure the serializer
configuration or a custom `@Serializable` converter is used for the totalElements
property to accept numeric strings (update the Meta class property named
totalElements accordingly).
There was a problem hiding this comment.
❌ 반영하지 않았습니다. Swagger 스펙에서 "totalElements": "123" 처럼 따옴표가 있는 문자열로 응답이 정의되어 있어 현재 String?이 올바른 타입입니다. 실제 서버 응답도 동일하게 문자열로 내려오는 것을 확인했습니다.
There was a problem hiding this comment.
@kimjw2003, 확인해 주셔서 감사합니다! Swagger 스펙과 실제 서버 응답이 모두 문자열로 내려온다면 String?이 올바른 타입이 맞습니다. 제 제안이 잘못되었습니다. 향후 리뷰에 반영해 두겠습니다.
(ᓀ‸ᓂ)
✏️ Learnings added
Learnt from: kimjw2003
Repo: imflint/Flint-Android PR: 207
File: app/src/main/java/com/flint/data/dto/search/SearchContentsResponseDto.kt:39-40
Timestamp: 2026-06-02T12:11:59.612Z
Learning: In `app/src/main/java/com/flint/data/dto/search/SearchContentsResponseDto.kt`, the `totalElements` field inside the `Meta` data class is intentionally declared as `String?` (not `Int?`) because the backend Swagger spec defines it as a quoted string (e.g., `"totalElements": "123"`) and the actual server response returns it as a string value, not a JSON number.
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: nahy-512
Repo: imflint/Flint-Android PR: 135
File: app/src/main/java/com/flint/domain/repository/AuthRepository.kt:1-22
Timestamp: 2026-01-19T00:48:44.889Z
Learning: Architect the Android app following Google's recommended Android App Architecture for this repo: place the data layer innermost, the domain layer can depend on the data layer, and avoid circular dependencies with other layers. For Kotlin files under app/src/main/java, ensure accompanying architecture aligns with this guidance (e.g., domain interfaces depending on data layer implementations or abstractions, not the other way around).
Learnt from: giovannijunseokim
Repo: imflint/Flint-Android PR: 153
File: app/src/main/java/com/flint/presentation/collectiondetail/CollectionDetailViewModel.kt:98-105
Timestamp: 2026-01-21T08:37:42.767Z
Learning: Guideline: For APIs like toggleContentBookmark that do not return a bookmarkCount, perform optimistic updates by updating the isBookmarked state on the client and compute bookmarkCount locally if needed. Do not rely on the server for the count; ensure the server response only conveys the boolean bookmarked state and synchronize this state accordingly. This applies to Kotlin Android ViewModels and related UI state management across files that handle similar bookmark toggle endpoints.
| val profileImageUrl = uploadProfileImageIfNeeded() | ||
|
|
||
| val signupRequest = SignupRequestModel( | ||
| tempToken = tempToken, | ||
| nickname = _uiState.value.nickname, | ||
| favoriteContentIds = _contentUiState.value.selectedContents.map { it.id }, | ||
| agreedTermsIds = _termsUiState.value.agreedTermsIds, | ||
| profileImageUrl = profileImageUrl, | ||
| ) |
There was a problem hiding this comment.
이미지 업로드 실패 시 무음 처리로 인한 UX 혼동 가능성.
사용자가 프로필 이미지를 선택했더라도 presigned URL 발급이나 S3 업로드가 실패하면 uploadProfileImageIfNeeded()가 null을 반환하고, 회원가입은 이미지 없이 그대로 진행됩니다. 프로필 이미지가 선택 항목이라면 의도된 동작일 수 있으나, 사용자가 분명히 이미지를 골랐는데 결과물에 반영되지 않으면 혼란을 줄 수 있습니다. 업로드 실패 시 토스트 등으로 안내하거나 재시도 흐름을 두는 것을 고려해 주세요.
🤖 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/flint/presentation/onboarding/OnboardingViewModel.kt`
around lines 229 - 237, The signup flow silently proceeds when
uploadProfileImageIfNeeded() returns null after a user selected an image; update
OnboardingViewModel so that after calling uploadProfileImageIfNeeded() you check
if the user had a selected image (e.g., _uiState.value.profileImageUri or
equivalent) and the returned profileImageUrl is null, then emit a UI event
(toast/snackbar) or trigger a retry/confirmation flow instead of building
SignupRequestModel and continuing; modify the code around
uploadProfileImageIfNeeded() and the signup creation (where SignupRequestModel
is constructed) to either abort/signup-block on failure or present a retry
dialog/notification to the user so the missing upload isn’t silent.
There was a problem hiding this comment.
❌ 반영하지 않았습니다. 프로필 이미지는 선택 항목(optional)이므로 업로드 실패 시 이미지 없이 회원가입을 진행하는 것은 의도된 동작입니다. 업로드 실패가 회원가입 자체를 막아서는 안 된다고 판단했습니다.
There was a problem hiding this comment.
@kimjw2003, 이해했습니다. 프로필 이미지가 선택 항목이라면 업로드 실패 시 이미지 없이 회원가입을 진행하는 현재 동작은 완전히 합리적입니다. 설명해 주셔서 감사합니다.
🐇 (´• ω •`)
✏️ Learnings added
Learnt from: kimjw2003
Repo: imflint/Flint-Android PR: 207
File: app/src/main/java/com/flint/presentation/onboarding/OnboardingViewModel.kt:231-239
Timestamp: 2026-06-02T12:12:02.548Z
Learning: In the Flint Android app (imflint/Flint-Android), the profile image in the onboarding signup flow (`OnboardingViewModel.signup()` in `app/src/main/java/com/flint/presentation/onboarding/OnboardingViewModel.kt`) is optional. When `uploadProfileImageIfNeeded()` returns null due to upload failure (presigned URL error or S3 upload error), the signup proceeds without a profile image — this is intentional and by design. Do not flag this as a UX issue.
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: nahy-512
Repo: imflint/Flint-Android PR: 135
File: app/src/main/java/com/flint/domain/repository/AuthRepository.kt:1-22
Timestamp: 2026-01-19T00:48:44.889Z
Learning: Architect the Android app following Google's recommended Android App Architecture for this repo: place the data layer innermost, the domain layer can depend on the data layer, and avoid circular dependencies with other layers. For Kotlin files under app/src/main/java, ensure accompanying architecture aligns with this guidance (e.g., domain interfaces depending on data layer implementations or abstractions, not the other way around).
Learnt from: giovannijunseokim
Repo: imflint/Flint-Android PR: 153
File: app/src/main/java/com/flint/presentation/collectiondetail/CollectionDetailViewModel.kt:98-105
Timestamp: 2026-01-21T08:37:42.767Z
Learning: Guideline: For APIs like toggleContentBookmark that do not return a bookmarkCount, perform optimistic updates by updating the isBookmarked state on the client and compute bookmarkCount locally if needed. Do not rely on the server for the count; ensure the server response only conveys the boolean bookmarked state and synchronize this state accordingly. This applies to Kotlin Android ViewModels and related UI state management across files that handle similar bookmark toggle endpoints.
- ContentResolver 블로킹 I/O를 Dispatchers.IO로 이동하여 ANR 방지 - checkAutoLogin() DataStore 읽기 실패 시 로그인 화면으로 안전하게 이동 - OnboardingTermsRoute 파라미터명 navigateToOnboardingProfile로 명확화 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Summary
imePadding적용SearchApigenre →List<String>, mediaType 추가, cursor 타입String?으로 변경SearchContentsResponseDtoMeta 필드 추가 (page, size, totalElements, totalPages)StorageApipresigned URL 응답에서BaseResponse래핑 제거OkHttpClient에 로깅 인터셉터 추가Test plan
profileImageUrl포함 확인🤖 Generated with Claude Code
Summary by CodeRabbit
릴리스 노트
새로운 기능
개선사항