androidx.navigationのコードリーディング
KMP対応のcompose navigationを実現したい。
androidxのnavigationを一旦読んで見る。
特にNavGraph scopeのViewModelの仕組みを知りたい。
NavControllerViewModel
というものが ViewModelStore
を持っている。
/**
* NavControllerViewModel is the always up to date view of the NavController's
* non configuration state
*/
internal class NavControllerViewModel : ViewModel(), NavViewModelStoreProvider {
private val viewModelStores = mutableMapOf<String, ViewModelStore>()
fun clear(backStackEntryId: String) { }
override fun onCleared() { }
override fun getViewModelStore(backStackEntryId: String): ViewModelStore { }
}
至るところに気になるアノテーションがある。
publicだけど同じgradle group ID かつ artifact IDでないとIDEが警告を出してくれるよう。
navigation-framgnetやnavigation-composeからのみ見えてほしいnavigation-coreのものを表現してるみたい?
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
バックスタック管理を見てみる
NavController
がNavBackStackEntry
を持っている。
public open class NavController(
public val context: Context
) {
/** ... */
public open val backQueue: ArrayDeque<NavBackStackEntry> = ArrayDeque()
/** ... */
}
NavBackStackEntry
は LifecycleOwner
, ViewModelStoreOwner
, HasDefaultViewModelProviderFactory
, SavedStateRegistryOwner
をimplementしている。
IDはuuidを生成している。
public class NavBackStackEntry private constructor(
private val context: Context?,
public var destination: NavDestination,
public val arguments: Bundle? = null,
private val navControllerLifecycleOwner: LifecycleOwner? = null,
private val viewModelStoreProvider: NavViewModelStoreProvider? = null,
public val id: String = UUID.randomUUID().toString(),
private val savedState: Bundle? = null
) : LifecycleOwner,
ViewModelStoreOwner,
HasDefaultViewModelProviderFactory,
SavedStateRegistryOwner {
/** ... */
}
ViewModelのclearの部分を見る。
ナビゲーションの処理終了時にNavController
の markTransitionComplete
が呼ばれ、backQueueに含まれておらず、saveState
がtrueでなければviewModelがclearされている。
override fun markTransitionComplete(entry: NavBackStackEntry) {
val savedState = entrySavedState[entry] == true
super.markTransitionComplete(entry)
entrySavedState.remove(entry)
if (!backQueue.contains(entry)) {
unlinkChildFromParent(entry)
// If the entry is no longer part of the backStack, we need to manually move
// it to DESTROYED, and clear its view model
if (entry.lifecycle.currentState.isAtLeast(Lifecycle.State.CREATED)) {
entry.maxLifecycle = Lifecycle.State.DESTROYED
}
if (!savedState) {
viewModel?.clear(entry.id)
}
updateBackStackLifecycle()
_visibleEntries.tryEmit(populateVisibleEntries())
} else if (!this@NavControllerNavigatorState.isNavigating) {
updateBackStackLifecycle()
_visibleEntries.tryEmit(populateVisibleEntries())
}
// else, updateBackStackLifecycle() will be called after any ongoing navigate() call
//
ComposeでのViewModelStoreの配り方を見てみる
LocalViewModelStoreOwner
が定義されている。
デフォルトはViewTreeViewModelStoreOwner
から取得する。(FragmentかActivityが取れる)
public object LocalViewModelStoreOwner {
private val LocalViewModelStoreOwner =
compositionLocalOf<ViewModelStoreOwner?> { null }
public val current: ViewModelStoreOwner?
@Composable
get() = LocalViewModelStoreOwner.current
?: ViewTreeViewModelStoreOwner.get(LocalView.current)
public infix fun provides(viewModelStoreOwner: ViewModelStoreOwner):
ProvidedValue<ViewModelStoreOwner?> {
return LocalViewModelStoreOwner.provides(viewModelStoreOwner)
}
}
NavHost
で現在の NavBackStackEntry
をセットしている。
@Composable
public fun NavBackStackEntry.LocalOwnersProvider(
saveableStateHolder: SaveableStateHolder,
content: @Composable () -> Unit
) {
CompositionLocalProvider(
LocalViewModelStoreOwner provides this,
LocalLifecycleOwner provides this,
LocalSavedStateRegistryOwner provides this
) {
saveableStateHolder.SaveableStateProvider(content)
}
}