Skip to content

SplashActivity Handler 제거, SplashScreen API + ViewModel + 테스트로 교체#400

Merged
unam98 merged 8 commits into
developfrom
feature/splash-compose-migration
Jun 26, 2026
Merged

SplashActivity Handler 제거, SplashScreen API + ViewModel + 테스트로 교체#400
unam98 merged 8 commits into
developfrom
feature/splash-compose-migration

Conversation

@unam98

@unam98 unam98 commented Jun 26, 2026

Copy link
Copy Markdown
Collaborator

작업 배경

  • Runnect-Android Compose+TDD 전환 전략의 일환 — SplashActivity의 레거시Handler 방식을 제거하고 구조적 안전망을 추가
  • Android 12+에서 강제 적용되는 SplashScreen API로 교체해 향후 호환성 확보

변경 사항

영역 내용
SplashViewModel (신규) Handler 제거 → viewModelScope + delay(1000ms), isReady: StateFlow, navigateEvent: SharedFlow
SplashActivity @AndroidEntryPoint 추가, installSplashScreen() + setContent { SplashScreen() } 으로 Compose 전체화면 렌더링, lifecycleScope로 navigate 이벤트 수신
SplashScreen (신규) splash.webp를 Compose Image로 전체화면 렌더링
SplashTheme windowBackground 방식 제거 → Theme.SplashScreen 부모, windowSplashScreenBackground = M1, postSplashScreenTheme 연결
activity_splash.xml 빈 레이아웃 삭제
libs.versions.toml / build.gradle androidx.core:core-splashscreen:1.0.1 추가

영향 범위

  • SplashActivity 단일 화면에 한정
  • 런타임 영향: 없음 — 1초 후 LoginActivity로 이동하는 동작 동일, 시각적 변화 Before/After 영상으로 확인

검증 매트릭스

영향 범위 테스트 코드
splash 화면 1초 유지 (isReady=falsesetKeepOnScreenCondition 유지) 초기 상태에서 isReady는 false다
1초 경과 전에는 isReady가 false다
1초 후 splash 해제 (isReady=true) 1초 경과 후 isReady가 true가 된다
LoginActivity 네비게이션 트리거 1초 후 navigateEvent가 emit된다

Before / After 영상

Before (Handler + windowBackground) After (SplashScreen API + ViewModel + Compose)
splash_before.mp4
splash_after_new.mp4

Test Plan

  • SplashViewModelTest 4개 통과

🤖 Generated with Claude Code

- androidx.core:core-splashscreen 도입, windowBackground 방식 제거
- SplashViewModel: Handler → viewModelScope + delay, isReady/navigateEvent StateFlow/SharedFlow
- SplashActivity: @androidentrypoint + viewModels(), setKeepOnScreenCondition으로 splash 유지
- SplashViewModelTest: 4개 케이스 (초기값, 1초 전, 1초 후 isReady, navigateEvent emit)
- activity_splash.xml 삭제 (빈 레이아웃)
@coderabbitai

coderabbitai Bot commented Jun 26, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@unam98, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 3 minutes and 51 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 22ca165c-6309-40ff-aaf7-03cfd396f57f

📥 Commits

Reviewing files that changed from the base of the PR and between 96fc9cf and 47689c8.

📒 Files selected for processing (7)
  • .github/scripts/run_android_tests.sh
  • .github/workflows/CI.yml
  • app/src/androidTest/java/com/runnect/runnect/presentation/splash/SplashScreenTest.kt
  • app/src/main/java/com/runnect/runnect/presentation/splash/SplashActivity.kt
  • app/src/main/java/com/runnect/runnect/presentation/splash/SplashScreen.kt
  • app/src/main/java/com/runnect/runnect/presentation/splash/SplashViewModel.kt
  • app/src/main/res/values/themes.xml
📝 Walkthrough

Walkthrough

The PR adds AndroidX splash-screen support, introduces a SplashViewModel that delays readiness and emits a navigation event, and updates SplashActivity to keep the splash visible until that event then open LoginActivity.

Changes

Splash screen startup flow

Layer / File(s) Summary
Splash screen wiring
gradle/libs.versions.toml, app/build.gradle, app/src/main/res/values/themes.xml
Adds the core-splashscreen version and library aliases, declares the app dependency, and switches SplashTheme to Theme.SplashScreen with splash-specific items.
SplashViewModel flow
app/src/main/java/com/runnect/runnect/presentation/splash/SplashViewModel.kt, app/src/test/java/com/runnect/runnect/presentation/splash/SplashViewModelTest.kt
Adds SplashViewModel with isReady and navigateEvent signals after SPLASH_DELAY, and tests the timing and flow emissions with a test dispatcher.
Splash activity navigation
app/src/main/java/com/runnect/runnect/presentation/splash/SplashActivity.kt
Installs the Android splash screen, keeps it on screen while isReady is false, and collects navigateEvent to start LoginActivity and finish the splash activity.

Sequence Diagram(s)

sequenceDiagram
  participant SplashActivity
  participant "AndroidX SplashScreen" as AndroidXSplashScreen
  participant SplashViewModel
  participant LoginActivity

  SplashActivity->>AndroidXSplashScreen: installSplashScreen()
  SplashActivity->>SplashViewModel: read isReady in setKeepOnScreenCondition()
  SplashViewModel-->>SplashActivity: isReady becomes true after SPLASH_DELAY
  SplashActivity->>SplashViewModel: collect navigateEvent in repeatOnLifecycle(STARTED)
  SplashViewModel-->>SplashActivity: emit navigateEvent
  SplashActivity->>LoginActivity: startActivity(Intent.FLAG_ACTIVITY_NO_ANIMATION)
  SplashActivity->>SplashActivity: finish()
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

I hopped through dawn with ears held high,
The splash screen paused, then waved goodbye.
isReady blinked, the timer ran,
I bounced to login—best rabbit plan!
🐇✨

🚥 Pre-merge checks | ✅ 4 | ❌ 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 (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: replacing the Handler-based splash flow with SplashScreen API, ViewModel, and tests.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/splash-compose-migration

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.

@unam98 unam98 self-assigned this Jun 26, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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/runnect/runnect/presentation/splash/SplashViewModel.kt`:
- Around line 22-29: The splash navigation event can be lost when
`SplashActivity` stops collecting during rotation/backgrounding because
`SplashViewModel` uses `_navigateEvent` with no replay. Update
`SplashViewModel`’s `MutableSharedFlow` for `navigateEvent` to retain the
emission for late collectors, or add a fallback in `SplashActivity` when
collecting `navigateEvent` so navigation happens immediately if `isReady` is
already true. Keep the fix centered on the `init` block and `navigateEvent` flow
setup so resuming after lifecycle transitions cannot leave the splash screen
stuck.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: d0fd9b8c-aa1d-4afb-83ff-17bd7e41ba56

📥 Commits

Reviewing files that changed from the base of the PR and between 8a0da8d and 96fc9cf.

📒 Files selected for processing (7)
  • app/build.gradle
  • app/src/main/java/com/runnect/runnect/presentation/splash/SplashActivity.kt
  • app/src/main/java/com/runnect/runnect/presentation/splash/SplashViewModel.kt
  • app/src/main/res/layout/activity_splash.xml
  • app/src/main/res/values/themes.xml
  • app/src/test/java/com/runnect/runnect/presentation/splash/SplashViewModelTest.kt
  • gradle/libs.versions.toml
💤 Files with no reviewable changes (1)
  • app/src/main/res/layout/activity_splash.xml

Comment thread app/src/main/java/com/runnect/runnect/presentation/splash/SplashViewModel.kt Outdated
unam98 added 2 commits June 26, 2026 22:55
- windowSplashScreenAnimatedIcon 제거 (전체화면 이미지를 아이콘 슬롯에 잘못 사용한 것 수정)
- SplashActivity에 setContent { SplashScreen() } 추가 — splash.webp를 Compose Image로 렌더링
- setKeepOnScreenCondition { false }로 시스템 스플래시 즉시 해제 후 Compose 화면 표시
- windowSplashScreenBackground = M1(보라)로 시스템 스플래시 → Compose 전환 시 색상 연속성 유지
finish()
}, DELAY_TIME)
}
setContent {

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

setContent { } — Compose 진입점

Activity에서 XML setContentView(R.layout.xxx)를 대체하는 Compose 진입점.
이 블록 안에 넘긴 @Composable 함수가 화면 전체를 그린다.

// 기존
setContentView(R.layout.activity_splash)

// Compose
setContent {
    SplashScreen()
}

핵심 개념:

  • setContent 내부에서 Compose 런타임이 초기화되고, @Composable 함수 호출 트리가 UI 트리로 변환됨
  • ComponentActivity.setContentandroidx.activity.compose 라이브러리가 제공 — AppCompatActivity를 상속해도 사용 가능

공식 문서 — setContent

import androidx.compose.ui.res.painterResource
import com.runnect.runnect.R

@Composable

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

@Composable — Compose 함수 선언

이 어노테이션이 붙은 함수는 UI를 **기술(describe)**하는 단위.
기존 XML inflate + View 참조 방식 대신 함수 호출 트리가 UI 구조가 된다.

// 기존: XML을 inflate하고 View에 데이터 바인딩
val binding = ActivitySplashBinding.inflate(layoutInflater)

// Compose: 함수 = UI
@Composable
fun SplashScreen() { ... }

핵심 개념:

  • @Composable 함수는 일반 함수처럼 호출하지만, Compose 런타임이 호출 트리를 추적해 상태 변화 시 변경된 부분만 다시 그림(recomposition)
  • 반환값이 없음 — 함수가 UI를 직접 '그리는' 것이 아니라 런타임에 UI 트리를 '기술'하는 것
  • @Composable 함수는 반드시 다른 @Composable 스코프에서 호출해야 함

공식 문서 — Composable functions

Image(
painter = painterResource(id = R.drawable.splash),
contentDescription = null,
modifier = Modifier.fillMaxSize(),

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Modifier — Compose의 레이아웃/스타일 체이닝

기존 View XML의 layout_width, layout_height, padding, background 등 속성을 코드로 체이닝하는 방식.

<!-- 기존 XML -->
<ImageView
    android:layout_width="match_parent"
    android:layout_height="match_parent" />
// Compose
Image(
    modifier = Modifier.fillMaxSize()  // match_parent × match_parent
)

핵심 개념:

  • Modifier순서가 중요함fillMaxSize().padding(16.dp)padding(16.dp).fillMaxSize()는 다른 결과
  • 각 Composable의 modifier 파라미터로 전달 — 컴포넌트 자체 크기·위치·동작을 외부에서 제어하는 패턴
  • fillMaxSize() = match_parent / wrapContentSize() = wrap_content / size(100.dp) = 고정 크기

공식 문서 — Modifiers

painter = painterResource(id = R.drawable.splash),
contentDescription = null,
modifier = Modifier.fillMaxSize(),
contentScale = ContentScale.FillBounds,

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

ContentScale — Compose Image의 스케일링 방식

기존 ImageViewandroid:scaleType 속성에 대응.

ContentScale ImageView scaleType 동작
FillBounds fitXY 비율 무시하고 컨테이너 꽉 채움
Crop centerCrop 비율 유지, 짧은 축 기준으로 크롭
Fit fitCenter 비율 유지, 긴 축 기준으로 맞춤
FillWidth fitStart(유사) 너비 기준으로 채움

여기서 FillBounds를 선택한 이유: splash.webp가 정확히 기기 화면 비율(1440×3200)로 제작된 전체화면 이미지라 비율 왜곡이 발생해도 원본 windowBackground 렌더링 방식과 동일한 결과를 냄.

공식 문서 — ContentScale

unam98 added 5 commits June 26, 2026 23:21
- 변경된 .kt 파일의 패키지를 추출해 --tests 필터 구성
- PR: 영향 범위 패키지 테스트만 실행 (unit + instrumented 동일 적용)
- push to develop: 전체 테스트 실행 (안전망 유지)
--tests는 JVM 전용 옵션으로 connectedDebugAndroidTest에선 지원 안 됨.
-Pandroid.testInstrumentationRunnerArguments.package 로 교체.
@unam98 unam98 merged commit 6139d87 into develop Jun 26, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant