Skip to content

토스트 메시지 구조 구현#42

Merged
chanho0908 merged 13 commits intodevelopfrom
feat/#40-toast-host
Jan 26, 2026
Merged

토스트 메시지 구조 구현#42
chanho0908 merged 13 commits intodevelopfrom
feat/#40-toast-host

Conversation

@dogmania
Copy link
Copy Markdown
Contributor

이슈 번호

작업내용

  • MainActivity에서 토스트 메시지를 렌더링하는 ToastHost 컴포저블을 구현했습니다.
  • 뷰모델에서 토스트 메시지 이벤트를 발생시키는 ToastManage를 구현했습니다.

결과물

2026-01-26.6.21.07.mov

리뷰어에게 추가로 요구하는 사항 (선택)

사용 방법은 뷰모델에서 상태 처리나 로직 실행 이후에 토스트를 트리거해야 하는 상황이 되면 다음과 같이 사용하시면 됩니다!

toastManager.tryShow(ToastData(label = "토스트 메시지", type = ToastType.ERROR))

그리고 지금 MainActivity에 safeContentPadding이 아직 남아있어서 작업 다 끝나고 세부적인 padding은 수정해야 할 수도 있습니다!

@dogmania dogmania requested a review from chanho0908 January 26, 2026 12:02
@dogmania dogmania self-assigned this Jan 26, 2026
@dogmania dogmania added the Feature Extra attention is needed label Jan 26, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Jan 26, 2026

📝 Walkthrough

Walkthrough

프로젝트 전역에서 사용 가능한 토스트 메시지 시스템을 구현했습니다. ToastManager를 통해 토스트 이벤트를 관리하고, ToastHost 컴포저블로 UI를 렌더링합니다. 토스트 타입(SUCCESS, DELETE, LIKE, ERROR)별 아이콘 리소스를 추가하고, Koin DI를 통해 의존성을 주입합니다. MainActivity에 ToastHost를 통합하고 HomeViewModel에 ToastManager를 주입했습니다.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

상세 리뷰

✅ 잘 설계된 부분

명확한 책임 분리와 아키텍처

  • ToastManager(이벤트 관리), ToastHost(UI 렌더링), ToastData/ToastType/ToastAction(모델)이 각각의 책임을 명확히 가지고 있습니다
  • Koin DI를 통한 모듈화로 의존성 관리가 깔끔합니다

SharedFlow 활용

  • BufferOverflow.DROP_OLDEST 정책으로 버퍼가 가득 찼을 때 오래된 항목을 자동으로 제거하는 것은 좋은 설계입니다
  • show(suspend)와 tryShow(non-blocking) 두 API를 제공하여 다양한 상황에 대응할 수 있습니다

UI 구현의 완성도

  • collectLatest를 사용하여 가장 최신 토스트만 표시하고, 이전 토스트의 자동 닫기를 취소하는 로직이 탄탄합니다
  • 애니메이션(슬라이드 + 페이드)과 자동 닫기 타이밍 관리가 구현되어 있습니다

아이콘 리소스의 일관성

  • 4가지 토스트 타입별로 통일된 크기(24x24dp)와 색상 체계의 아이콘을 제공합니다

⚠️ 검토가 필요한 부분

1. 토스트 타입과 아이콘 매핑

현재 ToastType이 정의되어 있지만, ToastHost에서 이 타입을 아이콘으로 매핑하는 로직이 명확히 보이지 않습니다.

// 예상되는 패턴 (확인 필요)
when (data.type) {
    ToastType.SUCCESS -> R.drawable.ic_toast_success
    ToastType.DELETE -> R.drawable.ic_toast_delete
    // ...
}

질문: ToastType과 drawable 리소스 간의 매핑은 어디서 관리되고 있나요? ToastHost 코드에서 이 부분이 구현되어 있는지 확인 부탁드립니다.


2. SharedFlow의 버퍼 크기 검토

val _toasts = MutableSharedFlow<ToastData>(
    extraBufferCapacity = 16,
    onBufferOverflow = BufferOverflow.DROP_OLDEST
)

버퍼 크기가 16으로 설정되어 있습니다.

고려해볼 점:

  • 실제 사용 시나리오에서 16개의 토스트가 동시에 쌓일 가능성은 낮습니다
  • 보통 토스트는 순차적으로 표시되므로 더 작은 버퍼(예: 1-2)도 충분할 수 있습니다
  • 과도한 버퍼는 메모리 낭비로 이어질 수 있습니다

제안: 실제 사용 패턴을 고려하여 최소 필요한 버퍼 크기로 조정하는 것을 권장합니다.


3. HomeViewModel의 ToastManager 주입

class HomeViewModel(private val toastManager: ToastManager,) : BaseViewModel()

ToastManager가 주입되었지만, 현재 코드에서 사용되는 부분이 보이지 않습니다.

질문: HomeViewModel에서 실제로 토스트를 표시하는 로직이 있는가요? 아니면 이것은 향후 사용을 위한 사전 준비인가요? 향후 구현 계획이 있다면 명시해주시기 바랍니다.


4. ToastHost의 바닥 패딩 기본값

fun ToastHost(
    toastManager: ToastManager,
    modifier: Modifier = Modifier,
    bottomPadding: Dp = 80.dp
)

바닥 패딩이 80.dp로 기본 설정되어 있습니다.

고려해볼 점:

  • 실제 화면에서 네비게이션 바, 플로팅 액션 버튼 등과 겹칠 가능성을 검토해야 합니다
  • 기기별, 회전 상태별로 다를 수 있습니다

제안: 80.dp가 적절한지 실제 UI에서 검증하고, 필요시 동적 계산(insets API 활용)도 고려해보세요.


5. 오류 타입 아이콘의 이름 불일치

ToastType 열거형에 ERROR가 정의되어 있지만, 아이콘 파일은 ic_toast_warning.xml이 추가되었습니다.

질문: ERROR 타입이 경고 아이콘을 사용하는 것이 의도인가요? 아니면 아이콘 이름을 ic_toast_error.xml로 통일하는 것이 더 명확할까요?


💡 개선 제안

테스트 커버리지

  • ToastManager의 show/tryShow 메서드에 대한 단위 테스트를 추가하면 버퍼 오버플로우 동작, 동시성 상황 등을 검증할 수 있습니다

에러 핸들링

  • SharedFlow 구독 중 예외가 발생하는 경우에 대한 처리 로직이 있는지 확인 부탁드립니다

문서화

  • ToastManager와 ToastHost의 KDoc 주석을 추가하면 사용자가 올바르게 활용할 수 있습니다

종합 평가: 토스트 시스템의 전반적인 구조와 구현이 튼튼합니다. 위의 질문과 제안 사항들을 검토하신 후 필요한 부분을 조정하면 더욱 완성도 있는 시스템이 될 것 같습니다. 👍

🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목이 주요 변경 사항을 명확하고 간결하게 설명합니다: ToastHost와 ToastManager를 통한 토스트 메시지 시스템 구현.
Description check ✅ Passed PR 설명이 변경 사항과 관련이 있으며, ToastHost 구현, ToastManager 구현, 사용 예시를 포함하고 있습니다.
Linked Issues check ✅ Passed PR이 #40 이슈의 모든 요구사항을 충족합니다: ToastHost Composable 구현, ToastManager 구현 완료.
Out of Scope Changes check ✅ Passed PR의 모든 변경사항은 토스트 메시지 시스템 구현과 관련이 있으며, 정의된 이슈 범위를 벗어나지 않습니다.

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

✨ Finishing touches
  • 📝 Generate docstrings

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 and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@core/ui/src/main/java/com/twix/ui/toast/ToastHost.kt`:
- Around line 79-87: The dismiss() coroutine is not tracked and can clear a
newly shown toast later; change it to assign the launched coroutine to
dismissJob, capture the current toast instance into a local val before the
delay, and after delay only set current = null if current is still that captured
instance (keep visible = false as before); use the same pattern as the existing
LaunchedEffect-based dismiss logic for guidance (track dismissal with
dismissJob, compare the captured toast reference, and cancel previous dismissJob
when starting a new one). Also add Preview composables covering Success, Error,
Delete, Like toast types and variations with/without action buttons so the
component has multiple-state previews.

In `@core/ui/src/main/java/com/twix/ui/toast/ToastManager.kt`:
- Around line 10-27: The current MutableSharedFlow backing _toasts in
ToastManager is configured with BufferOverflow.DROP_OLDEST, which contradicts
the show(data: ToastData) contract that promises "no loss"; change the
onBufferOverflow to BufferOverflow.SUSPEND so emit() will suspend instead of
dropping items when the buffer is full, and update the show() KDoc to reflect
that it will suspend until the toast is accepted; reference symbols: _toasts
(MutableSharedFlow<ToastData>), show(data: ToastData), and the BufferOverflow
setting.

In `@feature/main/src/main/java/com/twix/home/HomeViewModel.kt`:
- Around line 9-11: HomeViewModel's constructor parameter toastManager is
unused; remove the unused dependency by deleting the toastManager parameter from
the HomeViewModel constructor (and any corresponding property) or, if toast
usage is intended, add the actual toast calls where needed; update any places
that instantiate HomeViewModel (factory/DI bindings/tests) to stop providing
toastManager and clean up related imports; ensure the class signature and
BaseViewModel generic usage remain unchanged.
🧹 Nitpick comments (4)
core/design-system/src/main/res/drawable/ic_toast_heart.xml (1)

6-10: 하드코딩된 색상값이 Dark Mode를 지원하지 않습니다.

현재 #FF6363, #171717 등의 색상이 직접 하드코딩되어 있습니다. 이 방식은 다음과 같은 문제가 있습니다:

  1. Dark Mode 미지원: 시스템 테마가 변경되어도 아이콘 색상이 고정됩니다
  2. 유지보수 어려움: 브랜드 색상 변경 시 모든 파일을 수동으로 수정해야 합니다

디자인 시스템의 색상 리소스(예: @color/toast_heart_fill)를 참조하거나, 테마 속성을 사용하는 것을 권장합니다. 다만, 토스트 아이콘의 특성상 고정 색상이 의도된 디자인이라면 이 부분은 무시하셔도 됩니다.

혹시 Dark Mode에서 이 아이콘들이 어떻게 보여야 하는지 디자인 가이드가 있을까요?

core/ui/src/main/java/com/twix/ui/toast/ToastHost.kt (1)

50-191: ToastHost/ToastItem의 Preview 추가를 고려해 주실 수 있을까요?
왜 필요하냐면, SUCCESS/ERROR + 액션 유무 등 상태별 UI를 빠르게 검증할 수 있어 재사용 가능한 UI 컴포넌트 품질이 올라갑니다.
어떻게 개선하느냐면, 최소 2~3개 상태(@Preview)로 아이콘/액션 유무를 보여주는 프리뷰를 추가해 주세요. 코딩 가이드 기준, 다양한 상태 Preview 제공이 권장됩니다.

core/ui/build.gradle.kts (1)

12-15: UI 모듈의 domain 의존성 필요성 재검토 제안입니다.
왜 고민이 필요하냐면, UI가 도메인에 직접 의존하면 모듈 결합도가 높아질 수 있어요.
어떻게 개선하느냐면, 실제로 필요한 것이 AppTextStyle 정도라면 해당 타입을 design-system(또는 ui 공통)으로 이동하는 방안도 검토해 보실까요? 코딩 가이드 기준, 모듈 간 의존성 방향을 점검하는 것이 권장됩니다.

app/src/main/java/com/yapp/twix/main/MainActivity.kt (1)

22-22: Compose 컨텍스트에서 Koin 주입 방식 검토

현재 org.koin.android.ext.android.inject를 사용하고 계신데, 이는 Activity 레벨의 주입 방식입니다. setContent 블록 내에서도 동작하긴 하지만, Compose 환경에서는 org.koin.androidx.compose.koinInject<T>()를 사용하는 것이 더 관용적입니다.

현재 방식이 문제를 일으키지는 않지만, Compose 생명주기와 더 잘 통합되는 koinInject()로 변경하는 것을 고려해 보시겠어요?

♻️ Compose 친화적인 Koin 주입 방식
-import org.koin.android.ext.android.inject
+import org.koin.androidx.compose.koinInject

 class MainActivity : ComponentActivity() {
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)

         setContent {
-            val toastManager by inject<ToastManager>()
+            val toastManager = koinInject<ToastManager>()

Copy link
Copy Markdown
Contributor

@chanho0908 chanho0908 left a comment

Choose a reason for hiding this comment

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

캬... 고생했어 !! 현수는 재사용 가능한 코드를 정말 잘 만드는 것 같네 👍

@chanho0908 chanho0908 merged commit a25cd5a into develop Jan 26, 2026
29 checks passed
@chanho0908 chanho0908 deleted the feat/#40-toast-host branch January 30, 2026 16:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Feature Extra attention is needed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

토스트 메시지 구조 구현

2 participants