Skip to content

Conversation

@oungsi2000
Copy link
Contributor

@oungsi2000 oungsi2000 commented Jan 8, 2026

#️⃣ 이슈 번호

#21


🛠️ 작업 내용

  • 설정화면을 Compose로 마이그레이션 하였습니다.

🙇🏻 중점 리뷰 요청

  • 기존 SingleLiveData을 SharedFlow의 replayCache = 1로 설정하여 최대 한 번의 이벤트는 유실되지 않게끔 처리하였습니다.
    (Channel의 경우 다른 곳에서 이벤트를 소비할 경우 동작하지 않습니다)

  • ObserveAsEvent는 해당 PR 에서도 같은 코드를 사용하고 있습니다. PR이 머지가 된다면 해당 코드를 적용하겠습니다.

  • 학생회 페이지에서 푸시 알림 테스트는 통과하였습니다만...혹시 모르니 한 번씩 테스트 부탁드립니다.


📸 이미지 첨부 (Optional)

Summary by CodeRabbit

Release Notes

  • New Features
    • Updated settings screen with university subscription management, app version information, and direct access to policy and contact resources.

✏️ Tip: You can customize this high-level summary in your review settings.

앱 설정 화면(`SettingScreen`)을 Jetpack Compose로 신규 구현했습니다. 사용자가 대학 소식 알림을 설정하고, 앱 정보(버전, 약관, 문의하기)를 확인할 수 있는 UI를 구성했습니다.

- **`SettingScreen.kt` 추가:**
    - `Scaffold`와 공통 `Header` 컴포넌트를 사용하여 전체 화면 레이아웃을 구성했습니다.
    - `SubscriptionContent` 컴포저블을 통해 대학 이름 표시 및 알림 구독 상태를 제어하는 `Switch` UI를 구현했습니다.
    - `AppInfoContent` 컴포저블을 통해 앱 버전 정보 표시, 이용 약관 및 문의하기 버튼을 리스트 형태로 구현했습니다.
    - `HorizontalDivider`를 사용하여 콘텐츠 섹션을 시각적으로 구분하고, `FestabookTheme` 디자인 시스템을 적용했습니다.
설정 화면의 데이터를 `FestivalUiState`로 관리하도록 변경하고, 구독 스위치의 활성화 제어 기능을 추가했습니다. 또한 상단 앱바 컴포넌트를 교체하여 UI 일관성을 맞췄습니다.

- **`SettingScreen.kt` 수정:**
    - 컴포저블 파라미터를 `universityName` 문자열 대신 `FestivalUiState`로 변경하여, 상태(`Success`, `Error`)에 따라 콘텐츠 표시 및 에러 처리를 수행하도록 구조를 개편했습니다.
    - `LaunchedEffect`를 추가하여 `UiState.Error` 발생 시 `onError` 콜백이 호출되도록 로직을 추가했습니다.
    - 기존 `Header` 컴포넌트를 `FestabookTopAppBar`로 교체하고, 전체 화면 배경색을 `White`로 지정했습니다.
    - `SubscriptionContent`에 `isSubscribeEnabled` 파라미터를 추가하여 스위치의 `enabled` 속성을 제어하고, 비활성화 상태일 때의 트랙 색상을 정의했습니다.
`SettingViewModel`의 데이터 흐름을 개선하기 위해 기존 `LiveData` 기반의 상태 관리를 Coroutines Flow API로 마이그레이션했습니다.

- **`SettingViewModel.kt` 수정:**
    - UI 상태(`isAllowed`, `isLoading`)를 관리하던 `MutableLiveData`를 `MutableStateFlow`로 교체했습니다.
    - 일회성 이벤트(`permissionCheckEvent`, `error`, `success`) 처리를 위해 사용하던 `SingleLiveData`를 `MutableSharedFlow`로 변경했습니다.
    - 데이터 발행 방식을 `setValue`에서 `tryEmit`으로 수정하고, `Boolean?` 타입 체크 로직을 Flow의 `value` 접근 방식으로 간소화했습니다.
기존 XML 및 DataBinding 기반의 설정 화면을 Jetpack Compose로 마이그레이션했습니다.

- **`SettingFragment.kt` 리팩토링:**
    - `onCreateView`에서 `ComposeView`를 반환하도록 변경하고, `SettingScreen` 컴포저블을 통해 UI를 렌더링하도록 수정했습니다.
    - `DataBinding` 및 `View` 관련 로직을 제거하고, `collectAsStateWithLifecycle`을 사용하여 `ViewModel`의 상태를 구독하도록 변경했습니다.
    - 권한 요청 및 URL 이동 등의 사이드 이펙트를 처리하기 위해 `ObserveAsEvents`를 적용했습니다.

- **`ObserveAsEvents.kt` 추가:**
    - Compose 환경에서 `Flow` 기반의 이벤트를 생명주기에 맞춰 안전하게 수집할 수 있는 유틸리티 함수를 추가했습니다.
`SettingViewModel` 테스트 코드에서 `getOrAwaitValue` 확장 함수 의존성을 제거하고, `StateFlow` 및 `SharedFlow` 특성에 맞는 검증 로직으로 변경했습니다.

- **`SettingViewModelTest.kt` 수정:**
    - 불필요해진 `getOrAwaitValue` import 구문을 제거했습니다.
    - `permissionCheckEvent` 검증 시 `value` 대신 `replayCache.first()`를 사용하여 이벤트를 확인하도록 수정했습니다.
    - `isAllowed` 상태 검증 시 `getOrAwaitValue()` 호출을 `value` 프로퍼티 접근으로 대체했습니다.
    - 일부 코드의 줄바꿈 포맷팅을 수정했습니다.
@oungsi2000 oungsi2000 self-assigned this Jan 8, 2026
@oungsi2000 oungsi2000 added the Feat label Jan 8, 2026
@oungsi2000
Copy link
Contributor Author

@CodeRabbit review

@coderabbitai
Copy link

coderabbitai bot commented Jan 8, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai
Copy link

coderabbitai bot commented Jan 8, 2026

📝 Walkthrough

Walkthrough

The pull request migrates the SettingFragment UI from a binding-based Fragment setup to a Compose-based implementation, introducing a lifecycle-aware Flow observation utility, converting SettingViewModel's reactive properties from LiveData to Flow/StateFlow variants, and adding a new Compose component for the settings screen.

Changes

Cohort / File(s) Summary
Compose Infrastructure
app/src/main/java/com/daedan/festabook/presentation/common/component/ObserveAsEvents.kt, app/src/main/java/com/daedan/festabook/presentation/setting/component/SettingScreen.kt
New composable utility ObserveAsEvents for lifecycle-aware Flow collection on Main dispatcher. New SettingScreen Compose component with subscription toggle, app info section, and themed UI with preview.
ViewModel Reactive Properties
app/src/main/java/com/daedan/festabook/presentation/setting/SettingViewModel.kt
Converted 5 public properties from LiveData/SingleLiveData to Flow/StateFlow variants: permissionCheckEvent, isAllowed, error, isLoading, and success. Updated emission logic to use tryEmit() and adapted state management accordingly.
Fragment UI Migration
app/src/main/java/com/daedan/festabook/presentation/setting/SettingFragment.kt
Replaced onViewCreated with Compose-based onCreateView returning ComposeView. Wired state collection via collectAsStateWithLifecycle, integrated ObserveAsEvents for events, and rendered SettingScreen with event callbacks.
Test Updates
app/src/test/java/com/daedan/festabook/setting/SettingViewModelTest.kt
Updated test assertions to read from Flow's replayCache and StateFlow.value instead of LiveData getters. Minor formatting adjustments to mocked failure values.

Sequence Diagram(s)

sequenceDiagram
    participant SF as SettingFragment
    participant VM as SettingViewModel
    participant CS as SettingScreen
    participant Flow as Flow/StateFlow

    SF->>SF: onCreateView() creates ComposeView
    SF->>VM: collectAsStateWithLifecycle(permissionCheckEvent)
    SF->>VM: collectAsStateWithLifecycle(isAllowed)
    SF->>VM: collectAsStateWithLifecycle(error)
    SF->>VM: collectAsStateWithLifecycle(isLoading)
    SF->>VM: collectAsStateWithLifecycle(successFlow)
    VM->>Flow: StateFlow emits current state
    Flow->>SF: State collected as Compose State
    SF->>CS: Pass state and callbacks to SettingScreen
    CS->>CS: Render with ObserveAsEvents for events
    Note over CS: User interaction (subscribe toggle, policy click)
    CS->>VM: Call callback (onSubscribeClick, onPolicyClick)
    VM->>Flow: Emit new state/event
    Flow->>SF: Observe lifecycle & collect on STARTED
    SF->>CS: Recompose with new state
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~30 minutes

Suggested reviewers

  • parkjiminnnn

Poem

🐰 From LiveData to Flow we hop,
Fresh Compose builds from the crop,
Lifecycle-aware, lifecycle-safe,
Settings now have Jetpack grace,
A rabbit's refactor, complete and true! 🌟

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: migrating the settings screen UI to Jetpack Compose, which is the primary objective across all modified files.

✏️ 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

@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: 1

🤖 Fix all issues with AI agents
In
@app/src/main/java/com/daedan/festabook/presentation/setting/SettingFragment.kt:
- Around line 86-88: Remove the redundant super.onCreateView(...) invocation
inside SettingFragment (it inflates fragment_setting.xml in BaseFragment and its
result is unused) and update the class generic: change the class declaration to
extend BaseFragment<FragmentSettingBinding>() if the base requires a type
parameter, or remove the generic from BaseFragment usage entirely if not needed;
ensure no other code in SettingFragment references the discarded binding and
keep the ComposeView creation and setViewCompositionStrategy call intact.
🧹 Nitpick comments (7)
app/src/test/java/com/daedan/festabook/setting/SettingViewModelTest.kt (1)

24-25: Consider removing InstantTaskExecutorRule if no longer needed.

Since the ViewModel properties have been migrated from LiveData to StateFlow/SharedFlow, this rule may no longer be necessary. The only remaining LiveData is success (via asLiveData()), but it's not tested directly in these tests.

app/src/main/java/com/daedan/festabook/presentation/common/component/ObserveAsEvents.kt (1)

20-26: The withContext(Dispatchers.Main.immediate) may be redundant.

LaunchedEffect already runs in the composition's coroutine context (Main dispatcher), and repeatOnLifecycle preserves this context. The explicit withContext call adds unnecessary overhead.

♻️ Simplified implementation
     LaunchedEffect(flow, lifecycleOwner.lifecycle) {
         lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
-            withContext(Dispatchers.Main.immediate) {
-                flow.collect(onEvent)
-            }
+            flow.collect(onEvent)
         }
     }
app/src/main/java/com/daedan/festabook/presentation/setting/SettingFragment.kt (1)

70-70: Consider using requireView() instead of view!!.

Using view!! can throw an unhandled NullPointerException. While this callback occurs after view creation, using requireView() provides a clearer error message if called at an unexpected time.

♻️ Safer alternative
-                showNotificationDeniedSnackbar(view!!, requireContext())
+                showNotificationDeniedSnackbar(requireView(), requireContext())
app/src/main/java/com/daedan/festabook/presentation/setting/component/SettingScreen.kt (4)

59-64: Extract duplicated screenWidthDp calculation.

The screen width calculation is duplicated in SettingScreen (lines 59-64) and AppInfoContent (lines 191-196). Consider extracting this to a local composable helper or passing the value as a parameter.

♻️ Pass screenWidthDp as parameter to AppInfoContent
 @Composable
 private fun AppInfoContent(
     appVersion: String,
     onPolicyClick: () -> Unit,
     onContactUsClick: () -> Unit,
+    screenWidthDp: Dp,
     modifier: Modifier = Modifier,
 ) {
-    val windowInfo = LocalWindowInfo.current
-    val density = LocalDensity.current
-    val screenWidthDp =
-        with(density) {
-            windowInfo.containerSize.width.toDp()
-        }

Also applies to: 191-196


165-179: Consider making disabled state visually distinct.

The disabled track colors (disabledCheckedTrackColor = FestabookColor.black, disabledUncheckedTrackColor = FestabookColor.gray200) match the enabled colors. Users may not realize the switch is disabled during loading. Consider using a lighter/muted color for the disabled state.


224-272: Consider extracting a reusable ClickableInfoRow composable.

The Policy and Contact Us rows share identical structure. Extracting a reusable component would reduce duplication.

♻️ Extracted component example
@Composable
private fun ClickableInfoRow(
    text: String,
    screenWidthDp: Dp,
    onClick: () -> Unit,
    modifier: Modifier = Modifier,
) {
    Row(
        horizontalArrangement = Arrangement.SpaceBetween,
        verticalAlignment = Alignment.CenterVertically,
        modifier = modifier
            .requiredWidth(screenWidthDp)
            .clickable(onClick = onClick)
            .padding(
                horizontal = festabookSpacing.paddingScreenGutter,
                vertical = festabookSpacing.paddingBody3,
            ),
    ) {
        Text(text = text, style = FestabookTypography.titleMedium)
        Icon(
            painter = painterResource(R.drawable.ic_arrow_forward_right),
            contentDescription = stringResource(R.string.move),
            tint = Color.Unspecified,
        )
    }
}

90-101: Consider adding a loading indicator for non-Success states.

When festivalUiState is Loading, the SubscriptionContent area is empty. Consider showing a shimmer/skeleton placeholder for better UX during data loading.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a417c29 and 2e4ebc2.

📒 Files selected for processing (5)
  • app/src/main/java/com/daedan/festabook/presentation/common/component/ObserveAsEvents.kt
  • app/src/main/java/com/daedan/festabook/presentation/setting/SettingFragment.kt
  • app/src/main/java/com/daedan/festabook/presentation/setting/SettingViewModel.kt
  • app/src/main/java/com/daedan/festabook/presentation/setting/component/SettingScreen.kt
  • app/src/test/java/com/daedan/festabook/setting/SettingViewModelTest.kt
🧰 Additional context used
🧬 Code graph analysis (2)
app/src/main/java/com/daedan/festabook/presentation/setting/component/SettingScreen.kt (2)
app/src/main/java/com/daedan/festabook/presentation/common/component/FestabookTopAppBar.kt (1)
  • FestabookTopAppBar (13-31)
app/src/main/java/com/daedan/festabook/presentation/theme/FestabookTheme.kt (1)
  • FestabookTheme (15-34)
app/src/main/java/com/daedan/festabook/presentation/setting/SettingFragment.kt (4)
app/src/main/java/com/daedan/festabook/presentation/common/PermissionUtil.kt (1)
  • showNotificationDeniedSnackbar (13-32)
app/src/main/java/com/daedan/festabook/presentation/common/component/ObserveAsEvents.kt (1)
  • ObserveAsEvents (14-27)
app/src/main/java/com/daedan/festabook/presentation/common/FragmentUtil.kt (3)
  • showErrorSnackBar (54-56)
  • showErrorSnackBar (58-75)
  • showErrorSnackBar (77-109)
app/src/main/java/com/daedan/festabook/presentation/setting/component/SettingScreen.kt (1)
  • SettingScreen (47-120)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Run-PR-Test
🔇 Additional comments (10)
app/src/test/java/com/daedan/festabook/setting/SettingViewModelTest.kt (2)

45-60: LGTM!

The test correctly uses replayCache.first() to access the emitted event from the SharedFlow with replay = 1, which is the appropriate pattern for testing one-shot events.


62-118: LGTM!

The remaining tests correctly use StateFlow.value to access state and properly verify the rollback behavior when server operations fail.

app/src/main/java/com/daedan/festabook/presentation/common/component/ObserveAsEvents.kt (1)

12-13: Acknowledge duplication note.

The comment appropriately documents the intentional duplication with PR #24. Ensure proper conflict resolution during merge.

app/src/main/java/com/daedan/festabook/presentation/setting/SettingFragment.kt (1)

89-131: LGTM!

The Compose migration is well-structured:

  • State collection uses collectAsStateWithLifecycle for lifecycle awareness
  • ObserveAsEvents correctly handles one-shot events (permissions, success, ViewModel errors)
  • SettingScreen callbacks are properly wired
  • Error handling distinguishes between FestivalUiState.Error (UI state) and ViewModel operation errors
app/src/main/java/com/daedan/festabook/presentation/setting/component/SettingScreen.kt (1)

65-72: LGTM!

The use of rememberUpdatedState for onError correctly prevents stale closure issues in LaunchedEffect, ensuring the latest callback is always invoked.

app/src/main/java/com/daedan/festabook/presentation/setting/SettingViewModel.kt (5)

75-93: Verify the intended UX for optimistic success notification.

The success event is emitted (line 78) before the server call completes. If the server call fails, users see the success snackbar followed by an error snackbar. Consider emitting success only after server confirmation, or clarifying this is the intended behavior.


27-31: Be aware of event replay on resubscription.

MutableSharedFlow(replay = 1) caches the last emitted event, which replays to new subscribers (e.g., on configuration change). This could cause duplicate snackbars or permission requests. Consider using resetReplayCache() after consumption if this becomes problematic.

Also applies to: 39-43, 48-51


55-61: LGTM!

The notificationAllowClick function correctly checks the current allowed state before deciding to request permission or delete the notification.


96-118: LGTM!

The deleteNotificationId function correctly implements the optimistic UI pattern with proper rollback on failure.


48-53: No action needed. Both success (LiveData) and successFlow (SharedFlow) are actively used in different parts of the codebase: MainActivity.kt observes the LiveData version while SettingFragment.kt uses the SharedFlow via ObserveAsEvents. The dual exposure is intentional to support different consumer patterns.

Likely an incorrect or invalid review comment.

Comment on lines +86 to +88
ComposeView(requireContext()).apply {
super.onCreateView(inflater, container, savedInstanceState)
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -type f -name "*.kt" | xargs grep -l "class BaseFragment" | head -5

Repository: festabook/android

Length of output: 137


🏁 Script executed:

cat -n app/src/main/java/com/daedan/festabook/presentation/setting/SettingFragment.kt | sed -n '1,50p'

Repository: festabook/android

Length of output: 2673


🏁 Script executed:

cat -n app/src/main/java/com/daedan/festabook/presentation/setting/SettingFragment.kt | sed -n '80,110p'

Repository: festabook/android

Length of output: 1721


🏁 Script executed:

cat -n ./app/src/main/java/com/daedan/festabook/presentation/common/BaseFragment.kt

Repository: festabook/android

Length of output: 2326


🏁 Script executed:

cat -n app/src/main/java/com/daedan/festabook/presentation/setting/SettingFragment.kt | sed -n '80,132p'

Repository: festabook/android

Length of output: 2794


🏁 Script executed:

# Check if FragmentSettingBinding is used anywhere in SettingFragment
rg "FragmentSettingBinding" app/src/main/java/com/daedan/festabook/presentation/setting/SettingFragment.kt -n

Repository: festabook/android

Length of output: 173


🏁 Script executed:

# Check if SettingFragment accesses binding anywhere
rg "binding\." app/src/main/java/com/daedan/festabook/presentation/setting/SettingFragment.kt -n

Repository: festabook/android

Length of output: 126


🏁 Script executed:

# Check if binding is accessed in SettingFragment
rg "this\.binding" app/src/main/java/com/daedan/festabook/presentation/setting/SettingFragment.kt -n

Repository: festabook/android

Length of output: 43


🏁 Script executed:

# Verify the complete SettingFragment file to ensure nothing uses binding
wc -l app/src/main/java/com/daedan/festabook/presentation/setting/SettingFragment.kt

Repository: festabook/android

Length of output: 143


Remove unnecessary super.onCreateView() call and unused binding type parameter.

The super.onCreateView() call at line 87 inflates the XML layout (fragment_setting.xml) via DataBindingUtil.inflate() in BaseFragment, but the created binding and return value are immediately discarded since SettingFragment returns a ComposeView instead. This wastes resources during initialization. Additionally, the FragmentSettingBinding type parameter is never accessed in this fragment.

Remove the super.onCreateView() call and change the class declaration to BaseFragment<FragmentSettingBinding>() if the base class type parameter is required by the framework, or refactor to remove the type parameter entirely if possible.

🤖 Prompt for AI Agents
In
@app/src/main/java/com/daedan/festabook/presentation/setting/SettingFragment.kt
around lines 86 - 88, Remove the redundant super.onCreateView(...) invocation
inside SettingFragment (it inflates fragment_setting.xml in BaseFragment and its
result is unused) and update the class generic: change the class declaration to
extend BaseFragment<FragmentSettingBinding>() if the base requires a type
parameter, or remove the generic from BaseFragment usage entirely if not needed;
ensure no other code in SettingFragment references the discarded binding and
keep the ComposeView creation and setViewCompositionStrategy call intact.

Copy link
Contributor

@etama123 etama123 left a comment

Choose a reason for hiding this comment

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

밀러 고생하셨습니다 ~~

) {
val windowInfo = LocalWindowInfo.current
val density = LocalDensity.current
val screenWidthDp =
Copy link
Contributor

Choose a reason for hiding this comment

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

해당 값을 직접 계산해서 사용하기 보다는, 패딩이 필요한 컨텐츠에 한해서 따로 패딩을 주는 건 어떨까요?

Comment on lines +230 to +234
.clickable {
onPolicyClick()
}.padding(
horizontal = festabookSpacing.paddingScreenGutter,
vertical = festabookSpacing.paddingBody3,
Copy link
Contributor

Choose a reason for hiding this comment

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

패딩을 주기 전 부분만 클릭 속성이 적용되어 있는데, 의도된 부분일까요?

Comment on lines +249 to +273
Row(
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
modifier =
Modifier
.requiredWidth(screenWidthDp)
.clickable {
onContactUsClick()
}.padding(
horizontal = festabookSpacing.paddingScreenGutter,
vertical = festabookSpacing.paddingBody3,
),
) {
Text(
text = stringResource(R.string.setting_contact_us),
style = FestabookTypography.titleMedium,
)

Icon(
painter = painterResource(R.drawable.ic_arrow_forward_right),
contentDescription = stringResource(R.string.move),
tint = Color.Unspecified,
)
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

해당 메뉴 아이템을 하나의 컴포저블로 구성할 수 있을 거 같아요~

Comment on lines +179 to +186
)
}
}
}

@Composable
private fun AppInfoContent(
appVersion: String,
Copy link
Contributor

Choose a reason for hiding this comment

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

스위치도 앱 내에서 동일하게 사용하기 위해 하나의 공통 컴포저블로 만드는 것은 어떨까요?

Copy link
Contributor

@parkjiminnnn parkjiminnnn left a comment

Choose a reason for hiding this comment

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

마이그레이션 제가 한다고 큰소리 쳤는데 못해서 미안합니다 🙏
고생 많으셨어요 밀러 알림은 잘 오는 듯 해요!
코멘트 몇 개만 확인 부탁드려요~

Comment on lines +14 to +27
@Composable
fun <T> ObserveAsEvents(
flow: Flow<T>,
onEvent: suspend (T) -> Unit,
) {
val lifecycleOwner = LocalLifecycleOwner.current
LaunchedEffect(flow, lifecycleOwner.lifecycle) {
lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
withContext(Dispatchers.Main.immediate) {
flow.collect(onEvent)
}
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

좋은데요?

} else {
Timber.d("Notification permission denied")
showNotificationDeniedSnackbar(binding.root, requireContext())
showNotificationDeniedSnackbar(view!!, requireContext())
Copy link
Contributor

Choose a reason for hiding this comment

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

에디터가 requireView()를 추천하길래 알아봤더니 view!!requireView() 둘 다 뷰가 생성된 시점에 호출하면 둘 다 view객체를 반환한다고하네요.
하지만 예외 발생 측면에서 봤을 때 NPE 보다는 IllegalStateException으로 생명주기 위반을 좀 더 명확하게 알 수 있는 점에서 requireView()는 어떠신가용?

Comment on lines +28 to +30
MutableSharedFlow(
replay = 1,
)
Copy link
Contributor

Choose a reason for hiding this comment

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

저희 fragment가 싱글톤이고 화면 회전도 없어서 불필요한 이벤트 발생은 없을 것 같네요 😮

Comment on lines +52 to +53
val success: LiveData<Unit> = _success.asLiveData()
val successFlow = _success.asSharedFlow()
Copy link
Contributor

Choose a reason for hiding this comment

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

요친구만 LiveData로 하신 이유를 알 수 있을까요?

appVersion = appVersion,
onPolicyClick = onPolicyClick,
onContactUsClick = onContactUsClick,
modifier = Modifier.background(color = FestabookColor.white),
Copy link
Contributor

Choose a reason for hiding this comment

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

background를 따로 주신 이유가 궁금합니다.

Copy link
Contributor

Choose a reason for hiding this comment

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

테스트 코드에 전체적으로 assertAll이 있으면 좋을 것 같아요!

Copy link
Contributor

Choose a reason for hiding this comment

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

@get:Rule
    val instantTaskExecutorRule = InstantTaskExecutorRule()

라이브데이터가 사라져서 요친구도 이제 없어도 될 듯해요!

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.

4 participants