| name | implementing-navigation |
| description | Use when adding or changing feature navigation, NavKey, navigator extensions, EntryProvider, assisted ViewModel creation, or result bus handling in this Android repository. |
Implementing Navigation
feature navigation์ API ๋ชจ๋์ NavKey/navigator extension๊ณผ impl ๋ชจ๋์ EntryProvider๋ฅผ ํจ๊ป ๋ง์ถ๋ค.
์์
๋ฒ์
feature/<feature-name>/api/src/main/.../*NavKey.kt
- ํ์ํ ๊ฒฝ์ฐ
feature/<feature-name>/api/src/main/.../*Result.kt
- ํ์ํ ๊ฒฝ์ฐ
feature/<feature-name>/api/src/main/.../*Action.kt
feature/<feature-name>/impl/src/main/.../navigation/*EntryProvider.kt (์์ธ: photo-upload๋ง di/PhotoUploadEntryProvider.kt. ์ ๊ท ์์
์ navigation/ ํจํค์ง์ <Feature>EntryProviderModule ๋ค์ด๋ฐ์ ๋ฐ๋ฅธ๋ค)
- runtime nav argument๋ฅผ ๋ฐ๋ ํ๋ฉด์
ViewModel factory ์์ฑ๋ถ
๊ตฌํ ํ๋ฆ
- API ๋ชจ๋์
sealed interface <Feature>NavKey : NavKey๋ฅผ ์ ์ํ๋ค.
- ํ๋ฉด key๋
@Serializable data object ๋๋ @Serializable data class๋ก ๋๋ค.
MainNavigator ๋๋ AuthNavigator extension์ผ๋ก ์ด๋ ํจ์๋ฅผ ์ ์ํ๋ค.
- impl ๋ชจ๋์
EntryProvider์ entry<<Feature>NavKey.X>๋ฅผ ๋ฑ๋กํ๋ค.
- runtime nav argument๊ฐ ํ์ํ ViewModel์ entry ์์์ assisted factory๋ก ์์ฑํ๋ค.
- root flow ์ด๋์ด ์๋ entry๋
RootNavigator๋ฅผ ํจ๊ป ๋ฐ๋๋ค.
- ๋ค๋ก๊ฐ๊ธฐ๋ก ๋์์ค๋ฉด ์ ๋๋ ํ๋ฉด์ entry ๋๋ค์
key๋ฅผ ๋ฐ์ ์ด๋ ์ navigator.remove(key)๋ก ์คํ์์ ์ ๊ฑฐํ๋ค.
- ๋ค๋ฅธ ํ๋ฉด ๋ณ๊ฒฝ ๊ฒฐ๊ณผ๋ก ํ์ฌ ํ๋ฉด์ ๊ฐฑ์ ํ๋ entry๋
ResultEffect๋ฅผ ๋๋ค.
NavKey
sealed interface FeatureNavKey : NavKey {
@Serializable
data object FeatureMain : FeatureNavKey
@Serializable
data class FeatureDetail(
val id: Long,
val title: String,
) : FeatureNavKey
}
fun MainNavigator.navigateToFeature() {
navigate(FeatureNavKey.FeatureMain)
}
fun MainNavigator.navigateToFeatureDetail(id: Long, title: String) {
navigate(FeatureNavKey.FeatureDetail(id = id, title = title))
}
Main EntryProvider
@Module
@InstallIn(ActivityRetainedComponent::class)
object FeatureEntryProviderModule {
@IntoSet
@Provides
fun provideFeatureEntryBuilder(
navigator: MainNavigator,
): EntryProviderInstaller = {
featureEntry(navigator)
}
}
private fun EntryProviderScope<NavKey>.featureEntry(navigator: MainNavigator) {
entry<FeatureNavKey.FeatureMain> {
FeatureRoute(...)
}
}
Auth EntryProvider
typealias AuthEntryProviderInstaller = EntryProviderScope<NavKey>.() -> Unit
fun authEntryProvider(
rootNavigator: RootNavigator,
authNavigator: AuthNavigator,
): AuthEntryProviderInstaller = {
authEntry(rootNavigator, authNavigator)
}
Main์ @IntoSet์ผ๋ก ๋ฑ๋ก๋ EntryProviderInstaller๋ฅผ MainActivity๊ฐ set์ผ๋ก ์ฃผ์
๋ฐ์ ์๋ ์กฐ๋ฆฝํ๋ค. Auth๋ DI set์ด ์๋๋ผ MainActivity๊ฐ authEntryProvider(...)๋ฅผ ์ง์ ํธ์ถํด ์กฐ๋ฆฝํ๋ฏ๋ก, ์ ๊ท auth ํ๋ฉด์ AuthEntryProvider.kt์ authEntry์ entry๋ง ์ถ๊ฐํ๋ฉด ๋๊ณ app ๋ชจ๋ ์์ ์ ๋ถํ์ํ๋ค.
Assisted ViewModel
@HiltViewModel(assistedFactory = FeatureDetailViewModel.Factory::class)
internal class FeatureDetailViewModel @AssistedInject constructor(
@Assisted private val key: FeatureNavKey.FeatureDetail,
) : ViewModel() {
@AssistedFactory
interface Factory {
fun create(key: FeatureNavKey.FeatureDetail): FeatureDetailViewModel
}
}
assisted ViewModel์ entry์์ ์์ฑํด Route์ ํ์ ํ๋ผ๋ฏธํฐ๋ก ์ ๋ฌํ๋ค. viewModel: XxxViewModel = hiltViewModel() ๊ธฐ๋ณธ๊ฐ์ runtime nav argument๊ฐ ์๋ ํ๋ฉด์ Route์๋ง ๋๋ค.
@Composable
internal fun FeatureDetailRoute(
viewModel: FeatureDetailViewModel,
navigateBack: () -> Unit,
) {
...
}
entry<FeatureNavKey.FeatureDetail> { key ->
val viewModel = hiltViewModel<FeatureDetailViewModel, FeatureDetailViewModel.Factory>(
creationCallback = { factory -> factory.create(key) },
)
FeatureDetailRoute(
viewModel = viewModel,
navigateBack = navigator::goBack,
)
}
๊ณต์ ViewModel
์ฌ๋ฌ ํ๋ฉด์ด ํ๋์ ViewModel์ ๊ณต์ ํ๋ ๊ฒฝ์ฐ ๋ถ๋ชจ entry์ clazzContentKey๋ฅผ, ์์ entry์ HiltSharedViewModelStoreNavEntryDecorator.parent๋ฅผ ์ง์ ํ๋ค. ์ค์ ์์๋ MyPageEntryProvider.kt ์ฐธ๊ณ .
entry<MyPageNavKey.MyPage>(
clazzContentKey = { key -> key.toString() },
) { ... }
entry<MyPageNavKey.Profile>(
metadata = HiltSharedViewModelStoreNavEntryDecorator.parent(
MyPageNavKey.MyPage.toString(),
),
) { ... }
Result ํต์
result๋ ๋ค๋ฅธ ํ๋ฉด์์ ๋ณ๊ฒฝ๋ ๋ฐ์ดํฐ ๋๋ฌธ์ ํ์ฌ ํ๋ฉด์ ๊ฐฑ์ ํด์ผ ํ๋ ๊ฒฝ์ฐ์ ์ด๋ค.
consumer๋ result๋ฅผ ๋ฐ์ ํ์ฌ ํ๋ฉด์ intent๋ฅผ ์คํํ๋ ์ชฝ์ด๋ค.
relay๋ ํ์ ํ๋ฉด์ result๋ฅผ ํ์ฌ flow์ result๋ก ๋ฐ๊ฟ ๋ค์ ๋ณด๋ด๋ ์ชฝ์ด๋ค.
relay ํ๋ฆ: ํ์ ํ๋ฉด result -> ํ์ฌ flow EntryProvider -> ์์ ํ๋ฉด result
payload๊ฐ ํ์ํ result๋ data class ๋๋ sealed interface๋ก ์ ์ํ๊ณ ResultEffect<T>(resultBus) { result -> ... } ๋๋ค ์ธ์๋ก ์๋นํ๋ค. relay ์ฌ๋ฐํ์ allowDuplicate = false๋ก ๊ฐ์ result์ ์ค๋ณต ๋ฐํ์ ๋ง๋๋ค.
val resultBus = LocalResultEventBus.current
ResultEffect<FeatureResult>(resultBus) {
viewModel.store.onIntent(FeatureIntent.RefreshFeature)
}
ResultEffect<ChildResult>(resultBus) {
resultBus.sendResult(result = ParentResult, allowDuplicate = false)
}