Decompose
一言で表すと
Kotlin/MPP向けのlifecycle-awareなビジネスロジックのコンポーネントとルーティングを提供するライブラリ
概要
Decomposeで画面遷移をするには、router.push を使う
前の画面に戻るときはrouter.pop を使う
code:kotlin
class TodoRootComponent(
componentContext: ComponentContext
) : NavigationComponent, ComponentContext by componentContext {
private val router = router<Navigation, NavigationComponent>(
initialConfiguration = Navigation.List,
handleBackButton = true,
childFactory = ::childFactory
)
val routerState = router.state
private fun childFactory(
navigation: Navigation,
componentContext: ComponentContext
): NavigationComponent {
return when (navigation) {
is Navigation.List -> TodoListComponent(
componentContext = componentContext,
navigateToDetail = {
router.push(Navigation.Detail(it))
},
navigateToCreate = {
router.push(Navigation.Create)
}
)
is Navigation.Detail -> TodoDetailComponent(
todoId = navigation.todoId,
componentContext = componentContext,
backToList = router::pop // Mori Atsushi.icon 好みが分かれる書き方だ
)
is Navigation.Create -> TodoCreateComponent(
componentContext = componentContext,
backToList = router::pop
)
}
}
}
pushとpopの実装はこうなっている
code:kotlin
fun <C : Any> Navigator<C>.push(configuration: C) {
navigate { it + configuration }
}
fun <C : Any> Navigator<C>.pop() {
navigate { it.dropLast(1) }
}
navigateの実装
code:kotlin
override fun navigate(transformer: (stack: List<C>) -> List<C>) {
queue.addLast(transformer)
if (queue.size == 1) {
drainQueue()
}
}
private fun drainQueue() {
do {
val newStack = navigator.navigate(oldStack = stackHolder.stack, transformer = queue.first())
stackHolder.stack = newStack
state.value = newStack.toState()
queue.removeFirst()
} while (queue.isNotEmpty())
}
つまりnavigateはnavigationのページごとのConfig(引数みたいなもん)のリストの状態の遷移をさせる関数をキューに追加している
Mori Atsushi.icon なんで一度キューイングするんだろう?
drainQueueではStackNavigatorのnavigateを呼んでいる
code:kotlin
internal class StackNavigatorImpl<C : Any, T : Any>(
private val routerEntryFactory: RouterEntryFactory<C, T>
) : StackNavigator<C, T> {
override fun navigate(oldStack: RouterStack<C, T>, transformer: (stack: List<C>) -> List<C>): RouterStack<C, T> {
val newConfigurationStack = transformer((oldStack.backStack + oldStack.active).map(RouterEntry<C, *>::configuration))
check(newConfigurationStack.isNotEmpty()) { "Configuration stack can not be empty" }
val newActiveConfiguration = newConfigurationStack.last()
val newActiveEntry: RouterEntry.Created<C, *>
if (newActiveConfiguration === oldStack.active.configuration) {
newActiveEntry = oldStack.active
} else {
newActiveEntry =
when (val entry = oldStack.backStack.find { it.configuration === newActiveConfiguration }) {
is RouterEntry.Created -> entry.copy(savedState = null)
is RouterEntry.Destroyed -> routerEntryFactory(entry.configuration, entry.savedState)
null -> routerEntryFactory(newActiveConfiguration)
}
oldStack.active.lifecycleRegistry.pause()
newActiveEntry.lifecycleRegistry.resume()
oldStack.active.lifecycleRegistry.stop()
if (newConfigurationStack.none { it === oldStack.active.configuration }) {
oldStack.active.instanceKeeperDispatcher.destroy()
oldStack.active.lifecycleRegistry.destroy()
}
}
val newBackStackEntries =
newConfigurationStack.dropLast(1).map { configuration ->
if (oldStack.active.configuration === configuration) {
oldStack.active.copy(savedState = oldStack.active.stateKeeperDispatcher.save())
} else {
oldStack
.backStack
.find { it.configuration === configuration }
?: RouterEntry.Destroyed(configuration = configuration)
}
}
oldStack
.backStack
.filter { entry -> newConfigurationStack.none { it === entry.configuration } }
.destroy()
return RouterStack(active = newActiveEntry, backStack = newBackStackEntries)
}
}
気になるポイント
コメント
Go.iconルーティングを考えるときにComponentというワードがしっくりこなくて理解が進まなかった...
Mori Atsushi.icon Freezeちゃんと調べる
Mori Atsushi.icon AndroidのLifecycleをそのまま模倣するのが良いのかな?