Feat/#5 mypage screen구현#41
Hidden character warning
Conversation
Walkthrough스터디 지원자 조회 및 수락/거절 기능을 전체 계층(서비스, 데이터소스, 리포지토리, 도메인, 매퍼, ViewModel, Compose UI, 네비게이션)에 걸쳐 추가했습니다. 새로운 API 엔드포인트 및 화면/네비게이션이 포함됩니다. (47자) Changes
Sequence Diagram(s)sequenceDiagram
actor User as 사용자
participant UI as RecruitingStudyRequestScreen
participant VM as RecruitingStudyApplicationViewModel
participant Repo as StudyRepository
participant DS as StudyDataSource
participant API as StudyService (API)
User->>UI: 화면 진입 (studyId)
UI->>VM: load(studyId)
VM->>Repo: getStudyApplications(studyId)
Repo->>DS: getStudyApplications(studyId)
DS->>API: GET /api/studies/{studyId}/applications
API-->>DS: BaseResponse<StudyApplicationResponseDto>
DS-->>Repo: DTO 반환
Repo->>Repo: DTO → 도메인 매핑
Repo-->>VM: Result<StudyApplicationResultList>
VM-->>UI: uiState 업데이트 (Success)
User->>UI: 수락/거절 버튼 클릭 (applicationId)
UI->>VM: accept(applicationId) / reject(applicationId)
VM->>Repo: entryAcceptance(applicationId, decision)
Repo->>DS: entryAcceptance(applicationId, decision)
DS->>API: POST /api/studies/applications/{applicationId}?decision=...
API-->>DS: NullResultResponse
DS-->>Repo: 성공 응답
VM->>Repo: getStudyApplications(currentStudyId) (재로딩)
Repo-->>VM: 갱신된 목록
VM-->>UI: uiState 갱신
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested labels
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
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: 6
🤖 Fix all issues with AI agents
In
`@feature/mypage/src/main/java/com/umcspot/spot/mypage/recruiting/application/navigation/RecruitingStudyApplicationNavigation.kt`:
- Around line 33-34: `@Serializable` is incorrectly applied to the string
constant STUDY_APPLICATION_ROUTE; remove the `@Serializable` annotation from the
const val declaration and also delete the unused import
`kotlinx.serialization.Serializable` (the annotation belongs on classes/data
classes, not string constants). Ensure only the const val
STUDY_APPLICATION_ROUTE = "studyApplication/{studyId}" remains and no leftover
import or annotation references exist.
- Around line 22-23: backStackEntry.arguments?.getLong("studyId") returns 0L
when the key is missing so the current defensive ?: return@composable won't run;
change the logic to first ensure arguments is non-null and contains the
"studyId" key before calling getLong. For example, inside the composable guard
on backStackEntry.arguments (or assign val args = backStackEntry.arguments ?:
return@composable) then check args.containsKey("studyId") ?: return@composable
and only then call val studyId = args.getLong("studyId"); update the code paths
referencing backStackEntry, studyId and the composable accordingly.
In
`@feature/mypage/src/main/java/com/umcspot/spot/mypage/recruiting/application/RecruitingStudyApplicationScreen.kt`:
- Around line 155-170: The AcceptDialog currently shows a "수락 완료" message but
triggers viewmodel.accept(selectedApplicationId!!) only when the "확인" button is
pressed, which is confusing; change the dialog to a confirmation flow by
updating AcceptDialog props (e.g., modalTitle -> "수락하시겠어요?", modalDes ->
confirmation text) and keep the API call in onClick to perform accept, then on
successful completion show a separate success feedback (either open a new
AcceptDialog/SuccessDialog or trigger a toast) — locate the AcceptDialog usage,
the showAcceptDialog boolean, viewmodel.accept(...) and selectedApplicationId to
implement this: modify the dialog copy to be a confirmation prompt, call
viewmodel.accept on confirm, and handle success by setting a new state (e.g.,
showAcceptSuccessDialog or showToast) to display the completed message after the
API returns.
- Line 24: Remove the unused import of androidx.compose.material3.TextButton in
RecruitingStudyApplicationScreen.kt since the file uses the custom TextButton
instead; delete the line importing androidx.compose.material3.TextButton so only
the project's custom TextButton import remains (ensure no references to
material3.TextButton exist in the RecruitingStudyApplicationScreen composable).
- Around line 146-152: The code currently force-unwraps selectedApplicationId
(selectedApplicationId!!) in the Reject dialog onClick handler which can cause
NPE; modify the handler to defensively check for null before calling
viewmodel.reject — e.g., use a safe-call/let pattern (selectedApplicationId?.let
{ viewmodel.reject(it) } ) or an early return so the dialog is dismissed only
after a valid id is present; apply the same change to the corresponding
AcceptDialog handler (the similar force-unwrapping at the AcceptDialog ~168
reference) to avoid the same NPE.
In
`@feature/mypage/src/main/java/com/umcspot/spot/mypage/recruiting/application/RecruitingStudyApplicationViewModel.kt`:
- Around line 31-46: Move the side-effect assignment of currentStudyId out of
the _uiState.update lambda so update stays a pure state transformation: assign
currentStudyId = studyId before calling _uiState.update, then call
_uiState.update { it.copy(applications = if (info.applies.isEmpty())
UiState.Empty else UiState.Success(info)) } inside onSuccess. In onFailure,
replace the lone Log.e call with a UI state update that sets applications =
UiState.Failure(e.message ?: "신청 목록 불러오기 실패") (use the same _uiState.update
pattern) so the user sees an error, and remove the commented-out weather-related
code; ensure you reference the existing symbols _uiState.update, currentStudyId,
applications, UiState.Success, UiState.Empty, UiState.Failure, and onFailure
while making these changes.
🧹 Nitpick comments (5)
feature/mypage/src/main/java/com/umcspot/spot/mypage/waiting/WaitingStudyScreen.kt (2)
146-173:indexOf대신itemsIndexed사용을 권장합니다.
studyList.indexOf(item)은 매 아이템마다 O(n) 선형 탐색을 수행하여 전체 O(n²) 복잡도가 됩니다. 페이지네이션으로 데이터가 누적될 경우 성능에 영향을 줄 수 있습니다.♻️ 제안된 수정
- items( - items = studyList, - key = { it.id } - ) { item -> + itemsIndexed( + items = studyList, + key = { _, item -> item.id } + ) { index, item -> Spacer(Modifier.height(screenHeightDp(5.dp))) Box( modifier = Modifier.fillMaxWidth(), contentAlignment = Alignment.TopStart ) { StudyListItem( item = item, modifier = Modifier.fillMaxWidth(), onClick = { onStudyClick(item.id) } ) } - if (studyList.indexOf(item) != studyList.lastIndex) { + if (index != studyList.lastIndex) { Spacer(Modifier.height(screenHeightDp(5.dp))) HorizontalDivider( modifier = Modifier .fillMaxWidth(), color = SpotTheme.colors.G300, thickness = 1.dp ) } }
120-131: 테마 색상 사용 불일치.Line 98에서는
SpotTheme.colors.white를 사용하지만, Line 124에서는Color.White를 직접 사용하고 있습니다. 테마 지원(다크 모드 등)을 위해 일관되게 테마 색상을 사용하는 것이 좋습니다.♻️ 제안된 수정
is UiState.Success -> { WaitingStudyScreenContent( modifier = Modifier .fillMaxSize() - .background(Color.White) + .background(SpotTheme.colors.white) .padding(top = contentPadding.calculateTopPadding(), bottom = contentPadding.calculateBottomPadding()) .padding(horizontal = screenWidthDp(17.dp)),data/study/src/main/java/com/umcspot/spot/study/repositoryimpl/StudyRepositoryImpl.kt (1)
211-217:entryAcceptance에 에러 로깅 및 응답 검증 누락다른 메서드들과 달리
entryAcceptance에는onFailure에러 로깅이 없고, 응답의 성공 여부 검증도 없습니다.NullResultResponse가isSuccess플래그를 포함한다면, 이를 검증하는 것이 좋습니다.♻️ 일관성 있는 에러 처리 제안
override suspend fun entryAcceptance( applicationId: Long, decision: String ): Result<Unit> = runCatching { - studyDataSource.entryAcceptance(applicationId, decision) - } + val response = studyDataSource.entryAcceptance(applicationId, decision) + if (!response.isSuccess) { + throw Exception(response.message ?: "신청 처리 실패") + } + }.onFailure { + Log.e("StudyRepository", "entryAcceptance failed", it) + }feature/mypage/src/main/java/com/umcspot/spot/mypage/recruiting/application/RecruitingStudyApplicationViewModel.kt (1)
50-72: 수락/거절 실패 시 사용자 피드백 없음
accept()와reject()함수에서onFailure시 로깅만 수행하고 사용자에게 에러를 알리지 않습니다. 네트워크 오류나 서버 에러 발생 시 사용자는 아무런 피드백 없이 대기하게 됩니다.에러 발생 시 토스트 메시지나 스낵바를 통해 사용자에게 알려주는 것을 권장합니다.
SingleEvent또는SharedFlow를 사용하여 일회성 이벤트를 처리할 수 있습니다.feature/mypage/src/main/java/com/umcspot/spot/mypage/recruiting/application/RecruitingStudyApplicationScreen.kt (1)
204-204:indexOf()호출로 인한 O(n²) 복잡도
items루프 내에서applicationList.indexOf(item)을 호출하면 매 아이템마다 리스트를 순회하여 비효율적입니다.itemsIndexed를 사용하면 인덱스를 직접 얻을 수 있습니다.♻️ 수정 제안
- items( - items = applicationList, - key = { it.applicantId } - ) { item -> + itemsIndexed( + items = applicationList, + key = { _, item -> item.applicantId } + ) { index, item -> Spacer(Modifier.height(screenHeightDp(5.dp))) ... - if (applicationList.indexOf(item) != applicationList.lastIndex) { + if (index != applicationList.lastIndex) {
items대신itemsIndexedimport 추가:import androidx.compose.foundation.lazy.itemsIndexed
| ) { backStackEntry -> | ||
| val studyId = backStackEntry.arguments?.getLong("studyId") ?: return@composable |
There was a problem hiding this comment.
getLong은 키가 없을 때 0L을 반환하므로 의도한 방어 로직이 동작하지 않습니다.
Bundle.getLong("studyId")는 키가 존재하지 않을 때 null이 아닌 0L을 반환합니다. 따라서 ?: return@composable은 실행되지 않고, 잘못된 studyId = 0L로 화면이 로드될 수 있습니다.
🐛 수정 제안
- val studyId = backStackEntry.arguments?.getLong("studyId") ?: return@composable
+ val studyId = backStackEntry.arguments?.getLong("studyId")
+ .takeIf { it != 0L } ?: return@composable또는 arguments가 null인지 먼저 확인:
- val studyId = backStackEntry.arguments?.getLong("studyId") ?: return@composable
+ val args = backStackEntry.arguments ?: return@composable
+ val studyId = args.getLong("studyId")🤖 Prompt for AI Agents
In
`@feature/mypage/src/main/java/com/umcspot/spot/mypage/recruiting/application/navigation/RecruitingStudyApplicationNavigation.kt`
around lines 22 - 23, backStackEntry.arguments?.getLong("studyId") returns 0L
when the key is missing so the current defensive ?: return@composable won't run;
change the logic to first ensure arguments is non-null and contains the
"studyId" key before calling getLong. For example, inside the composable guard
on backStackEntry.arguments (or assign val args = backStackEntry.arguments ?:
return@composable) then check args.containsKey("studyId") ?: return@composable
and only then call val studyId = args.getLong("studyId"); update the code paths
referencing backStackEntry, studyId and the composable accordingly.
| @Serializable | ||
| const val STUDY_APPLICATION_ROUTE = "studyApplication/{studyId}" |
There was a problem hiding this comment.
@Serializable 어노테이션이 const val에 잘못 적용되었습니다.
@Serializable은 클래스나 데이터 클래스에 사용되며, 문자열 상수에는 적용되지 않습니다. 이 어노테이션은 불필요하며 컴파일 경고를 발생시킬 수 있습니다.
♻️ 수정 제안
-@Serializable
const val STUDY_APPLICATION_ROUTE = "studyApplication/{studyId}"또한 10번 라인의 import kotlinx.serialization.Serializable도 제거하세요.
🤖 Prompt for AI Agents
In
`@feature/mypage/src/main/java/com/umcspot/spot/mypage/recruiting/application/navigation/RecruitingStudyApplicationNavigation.kt`
around lines 33 - 34, `@Serializable` is incorrectly applied to the string
constant STUDY_APPLICATION_ROUTE; remove the `@Serializable` annotation from the
const val declaration and also delete the unused import
`kotlinx.serialization.Serializable` (the annotation belongs on classes/data
classes, not string constants). Ensure only the const val
STUDY_APPLICATION_ROUTE = "studyApplication/{studyId}" remains and no leftover
import or annotation references exist.
...main/java/com/umcspot/spot/mypage/recruiting/application/RecruitingStudyApplicationScreen.kt
Outdated
Show resolved
Hide resolved
...main/java/com/umcspot/spot/mypage/recruiting/application/RecruitingStudyApplicationScreen.kt
Show resolved
Hide resolved
| AcceptDialog( | ||
| visible = showAcceptDialog, | ||
| painter = painterResource(R.drawable.group_accept), | ||
| painterTint = SpotTheme.colors.B500, | ||
| modalTitle = "수락 완료", | ||
| modalDes = "수락이 완료되었어요.", | ||
| okButtonText = "확인", | ||
| noButtonText = null, | ||
| onDismiss = { | ||
| showAcceptDialog = false | ||
| }, | ||
| onClick = { | ||
| showAcceptDialog = false | ||
| viewmodel.accept(selectedApplicationId!!) | ||
| } | ||
| ) |
There was a problem hiding this comment.
AcceptDialog의 UX 흐름이 혼란스럽습니다.
다이얼로그 제목이 "수락 완료"이고 설명이 "수락이 완료되었어요"인데, 실제 수락 API 호출은 "확인" 버튼 클릭 시 발생합니다. 사용자는 이미 완료된 것으로 오해할 수 있습니다.
수락 확인을 요청하는 다이얼로그("수락하시겠어요?")를 먼저 보여주고, API 성공 후 완료 메시지를 표시하거나, 즉시 수락 처리 후 결과를 토스트로 알리는 방식을 권장합니다.
🤖 Prompt for AI Agents
In
`@feature/mypage/src/main/java/com/umcspot/spot/mypage/recruiting/application/RecruitingStudyApplicationScreen.kt`
around lines 155 - 170, The AcceptDialog currently shows a "수락 완료" message but
triggers viewmodel.accept(selectedApplicationId!!) only when the "확인" button is
pressed, which is confusing; change the dialog to a confirmation flow by
updating AcceptDialog props (e.g., modalTitle -> "수락하시겠어요?", modalDes ->
confirmation text) and keep the API call in onClick to perform accept, then on
successful completion show a separate success feedback (either open a new
AcceptDialog/SuccessDialog or trigger a toast) — locate the AcceptDialog usage,
the showAcceptDialog boolean, viewmodel.accept(...) and selectedApplicationId to
implement this: modify the dialog copy to be a confirmation prompt, call
viewmodel.accept on confirm, and handle success by setting a new state (e.g.,
showAcceptSuccessDialog or showToast) to display the completed message after the
API returns.
| .onSuccess { info -> | ||
| _uiState.update { | ||
| currentStudyId = studyId | ||
| it.copy( | ||
| applications = if (info.applies.isEmpty()) { | ||
| UiState.Empty | ||
| } else { | ||
| UiState.Success(info) | ||
| } | ||
| ) | ||
| } | ||
| } | ||
| .onFailure { e -> | ||
| Log.e("RecruitingStudyApplicationViewModel", "loadStudyApplicationsError", e) | ||
| // _uiState.update { it.copy(weatherInfo = UiState.Failure(e.message ?: "날씨 불러오기 실패")) } | ||
| } |
There was a problem hiding this comment.
_uiState.update 블록 내부에서 side effect 발생 및 에러 처리 미흡
currentStudyId = studyId할당이update람다 내부에 있습니다.update는 순수 함수여야 하며, side effect는 외부에서 처리해야 합니다.onFailure에서 로깅만 하고 UI 상태를 업데이트하지 않아 사용자에게 에러 피드백이 없습니다.- 45번 라인의 주석 처리된 코드는 정리가 필요합니다.
♻️ 수정 제안
viewModelScope.launch {
+ currentStudyId = studyId
studyRepository.getStudyApplications(
studyId = studyId
)
.onSuccess { info ->
_uiState.update {
- currentStudyId = studyId
it.copy(
applications = if (info.applies.isEmpty()) {
UiState.Empty
} else {
UiState.Success(info)
}
)
}
}
.onFailure { e ->
Log.e("RecruitingStudyApplicationViewModel", "loadStudyApplicationsError", e)
-// _uiState.update { it.copy(weatherInfo = UiState.Failure(e.message ?: "날씨 불러오기 실패")) }
+ _uiState.update { it.copy(applications = UiState.Failure(e.message ?: "데이터를 불러오지 못했습니다")) }
}
}🤖 Prompt for AI Agents
In
`@feature/mypage/src/main/java/com/umcspot/spot/mypage/recruiting/application/RecruitingStudyApplicationViewModel.kt`
around lines 31 - 46, Move the side-effect assignment of currentStudyId out of
the _uiState.update lambda so update stays a pure state transformation: assign
currentStudyId = studyId before calling _uiState.update, then call
_uiState.update { it.copy(applications = if (info.applies.isEmpty())
UiState.Empty else UiState.Success(info)) } inside onSuccess. In onFailure,
replace the lone Log.e call with a UI state update that sets applications =
UiState.Failure(e.message ?: "신청 목록 불러오기 실패") (use the same _uiState.update
pattern) so the user sees an error, and remove the commented-out weather-related
code; ensure you reference the existing symbols _uiState.update, currentStudyId,
applications, UiState.Success, UiState.Empty, UiState.Failure, and onFailure
while making these changes.
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In
`@feature/mypage/src/main/java/com/umcspot/spot/mypage/recruiting/application/RecruitingStudyApplicationScreen.kt`:
- Around line 121-124: The current onAcceptClick handler sets showAcceptDialog =
true immediately after calling viewmodel.accept(it), which shows the success
dialog regardless of API outcome; change the flow so the dialog is shown only
when the ViewModel reports success — remove the immediate showAcceptDialog
assignment in RecruitingStudyApplicationScreen's onAcceptClick/onRejectClick and
instead subscribe to the ViewModel's result event (e.g., actionResult SharedFlow
or an equivalent LiveData) inside LaunchedEffect or a collector, then set
showAcceptDialog = true only when the collected result is ActionResult.Success
(and handle ActionResult.Error for toasts); apply the same pattern for the
similar block at the other occurrence.
In
`@feature/mypage/src/main/java/com/umcspot/spot/mypage/recruiting/application/RecruitingStudyApplicationViewModel.kt`:
- Around line 49-71: Add a user-feedback result event and emit success/error
from both accept and reject: introduce a sealed ActionResult (Success /
Error(message)), back it with a private MutableSharedFlow<ActionResult> named
_actionResult and expose actionResult: SharedFlow<ActionResult>; then in
RecruitingStudyApplicationViewModel functions accept(applicationId: Long) and
reject(applicationId: Long) call studyRepository.entryAcceptance(...).onSuccess
{ load(currentStudyId); _actionResult.emit(ActionResult.Success) } and
.onFailure { Log.e(...); _actionResult.emit(ActionResult.Error(e.message ?: "처리에
실패했습니다")) } so the UI can show a toast/snackbar from actionResult.
🧹 Nitpick comments (2)
feature/mypage/src/main/java/com/umcspot/spot/mypage/recruiting/application/RecruitingStudyApplicationScreen.kt (2)
182-209:indexOf대신itemsIndexed사용 권장
applicationList.indexOf(item)은 매 항목마다 O(n) 탐색을 수행하여 전체 O(n²) 복잡도가 됩니다.itemsIndexed를 사용하면 인덱스가 직접 제공됩니다.♻️ 수정 제안
- items( - items = applicationList, - key = { it.applicantId } - ) { item -> + itemsIndexed( + items = applicationList, + key = { _, item -> item.applicantId } + ) { index, item -> Spacer(Modifier.height(screenHeightDp(5.dp))) Box( modifier = Modifier.fillMaxWidth(), contentAlignment = Alignment.TopStart ) { ApplicationItem( applicationInfo = item, onAcceptClick = onAcceptClick, onRejectClick = onRejectClick ) } - if (applicationList.indexOf(item) != applicationList.lastIndex) { + if (index != applicationList.lastIndex) { Spacer(Modifier.height(screenHeightDp(5.dp))) HorizontalDivider( modifier = Modifier .fillMaxWidth(), color = SpotTheme.colors.G300, thickness = 1.dp ) } }
98-111:Empty와Failure상태 구분 필요
UiState.Empty와UiState.Failure를 동일하게 처리하여 사용자가 데이터 없음과 네트워크 오류를 구분할 수 없습니다. 에러 발생 시 적절한 메시지와 재시도 옵션을 제공하는 것이 좋습니다.♻️ 상태 분리 예시
- is UiState.Empty, is UiState.Failure -> { + is UiState.Empty -> { Box( modifier = Modifier .fillMaxSize() .background(SpotTheme.colors.white), contentAlignment = Alignment.Center ) { EmptyAlert( alertTitle = "신청자가 아직 없어요!", alertDes = "SPOT과 함께 새로운 만남을 기다려봐요", painter = painterResource(R.drawable.group_add), ) } } + is UiState.Failure -> { + Box( + modifier = Modifier + .fillMaxSize() + .background(SpotTheme.colors.white), + contentAlignment = Alignment.Center + ) { + EmptyAlert( + alertTitle = "오류가 발생했어요", + alertDes = ui.message ?: "잠시 후 다시 시도해주세요", + painter = painterResource(R.drawable.group_add), + // 재시도 버튼 추가 고려 + ) + } + }
| onAcceptClick = { | ||
| viewmodel.accept(it) | ||
| showAcceptDialog = true | ||
| }, |
There was a problem hiding this comment.
accept API 결과와 무관하게 성공 다이얼로그 표시
onAcceptClick에서 viewmodel.accept(it)을 호출한 직후 showAcceptDialog = true로 설정합니다. API 호출이 비동기로 실행되므로 실패하더라도 "수락 완료" 다이얼로그가 표시됩니다.
API 성공 콜백에서 다이얼로그를 표시하거나, ViewModel에서 결과 이벤트를 발행하여 UI에서 수신하는 방식을 권장합니다.
🛠️ 수정 접근 방식
onAcceptClick = {
- viewmodel.accept(it)
- showAcceptDialog = true
+ selectedApplicationId = it
+ viewmodel.accept(it)
+ // ViewModel에서 성공 이벤트 발행 시 다이얼로그 표시
},ViewModel에서 actionResult SharedFlow를 구독하여 성공 시에만 다이얼로그를 표시:
LaunchedEffect(Unit) {
viewmodel.actionResult.collect { result ->
when (result) {
is ActionResult.Success -> showAcceptDialog = true
is ActionResult.Error -> { /* 에러 토스트 표시 */ }
}
}
}Also applies to: 151-165
🤖 Prompt for AI Agents
In
`@feature/mypage/src/main/java/com/umcspot/spot/mypage/recruiting/application/RecruitingStudyApplicationScreen.kt`
around lines 121 - 124, The current onAcceptClick handler sets showAcceptDialog
= true immediately after calling viewmodel.accept(it), which shows the success
dialog regardless of API outcome; change the flow so the dialog is shown only
when the ViewModel reports success — remove the immediate showAcceptDialog
assignment in RecruitingStudyApplicationScreen's onAcceptClick/onRejectClick and
instead subscribe to the ViewModel's result event (e.g., actionResult SharedFlow
or an equivalent LiveData) inside LaunchedEffect or a collector, then set
showAcceptDialog = true only when the collected result is ActionResult.Success
(and handle ActionResult.Error for toasts); apply the same pattern for the
similar block at the other occurrence.
| fun accept(applicationId: Long) { | ||
| viewModelScope.launch { | ||
| studyRepository.entryAcceptance(applicationId = applicationId, decision = "APPROVE") | ||
| .onSuccess { | ||
| load(currentStudyId) | ||
| } | ||
| .onFailure { e -> | ||
| Log.e("RecruitingStudyApplicationViewModel", "acceptError", e) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| fun reject(applicationId: Long) { | ||
| viewModelScope.launch { | ||
| studyRepository.entryAcceptance(applicationId = applicationId, decision = "REJECT") | ||
| .onSuccess { | ||
| load(currentStudyId) | ||
| } | ||
| .onFailure { e -> | ||
| Log.e("RecruitingStudyApplicationViewModel", "rejectError", e) | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
accept/reject 실패 시 사용자 피드백 누락
accept와 reject 함수 모두 onFailure에서 로깅만 수행합니다. API 호출 실패 시 사용자에게 피드백이 없어 작업 결과를 알 수 없습니다.
에러 상태를 UI 상태에 추가하거나, 결과 이벤트(예: SharedFlow)를 통해 토스트/스낵바로 알림을 표시하는 것을 권장합니다.
🛠️ 결과 이벤트 추가 예시
// State에 결과 이벤트 추가
sealed class ActionResult {
object Success : ActionResult()
data class Error(val message: String) : ActionResult()
}
private val _actionResult = MutableSharedFlow<ActionResult>()
val actionResult: SharedFlow<ActionResult> = _actionResult
// accept 함수 수정
fun accept(applicationId: Long) {
viewModelScope.launch {
studyRepository.entryAcceptance(applicationId = applicationId, decision = "APPROVE")
.onSuccess {
load(currentStudyId)
_actionResult.emit(ActionResult.Success)
}
.onFailure { e ->
Log.e("RecruitingStudyApplicationViewModel", "acceptError", e)
_actionResult.emit(ActionResult.Error(e.message ?: "수락 처리에 실패했습니다"))
}
}
}🤖 Prompt for AI Agents
In
`@feature/mypage/src/main/java/com/umcspot/spot/mypage/recruiting/application/RecruitingStudyApplicationViewModel.kt`
around lines 49 - 71, Add a user-feedback result event and emit success/error
from both accept and reject: introduce a sealed ActionResult (Success /
Error(message)), back it with a private MutableSharedFlow<ActionResult> named
_actionResult and expose actionResult: SharedFlow<ActionResult>; then in
RecruitingStudyApplicationViewModel functions accept(applicationId: Long) and
reject(applicationId: Long) call studyRepository.entryAcceptance(...).onSuccess
{ load(currentStudyId); _actionResult.emit(ActionResult.Success) } and
.onFailure { Log.e(...); _actionResult.emit(ActionResult.Error(e.message ?: "처리에
실패했습니다")) } so the UI can show a toast/snackbar from actionResult.
Related issue 🛠
Work Description 📝
Screenshot 📸
Uncompleted Tasks 😅
PR Point 📌
트러블 슈팅 💥
Summary by CodeRabbit
릴리스 노트
새로운 기능
버그 수정 / 개선
✏️ Tip: You can customize this high-level summary in your review settings.