Closed6

androidx.navigationのコードリーディング

Mori AtsushiMori Atsushi

KMP対応のcompose navigationを実現したい。
androidxのnavigationを一旦読んで見る。
特にNavGraph scopeのViewModelの仕組みを知りたい。

Mori AtsushiMori Atsushi

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 { }
}
Mori AtsushiMori Atsushi

バックスタック管理を見てみる

NavControllerNavBackStackEntryを持っている。

public open class NavController(
    public val context: Context
) {
    /** ... */
    public open val backQueue: ArrayDeque<NavBackStackEntry> = ArrayDeque()
    /** ... */
}

NavBackStackEntryLifecycleOwner, 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 {
    /** ... */
}
Mori AtsushiMori Atsushi

ViewModelのclearの部分を見る。
ナビゲーションの処理終了時にNavControllermarkTransitionComplete が呼ばれ、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
            // 
Mori AtsushiMori Atsushi

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)
    }
}
このスクラップは2021/11/17にクローズされました