Skip to content

Conversation

@juhwankim-dev
Copy link
Member

as-is) 타이머 화면에서 힌트 화면으로 넘어갈 때 argument를 넘겨준다.
to-be) 하나의 viewModel을 같이 공유해서 사용하도록 한다. (graph viewModel)

왜?
힌트 화면에서 진행률만 확인하고 힌트 차감없이 되돌아가는 기능이 새로 개발될 예정임
기존에는 힌트를 차감하고 다음 화면으로 넘어가는 방식이였으나
앞으로는 힌트 화면에서 데이터의 변경을 담당하게 될거임.
근데 이걸 setFragmentResult로 하면 다소 코드가 너저분해지고
나중에 기능이 확장될 것을 생각하면 이 방식은 누가 누구에게 무엇을 넘겼는지 파악하기 어려워짐

왜?
힌트 화면에서 진행률만 확인하고 힌트 차감없이 되돌아가는 기능이 새로 개발될 예정임
기존에는 힌트를 차감하고 다음 화면으로 넘어가는 방식이였으나
앞으로는 힌트 화면에서 데이터의 변경을 담당하게 될거임.
근데 이걸 setFragmentResult로 하면 다소 코드가 너저분해지고
나중에 기능이 확장될 것을 생각하면 이 방식은 누가 누구에게 무엇을 넘겼는지 파악하기 어려워짐

어떻게?
argument를 넘기는 방식에서 graph viewModel을 사용해 관리하는 방법으로 변경
왜?
굉장히 긴 샘플 문자열 데이터가 들어가있었는데
이렇게 되면 ui에서 실제 데이터가 로드되기 전 초기값을 보여주게 되었을때 사용자에게 노출될 가능성이 있음
(데이터 로드전에는 로딩으로 표시한다거나.. 처리를 하면 되긴하지만 놓칠 가능성이 있으니까)
그래서 불필요한 초기값 세팅들을 수정함
id도 -1로 하면 버그가 생길 가능성이 있어서 0으로 수정
@juhwankim-dev juhwankim-dev self-assigned this Dec 25, 2025
@github-actions
Copy link

🤖 AI Code Review (Gemini)

안녕하세요, NextRoom 프로젝트의 NR-122 Pull Request에 대한 코드 리뷰를 시작하겠습니다.

이번 PR은 타이머 화면과 힌트 화면이 GameSharedViewModel을 공유하여 데이터를 주고받도록 리팩토링하는 것을 목표로 하고 있습니다. 기존에 FragmentResultarguments를 통해 데이터를 전달하던 방식에서 벗어나, hiltNavGraphViewModels를 활용하여 더 깔끔하고 확장성 있는 데이터 공유 방식을 도입하려는 시도는 매우 긍정적입니다.


💡 전반적인 피드백

  • 긍정적인 변화: GameSharedViewModel 도입을 통해 TimerFragmentHintFragment 간의 데이터 전달 방식이 훨씬 간결하고 명확해졌습니다. 특히 힌트 화면에서 데이터 변경이 일어날 경우를 대비한 유연한 구조를 마련한 점이 좋습니다. SavedStateHandle을 통한 arguments 전달 방식이 제거되어 코드 가독성도 향상되었습니다.
  • Clean Architecture 준수: GameSharedViewModelpresentation 모듈에 위치하며 UI 관련 상태를 공유하는 역할을 하는 것은 Clean Architecture 원칙에 부합합니다.
  • Orbit MVI 패턴 유지: HintViewModel은 여전히 Orbit MVI 패턴을 유지하며 자신의 상태를 관리하고, HintFragmentGameSharedViewModel로부터 받은 데이터를 HintViewModel로 전달하는 방식은 각 ViewModel의 책임을 명확히 분리합니다.

🟡 Warning

File: presentation/src/main/java/com/nextroom/nextroom/presentation/ui/main/GameSharedViewModel.kt
Line: 25, 29

    fun setCurrentHint(hint: Hint) {
        _currentHint.value = hint
    }

    fun updateCurrentHint(hint: Hint) {
        _currentHint.value = hint
    }

setCurrentHintupdateCurrentHint 두 함수가 동일한 로직을 수행하고 있습니다. 둘 중 하나만 남겨두고 사용하는 것이 코드의 중복을 피하고 혼란을 줄일 수 있습니다. setCurrentHint가 더 명확해 보이므로 updateCurrentHint를 제거하는 것을 권장합니다.


💡 Suggestion

File: presentation/src/main/java/com/nextroom/nextroom/presentation/ui/hint/HintFragment.kt
Line: 48-57

    private fun initSubscribe() {
        viewLifecycleOwner.repeatOnStarted {
            launch {
                gameSharedViewModel.currentHint.collect { hint ->
                    hint?.let { viewModel.setHint(it) }
                }
            }
            launch {
                gameSharedViewModel.subscribeStatus.collect { subscribeStatus ->
                    viewModel.setSubscribeStatus(subscribeStatus)
                }
            }
        }
    }

HintFragment에서 gameSharedViewModelcurrentHintsubscribeStatus를 구독하여 HintViewModelsetHintsetSubscribeStatus를 호출하는 방식은 현재로서는 명확합니다.

하지만 HintViewModelHintFragment를 통해 GameSharedViewModel의 데이터를 전달받는 대신, HintViewModel이 직접 GameSharedViewModel을 주입받아 해당 Flow를 구독하는 방식도 고려해볼 수 있습니다. 이렇게 하면 HintFragmentGameSharedViewModelHintViewModel 사이의 브릿지 역할을 할 필요가 없어집니다.

예시 (고려해볼 수 있는 대안):

// HintViewModel.kt
@HiltViewModel
class HintViewModel @Inject constructor(
    private val timerRepository: TimerRepository,
    private val dataStoreRepository: DataStoreRepository,
    private val gameSharedViewModel: GameSharedViewModel, // GameSharedViewModel 주입
) : BaseViewModel<HintState, HintEvent>() {

    override val container: Container<HintState, HintEvent> = container(HintState())

    init {
        // ViewModel 내부에서 GameSharedViewModel의 Flow를 구독
        viewModelScope.launch {
            gameSharedViewModel.currentHint.collect { hint ->
                hint?.let { setHint(it) }
            }
        }
        viewModelScope.launch {
            gameSharedViewModel.subscribeStatus.collect { subscribeStatus ->
                setSubscribeStatus(subscribeStatus)
            }
        }
        // ... 기존 로직
    }
    // ... setHint, setSubscribeStatus 등 기존 함수
}

// HintFragment.kt
// initSubscribe() 제거
// viewModel은 그대로 사용

장점:

  • HintFragment가 더 간결해지고, UI 로직에만 집중할 수 있습니다.
  • HintViewModel이 필요한 데이터를 직접 가져오므로, 데이터 흐름이 더 명확해질 수 있습니다.

단점:

  • HintViewModelGameSharedViewModel에 직접적인 의존성을 갖게 됩니다. (이는 hiltNavGraphViewModels를 사용하는 목적과 일치하므로 큰 단점은 아닐 수 있습니다.)
  • HintViewModel 내에서 viewModelScope를 사용하여 Flow를 구독해야 합니다.

현재 구현도 유효하며, 이 제안은 향후 유사한 패턴이 반복될 경우 고려해볼 만한 대안으로 제시합니다.


💡 Suggestion

File: presentation/src/main/java/com/nextroom/nextroom/presentation/model/Hint.kt
Line: 6

data class Hint(
    val id: Int = 0,
    val progress: Int = 0,
    val hint: String = "",
    val answer: String = "",
    val answerOpened: Boolean = false, // 추가
    val hintImageUrlList: List<String> = emptyList(), // 추가
    val answerImageUrlList: List<String> = emptyList(), // 추가
) : Serializable

HintViewModelonOpenHint 함수에서 TimerViewModel로부터 전달받은 Hint 객체를 copy할 때 answerOpened, hintImageUrlList, answerImageUrlList 필드도 함께 복사하고 있습니다.

// TimerViewModel.kt
// ...
                    TimerEvent.OnOpenHint(
                        hint = Hint(
                            id = hint.id,
                            progress = hint.progress,
                            hint = hint.hint,
                            answer = hint.answer,
                            answerOpened = state.answerOpenedHints.contains(hint.id),
                            hintImageUrlList = hint.hintImageUrlList.toList(),
                            answerImageUrlList = hint.answerImageUrlList.toList()
                        )
                    )
// ...

presentation/model/Hint.ktHint 데이터 클래스 기본 생성자에 이 필드들을 추가하여, Hint()와 같이 기본 생성자를 호출할 때도 완전한 Hint 객체가 생성되도록 하는 것이 좋습니다. 이렇게 하면 HintState에서 Hint()를 사용할 때도 모든 필드가 초기화된 상태로 시작할 수 있습니다.


💡 Suggestion

File: presentation/src/main/java/com/nextroom/nextroom/presentation/ui/hint/HintViewModel.kt
Line: 18-20

class HintViewModel @Inject constructor(
    private val timerRepository: TimerRepository,
    // private val statsRepository: StatisticsRepository, // 제거
    // private val adminRepository: AdminRepository, // 제거
    private val dataStoreRepository: DataStoreRepository,
) : BaseViewModel<HintState, HintEvent>() {

주석 처리된 statsRepositoryadminRepository는 이미 사용되지 않는 것으로 보이며, 이번 PR에서 관련 주석 코드도 제거되었습니다. 사용하지 않는 의존성은 제거하여 ViewModel의 책임을 명확히 하고 불필요한 객체 생성을 막는 것이 좋습니다.


💡 Suggestion

File: presentation/src/main/java/com/nextroom/nextroom/presentation/ui/hint/HintState.kt
Line: 12

data class HintState(
    val loading: Boolean = false,
    val hint: Hint = Hint(), // 변경됨
    val lastSeconds: Int = 0,
    val userSubscribeStatus: SubscribeStatus = SubscribeStatus.Default,
    val networkDisconnectedCount: Int = 0
)

HintStatehint 필드 기본값이 Hint()로 변경되었습니다. 이는 매우 좋은 변경입니다. 다만, Hint 데이터 클래스의 기본값에 answerOpened, hintImageUrlList, answerImageUrlList 필드도 포함되어 있다면, HintState 초기화 시에도 이 필드들이 올바르게 초기화될 것입니다. (위 Hint.kt 제안과 연관됩니다.)


Review Summary

이번 PR은 GameSharedViewModel을 도입하여 TimerFragmentHintFragment 간의 데이터 공유 방식을 개선하는 훌륭한 리팩토링입니다. 기존의 arguments 전달 방식의 한계를 극복하고, 향후 기능 확장을 위한 유연한 기반을 마련했습니다.

제시된 Warning과 Suggestion들은 대부분 코드의 일관성과 명확성을 높이기 위한 제안이며, 현재 구현이 동작하는 데 큰 문제는 없습니다. 특히 GameSharedViewModel의 중복 함수 제거와 Hint 데이터 클래스의 기본값 개선은 반영하는 것을 권장합니다.

전반적으로 잘 작성된 코드이며, 프로젝트의 Clean Architecture 및 Orbit MVI 패턴을 잘 준수하고 있습니다.


최종 승인 여부: 승인 (Approval)

위에서 언급된 Warning과 Suggestion들을 고려하여 개선하면 더욱 완벽한 코드가 될 것입니다.


This review was automatically generated using Gemini AI. Please use your judgment when addressing the feedback.

왜?
setCurrentHint와 역할이 겹쳐 제거한다.
@juhwankim-dev juhwankim-dev merged commit a0a9c88 into develop Dec 25, 2025
1 check failed
@juhwankim-dev juhwankim-dev deleted the feature/NR-122 branch December 25, 2025 16:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants