Skip to content

Commit 96fc9cf

Browse files
committed
feat: SplashActivity Handler 제거, SplashScreen API + ViewModel + 테스트로 교체
- 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 삭제 (빈 레이아웃)
1 parent 8a0da8d commit 96fc9cf

7 files changed

Lines changed: 126 additions & 34 deletions

File tree

app/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ dependencies {
137137
implementation libs.androidx.swiperefreshlayout
138138
implementation libs.androidx.preference.ktx
139139
implementation libs.androidx.security.crypto.ktx
140+
implementation libs.androidx.splashscreen
140141

141142
// Compose
142143
implementation platform(libs.compose.bom)

app/src/main/java/com/runnect/runnect/presentation/splash/SplashActivity.kt

Lines changed: 22 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,35 +2,36 @@ package com.runnect.runnect.presentation.splash
22

33
import android.content.Intent
44
import android.os.Bundle
5-
import android.os.Handler
6-
import android.os.Looper
75
import androidx.activity.enableEdgeToEdge
6+
import androidx.activity.viewModels
87
import androidx.appcompat.app.AppCompatActivity
9-
import com.runnect.runnect.R
8+
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
9+
import androidx.lifecycle.Lifecycle
10+
import androidx.lifecycle.lifecycleScope
11+
import androidx.lifecycle.repeatOnLifecycle
1012
import com.runnect.runnect.presentation.login.LoginActivity
11-
import com.runnect.runnect.presentation.scheme.SchemeActivity
12-
13+
import dagger.hilt.android.AndroidEntryPoint
14+
import kotlinx.coroutines.launch
1315

16+
@AndroidEntryPoint
1417
class SplashActivity : AppCompatActivity() {
15-
private val handler = Handler(Looper.getMainLooper())
18+
private val viewModel: SplashViewModel by viewModels()
1619

1720
override fun onCreate(savedInstanceState: Bundle?) {
18-
enableEdgeToEdge()
21+
val splashScreen = installSplashScreen()
22+
splashScreen.setKeepOnScreenCondition { !viewModel.isReady.value }
1923
super.onCreate(savedInstanceState)
20-
setContentView(R.layout.activity_splash)
21-
navigateToLoginScreen()
22-
}
23-
24-
private fun navigateToLoginScreen() {
25-
handler.postDelayed({
26-
val intent = Intent(this, LoginActivity::class.java)
27-
intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION)
28-
startActivity(intent)
29-
finish()
30-
}, DELAY_TIME)
31-
}
24+
enableEdgeToEdge()
3225

33-
companion object {
34-
private const val DELAY_TIME = 1000L
26+
lifecycleScope.launch {
27+
repeatOnLifecycle(Lifecycle.State.STARTED) {
28+
viewModel.navigateEvent.collect {
29+
startActivity(Intent(this@SplashActivity, LoginActivity::class.java).apply {
30+
addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION)
31+
})
32+
finish()
33+
}
34+
}
35+
}
3536
}
3637
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package com.runnect.runnect.presentation.splash
2+
3+
import androidx.lifecycle.ViewModel
4+
import androidx.lifecycle.viewModelScope
5+
import dagger.hilt.android.lifecycle.HiltViewModel
6+
import kotlinx.coroutines.delay
7+
import kotlinx.coroutines.flow.MutableSharedFlow
8+
import kotlinx.coroutines.flow.MutableStateFlow
9+
import kotlinx.coroutines.flow.SharedFlow
10+
import kotlinx.coroutines.flow.StateFlow
11+
import kotlinx.coroutines.flow.asSharedFlow
12+
import kotlinx.coroutines.flow.asStateFlow
13+
import kotlinx.coroutines.launch
14+
import javax.inject.Inject
15+
16+
@HiltViewModel
17+
class SplashViewModel @Inject constructor() : ViewModel() {
18+
19+
private val _isReady = MutableStateFlow(false)
20+
val isReady: StateFlow<Boolean> = _isReady.asStateFlow()
21+
22+
private val _navigateEvent = MutableSharedFlow<Unit>(extraBufferCapacity = 1)
23+
val navigateEvent: SharedFlow<Unit> = _navigateEvent.asSharedFlow()
24+
25+
init {
26+
viewModelScope.launch {
27+
delay(SPLASH_DELAY)
28+
_isReady.value = true
29+
_navigateEvent.emit(Unit)
30+
}
31+
}
32+
33+
companion object {
34+
const val SPLASH_DELAY = 1000L
35+
}
36+
}

app/src/main/res/layout/activity_splash.xml

Lines changed: 0 additions & 9 deletions
This file was deleted.

app/src/main/res/values/themes.xml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,10 @@
2929
<item name="editTextStyle">@style/Widget.Runnect.EditText</item>
3030
</style>
3131

32-
<style name="SplashTheme" parent="Theme.AppCompat.Light.DarkActionBar">
33-
<item name="windowNoTitle">true</item>
34-
<item name="windowActionBar">false</item>
35-
<item name="android:windowBackground">@drawable/splash</item>
32+
<style name="SplashTheme" parent="Theme.SplashScreen">
33+
<item name="windowSplashScreenBackground">@color/white</item>
34+
<item name="windowSplashScreenAnimatedIcon">@drawable/splash</item>
35+
<item name="postSplashScreenTheme">@style/Theme.Runnect</item>
3636
</style>
3737

3838
<!-- 신고하기 BottomSheet 테마 -->
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package com.runnect.runnect.presentation.splash
2+
3+
import app.cash.turbine.test
4+
import kotlinx.coroutines.Dispatchers
5+
import kotlinx.coroutines.ExperimentalCoroutinesApi
6+
import kotlinx.coroutines.test.StandardTestDispatcher
7+
import kotlinx.coroutines.test.advanceTimeBy
8+
import kotlinx.coroutines.test.advanceUntilIdle
9+
import kotlinx.coroutines.test.resetMain
10+
import kotlinx.coroutines.test.runTest
11+
import kotlinx.coroutines.test.setMain
12+
import org.junit.After
13+
import org.junit.Assert.assertFalse
14+
import org.junit.Assert.assertTrue
15+
import org.junit.Before
16+
import org.junit.Test
17+
18+
@OptIn(ExperimentalCoroutinesApi::class)
19+
class SplashViewModelTest {
20+
private val testDispatcher = StandardTestDispatcher()
21+
22+
@Before
23+
fun setUp() {
24+
Dispatchers.setMain(testDispatcher)
25+
}
26+
27+
@After
28+
fun tearDown() {
29+
Dispatchers.resetMain()
30+
}
31+
32+
@Test
33+
fun `초기 상태에서 isReady는 false다`() {
34+
val viewModel = SplashViewModel()
35+
assertFalse(viewModel.isReady.value)
36+
}
37+
38+
@Test
39+
fun `1초 경과 전에는 isReady가 false다`() = runTest(testDispatcher) {
40+
val viewModel = SplashViewModel()
41+
advanceTimeBy(SplashViewModel.SPLASH_DELAY - 1)
42+
assertFalse(viewModel.isReady.value)
43+
}
44+
45+
@Test
46+
fun `1초 경과 후 isReady가 true가 된다`() = runTest(testDispatcher) {
47+
val viewModel = SplashViewModel()
48+
advanceUntilIdle()
49+
assertTrue(viewModel.isReady.value)
50+
}
51+
52+
@Test
53+
fun `1초 후 navigateEvent가 emit된다`() = runTest(testDispatcher) {
54+
val viewModel = SplashViewModel()
55+
viewModel.navigateEvent.test {
56+
advanceTimeBy(SplashViewModel.SPLASH_DELAY)
57+
awaitItem()
58+
cancelAndIgnoreRemainingEvents()
59+
}
60+
}
61+
}

gradle/libs.versions.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ viewpager2 = "1.1.0"
2626
swiperefreshlayout = "1.2.0"
2727
preference-ktx = "1.2.1"
2828
security-crypto = "1.1.0"
29+
splashscreen = "1.0.1"
2930

3031
# Compose
3132
compose-bom = "2026.03.01"
@@ -106,6 +107,7 @@ androidx-viewpager2 = { group = "androidx.viewpager2", name = "viewpager2", vers
106107
androidx-swiperefreshlayout = { group = "androidx.swiperefreshlayout", name = "swiperefreshlayout", version.ref = "swiperefreshlayout" }
107108
androidx-preference-ktx = { group = "androidx.preference", name = "preference-ktx", version.ref = "preference-ktx" }
108109
androidx-security-crypto-ktx = { group = "androidx.security", name = "security-crypto-ktx", version.ref = "security-crypto" }
110+
androidx-splashscreen = { group = "androidx.core", name = "core-splashscreen", version.ref = "splashscreen" }
109111

110112
# Compose
111113
compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "compose-bom" }

0 commit comments

Comments
 (0)