From 6f257bbec12de85897587866331e8a60296a3f12 Mon Sep 17 00:00:00 2001 From: Jay Ohms Date: Thu, 2 Jul 2026 09:24:04 -0400 Subject: [PATCH 1/8] Initial work to defer lazy-loaded NavigatorHost tabs until they're initially selected --- .../dev/hotwire/demo/main/MainActivity.kt | 6 +++- .../activities/HotwireActivityDelegate.kt | 31 +++++++++++++++++-- .../navigation/navigator/NavigatorHost.kt | 23 +++++++++++++- .../tabs/HotwireBottomNavigationController.kt | 18 ++++++++++- 4 files changed, 73 insertions(+), 5 deletions(-) diff --git a/demo/src/main/kotlin/dev/hotwire/demo/main/MainActivity.kt b/demo/src/main/kotlin/dev/hotwire/demo/main/MainActivity.kt index 5cc2d222..9bb26793 100644 --- a/demo/src/main/kotlin/dev/hotwire/demo/main/MainActivity.kt +++ b/demo/src/main/kotlin/dev/hotwire/demo/main/MainActivity.kt @@ -35,7 +35,11 @@ class MainActivity : HotwireActivity() { private fun initializeBottomTabs() { val bottomNavigationView = findViewById(R.id.bottom_nav) - bottomNavigationController = HotwireBottomNavigationController(this, bottomNavigationView) + bottomNavigationController = HotwireBottomNavigationController( + activity = this, + view = bottomNavigationView, + deferInitialTabLoad = true + ) bottomNavigationController.load(mainTabs, viewModel.selectedTabIndex) bottomNavigationController.setOnTabSelectedListener { index, _ -> viewModel.selectedTabIndex = index diff --git a/navigation-fragments/src/main/java/dev/hotwire/navigation/activities/HotwireActivityDelegate.kt b/navigation-fragments/src/main/java/dev/hotwire/navigation/activities/HotwireActivityDelegate.kt index 0bd3710c..c325da84 100644 --- a/navigation-fragments/src/main/java/dev/hotwire/navigation/activities/HotwireActivityDelegate.kt +++ b/navigation-fragments/src/main/java/dev/hotwire/navigation/activities/HotwireActivityDelegate.kt @@ -18,6 +18,16 @@ import dev.hotwire.navigation.observers.HotwireActivityObserver class HotwireActivityDelegate(val activity: HotwireActivity) { private val navigatorHosts = mutableMapOf() + /** + * The IDs of navigator hosts whose start destination should not be loaded + * until the host first becomes the current navigator (e.g. when its bottom + * tab is first selected). Hosts not in this set are loaded eagerly as soon + * as their view is created. Populated by + * [dev.hotwire.navigation.tabs.HotwireBottomNavigationController] when + * deferred tab loading is enabled. + */ + internal val deferredNavigatorHostIds = mutableSetOf() + private val onBackPressedCallback = object : OnBackPressedCallback(enabled = true) { override fun handleOnBackPressed() { currentNavigator?.pop() @@ -66,6 +76,10 @@ class HotwireActivityDelegate(val activity: HotwireActivity) { val navigatorHost = navigatorHosts[currentNavigatorHostId] if (navigatorHost != null) { + // Load the host's start destination on demand the first time it + // becomes the current navigator. This is a no-op if it's already + // been loaded. + navigatorHost.initControllerGraphIfNeeded() updateOnBackPressedCallback(navigatorHost) } } @@ -80,6 +94,13 @@ class HotwireActivityDelegate(val activity: HotwireActivity) { if (currentNavigatorHostId == host.id) { updateOnBackPressedCallback(host) } + + // Load the host's start destination unless it's a deferred tab host + // that isn't currently selected. Deferred hosts are loaded when they + // first become the current navigator (see setCurrentNavigator). + if (host.id !in deferredNavigatorHostIds || currentNavigatorHostId == host.id) { + host.initControllerGraphIfNeeded() + } } } @@ -106,16 +127,22 @@ class HotwireActivityDelegate(val activity: HotwireActivity) { /** * Resets the sessions associated with all registered navigator hosts. + * Hosts that haven't loaded their start destination yet are skipped. */ fun resetSessions() { - navigatorHosts.forEach { it.value.navigator.session.reset() } + navigatorHosts.values + .filter { it.isGraphInitialized } + .forEach { it.navigator.session.reset() } } /** * Resets all registered navigators via [Navigator.reset]. + * Hosts that haven't loaded their start destination yet are skipped. */ fun resetNavigators() { - navigatorHosts.forEach { it.value.navigator.reset() } + navigatorHosts.values + .filter { it.isGraphInitialized } + .forEach { it.navigator.reset() } } private fun listenToDestinationChanges(host: NavigatorHost) { diff --git a/navigation-fragments/src/main/java/dev/hotwire/navigation/navigator/NavigatorHost.kt b/navigation-fragments/src/main/java/dev/hotwire/navigation/navigator/NavigatorHost.kt index 25291b7b..5a6d82e4 100644 --- a/navigation-fragments/src/main/java/dev/hotwire/navigation/navigator/NavigatorHost.kt +++ b/navigation-fragments/src/main/java/dev/hotwire/navigation/navigator/NavigatorHost.kt @@ -22,6 +22,13 @@ open class NavigatorHost : NavHostFragment(), FragmentOnAttachListener { lateinit var navigator: Navigator private set + /** + * Whether the navigation graph has been built and the start destination + * loaded. See [initControllerGraphIfNeeded]. + */ + internal var isGraphInitialized = false + private set + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -29,7 +36,10 @@ open class NavigatorHost : NavHostFragment(), FragmentOnAttachListener { navigator = Navigator(this, configuration, activity) childFragmentManager.addFragmentOnAttachListener(this) - initControllerGraph() + // The graph (and therefore the start destination) is not built here. + // It's built when the host registers with the Activity delegate in + // onViewCreated, which allows the delegate to load the host eagerly + // (the default) or defer loading until its tab is first selected. } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { @@ -56,7 +66,18 @@ open class NavigatorHost : NavHostFragment(), FragmentOnAttachListener { return isAdded && !isDetached && childFragmentManager.primaryNavigationFragment != null } + /** + * Builds the navigation graph and loads the start destination if it hasn't + * been built already. This is idempotent, so it's safe to call whenever the + * host may need to become ready for navigation (e.g. when its tab is selected). + */ + internal fun initControllerGraphIfNeeded() { + if (isGraphInitialized) return + initControllerGraph() + } + internal fun initControllerGraph() { + isGraphInitialized = true ensureDeeplinkStartLocationValid() navController.apply { diff --git a/navigation-fragments/src/main/java/dev/hotwire/navigation/tabs/HotwireBottomNavigationController.kt b/navigation-fragments/src/main/java/dev/hotwire/navigation/tabs/HotwireBottomNavigationController.kt index faff460e..c6ef63ae 100644 --- a/navigation-fragments/src/main/java/dev/hotwire/navigation/tabs/HotwireBottomNavigationController.kt +++ b/navigation-fragments/src/main/java/dev/hotwire/navigation/tabs/HotwireBottomNavigationController.kt @@ -21,13 +21,19 @@ import dev.hotwire.navigation.navigator.presentationContext /** * A [BottomNavigationView] controller that manages multiple [HotwireBottomTab]s, each associated * with its own [NavigatorHost] instance in the Activity layout. + * + * @param deferInitialTabLoad When `false` (the default), every tab's [NavigatorHost] loads its + * start location as soon as its view is created, so all tabs load up front. When `true`, a tab's + * start location is not loaded until that tab is first selected, avoiding loading (and creating a + * web view visit for) every tab at once. The initially selected tab still loads immediately. */ class HotwireBottomNavigationController( val activity: HotwireActivity, val view: BottomNavigationView, val initialVisibility: Visibility = Visibility.DEFAULT, val clearNavigationOnTabReselection: Boolean = true, - val animateVisibilityChanges: Boolean = true + val animateVisibilityChanges: Boolean = true, + val deferInitialTabLoad: Boolean = false ) : NavController.OnDestinationChangedListener { /** @@ -100,6 +106,16 @@ class HotwireBottomNavigationController( this.tabs = tabs + // Register which hosts should defer loading before their views are + // created and registered with the delegate. The initially selected tab + // is still loaded immediately when it becomes the current navigator. + activity.delegate.deferredNavigatorHostIds.apply { + clear() + if (deferInitialTabLoad) { + addAll(tabs.map { it.configuration.navigatorHostId }) + } + } + val initialIndex = selectedTabIndex.coerceIn(0, tabs.lastIndex) val initialTab = tabs[initialIndex] From 14559db1b3076ea7cbf02b3252005e9e07826dfe Mon Sep 17 00:00:00 2001 From: Jay Ohms Date: Thu, 2 Jul 2026 09:41:39 -0400 Subject: [PATCH 2/8] Change load load flag name --- .../navigation/tabs/HotwireBottomNavigationController.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/navigation-fragments/src/main/java/dev/hotwire/navigation/tabs/HotwireBottomNavigationController.kt b/navigation-fragments/src/main/java/dev/hotwire/navigation/tabs/HotwireBottomNavigationController.kt index c6ef63ae..e9905c64 100644 --- a/navigation-fragments/src/main/java/dev/hotwire/navigation/tabs/HotwireBottomNavigationController.kt +++ b/navigation-fragments/src/main/java/dev/hotwire/navigation/tabs/HotwireBottomNavigationController.kt @@ -22,7 +22,7 @@ import dev.hotwire.navigation.navigator.presentationContext * A [BottomNavigationView] controller that manages multiple [HotwireBottomTab]s, each associated * with its own [NavigatorHost] instance in the Activity layout. * - * @param deferInitialTabLoad When `false` (the default), every tab's [NavigatorHost] loads its + * @param lazyLoadTabs When `false` (the default), every tab's [NavigatorHost] loads its * start location as soon as its view is created, so all tabs load up front. When `true`, a tab's * start location is not loaded until that tab is first selected, avoiding loading (and creating a * web view visit for) every tab at once. The initially selected tab still loads immediately. @@ -33,7 +33,7 @@ class HotwireBottomNavigationController( val initialVisibility: Visibility = Visibility.DEFAULT, val clearNavigationOnTabReselection: Boolean = true, val animateVisibilityChanges: Boolean = true, - val deferInitialTabLoad: Boolean = false + val lazyLoadTabs: Boolean = false ) : NavController.OnDestinationChangedListener { /** @@ -111,7 +111,7 @@ class HotwireBottomNavigationController( // is still loaded immediately when it becomes the current navigator. activity.delegate.deferredNavigatorHostIds.apply { clear() - if (deferInitialTabLoad) { + if (lazyLoadTabs) { addAll(tabs.map { it.configuration.navigatorHostId }) } } From af709d5181adc1d04035babdd71966abaefe8192 Mon Sep 17 00:00:00 2001 From: Jay Ohms Date: Thu, 2 Jul 2026 10:11:03 -0400 Subject: [PATCH 3/8] Update the api name in the demo --- demo/src/main/kotlin/dev/hotwire/demo/main/MainActivity.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demo/src/main/kotlin/dev/hotwire/demo/main/MainActivity.kt b/demo/src/main/kotlin/dev/hotwire/demo/main/MainActivity.kt index 9bb26793..3e1f57ca 100644 --- a/demo/src/main/kotlin/dev/hotwire/demo/main/MainActivity.kt +++ b/demo/src/main/kotlin/dev/hotwire/demo/main/MainActivity.kt @@ -38,7 +38,7 @@ class MainActivity : HotwireActivity() { bottomNavigationController = HotwireBottomNavigationController( activity = this, view = bottomNavigationView, - deferInitialTabLoad = true + lazyLoadTabs = true ) bottomNavigationController.load(mainTabs, viewModel.selectedTabIndex) bottomNavigationController.setOnTabSelectedListener { index, _ -> From 7fd573d38be5a63bdebe8757b9f915e9926d0693 Mon Sep 17 00:00:00 2001 From: Jay Ohms Date: Thu, 2 Jul 2026 10:19:17 -0400 Subject: [PATCH 4/8] Make the initControllerGraph() function private and add an explicit resetControllerGraph() --- .../dev/hotwire/navigation/navigator/Navigator.kt | 2 +- .../hotwire/navigation/navigator/NavigatorHost.kt | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/navigation-fragments/src/main/java/dev/hotwire/navigation/navigator/Navigator.kt b/navigation-fragments/src/main/java/dev/hotwire/navigation/navigator/Navigator.kt index e63b7856..fd76e56c 100644 --- a/navigation-fragments/src/main/java/dev/hotwire/navigation/navigator/Navigator.kt +++ b/navigation-fragments/src/main/java/dev/hotwire/navigation/navigator/Navigator.kt @@ -211,7 +211,7 @@ class Navigator( navigateWhenReady { clearAll { session.reset() - host.initControllerGraph() + host.resetControllerGraph() if (host.view == null) { onReset() diff --git a/navigation-fragments/src/main/java/dev/hotwire/navigation/navigator/NavigatorHost.kt b/navigation-fragments/src/main/java/dev/hotwire/navigation/navigator/NavigatorHost.kt index 5a6d82e4..2198ea81 100644 --- a/navigation-fragments/src/main/java/dev/hotwire/navigation/navigator/NavigatorHost.kt +++ b/navigation-fragments/src/main/java/dev/hotwire/navigation/navigator/NavigatorHost.kt @@ -35,11 +35,6 @@ open class NavigatorHost : NavHostFragment(), FragmentOnAttachListener { activity = requireActivity() as HotwireActivity navigator = Navigator(this, configuration, activity) childFragmentManager.addFragmentOnAttachListener(this) - - // The graph (and therefore the start destination) is not built here. - // It's built when the host registers with the Activity delegate in - // onViewCreated, which allows the delegate to load the host eagerly - // (the default) or defer loading until its tab is first selected. } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { @@ -72,11 +67,16 @@ open class NavigatorHost : NavHostFragment(), FragmentOnAttachListener { * host may need to become ready for navigation (e.g. when its tab is selected). */ internal fun initControllerGraphIfNeeded() { - if (isGraphInitialized) return + if (!isGraphInitialized) { + initControllerGraph() + } + } + + internal fun resetControllerGraph() { initControllerGraph() } - internal fun initControllerGraph() { + private fun initControllerGraph() { isGraphInitialized = true ensureDeeplinkStartLocationValid() From d41820ff0e47fc597d4c5c1e95e853cefe0cd129 Mon Sep 17 00:00:00 2001 From: Jay Ohms Date: Thu, 2 Jul 2026 12:12:22 -0400 Subject: [PATCH 5/8] Improve the internal APIs for letting lazy navigator hosts --- .../activities/HotwireActivityDelegate.kt | 30 +++++++++++-------- .../tabs/HotwireBottomNavigationController.kt | 13 ++++---- 2 files changed, 22 insertions(+), 21 deletions(-) diff --git a/navigation-fragments/src/main/java/dev/hotwire/navigation/activities/HotwireActivityDelegate.kt b/navigation-fragments/src/main/java/dev/hotwire/navigation/activities/HotwireActivityDelegate.kt index c325da84..3bb7386f 100644 --- a/navigation-fragments/src/main/java/dev/hotwire/navigation/activities/HotwireActivityDelegate.kt +++ b/navigation-fragments/src/main/java/dev/hotwire/navigation/activities/HotwireActivityDelegate.kt @@ -18,15 +18,7 @@ import dev.hotwire.navigation.observers.HotwireActivityObserver class HotwireActivityDelegate(val activity: HotwireActivity) { private val navigatorHosts = mutableMapOf() - /** - * The IDs of navigator hosts whose start destination should not be loaded - * until the host first becomes the current navigator (e.g. when its bottom - * tab is first selected). Hosts not in this set are loaded eagerly as soon - * as their view is created. Populated by - * [dev.hotwire.navigation.tabs.HotwireBottomNavigationController] when - * deferred tab loading is enabled. - */ - internal val deferredNavigatorHostIds = mutableSetOf() + private val lazyNavigatorHostIds = mutableSetOf() private val onBackPressedCallback = object : OnBackPressedCallback(enabled = true) { override fun handleOnBackPressed() { @@ -84,6 +76,18 @@ class HotwireActivityDelegate(val activity: HotwireActivity) { } } + /** + * Marks the given navigator hosts as lazy, meaning their start destination + * won't be loaded until the host first becomes the current navigator (e.g. + * when its bottom tab is first selected). Any host not marked as lazy is + * loaded eagerly as soon as its view is created. Replaces any previously + * marked hosts. + */ + internal fun setLazyNavigatorHosts(navigatorHostIds: Collection) { + lazyNavigatorHostIds.clear() + lazyNavigatorHostIds.addAll(navigatorHostIds) + } + internal fun registerNavigatorHost(host: NavigatorHost) { logEvent("navigatorRegistered", listOf("navigator" to host.navigator.configuration.name)) @@ -95,10 +99,10 @@ class HotwireActivityDelegate(val activity: HotwireActivity) { updateOnBackPressedCallback(host) } - // Load the host's start destination unless it's a deferred tab host - // that isn't currently selected. Deferred hosts are loaded when they - // first become the current navigator (see setCurrentNavigator). - if (host.id !in deferredNavigatorHostIds || currentNavigatorHostId == host.id) { + // Load the host's start destination unless it's a lazy host that + // isn't currently selected. Lazy hosts are loaded when they first + // become the current navigator (see setCurrentNavigator). + if (host.id !in lazyNavigatorHostIds || currentNavigatorHostId == host.id) { host.initControllerGraphIfNeeded() } } diff --git a/navigation-fragments/src/main/java/dev/hotwire/navigation/tabs/HotwireBottomNavigationController.kt b/navigation-fragments/src/main/java/dev/hotwire/navigation/tabs/HotwireBottomNavigationController.kt index e9905c64..7e414c78 100644 --- a/navigation-fragments/src/main/java/dev/hotwire/navigation/tabs/HotwireBottomNavigationController.kt +++ b/navigation-fragments/src/main/java/dev/hotwire/navigation/tabs/HotwireBottomNavigationController.kt @@ -106,14 +106,11 @@ class HotwireBottomNavigationController( this.tabs = tabs - // Register which hosts should defer loading before their views are - // created and registered with the delegate. The initially selected tab - // is still loaded immediately when it becomes the current navigator. - activity.delegate.deferredNavigatorHostIds.apply { - clear() - if (lazyLoadTabs) { - addAll(tabs.map { it.configuration.navigatorHostId }) - } + // Mark the tab hosts as lazy before their views are created and + // registered with the delegate. The initially selected tab is still + // loaded immediately when it becomes the current navigator. + if (lazyLoadTabs) { + activity.delegate.setLazyNavigatorHosts(tabs.map { it.configuration.navigatorHostId }) } val initialIndex = selectedTabIndex.coerceIn(0, tabs.lastIndex) From f099b15a940137e4281da35951133d29dd7a0c40 Mon Sep 17 00:00:00 2001 From: Jay Ohms Date: Thu, 2 Jul 2026 12:20:45 -0400 Subject: [PATCH 6/8] Check if isGraphInitialized in the host's isReady() function --- .../java/dev/hotwire/navigation/navigator/NavigatorHost.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/navigation-fragments/src/main/java/dev/hotwire/navigation/navigator/NavigatorHost.kt b/navigation-fragments/src/main/java/dev/hotwire/navigation/navigator/NavigatorHost.kt index 2198ea81..9c9af51e 100644 --- a/navigation-fragments/src/main/java/dev/hotwire/navigation/navigator/NavigatorHost.kt +++ b/navigation-fragments/src/main/java/dev/hotwire/navigation/navigator/NavigatorHost.kt @@ -58,7 +58,10 @@ open class NavigatorHost : NavHostFragment(), FragmentOnAttachListener { * has not been created yet. */ fun isReady(): Boolean { - return isAdded && !isDetached && childFragmentManager.primaryNavigationFragment != null + return isGraphInitialized + && isAdded + && !isDetached + && childFragmentManager.primaryNavigationFragment != null } /** From ca63c6ac5c6731dcfc4a424e1983928b3d937fc7 Mon Sep 17 00:00:00 2001 From: Jay Ohms Date: Thu, 2 Jul 2026 12:23:21 -0400 Subject: [PATCH 7/8] Cleanup --- .../navigation/activities/HotwireActivityDelegate.kt | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/navigation-fragments/src/main/java/dev/hotwire/navigation/activities/HotwireActivityDelegate.kt b/navigation-fragments/src/main/java/dev/hotwire/navigation/activities/HotwireActivityDelegate.kt index 3bb7386f..b451040e 100644 --- a/navigation-fragments/src/main/java/dev/hotwire/navigation/activities/HotwireActivityDelegate.kt +++ b/navigation-fragments/src/main/java/dev/hotwire/navigation/activities/HotwireActivityDelegate.kt @@ -17,7 +17,6 @@ import dev.hotwire.navigation.observers.HotwireActivityObserver @Suppress("unused", "MemberVisibilityCanBePrivate") class HotwireActivityDelegate(val activity: HotwireActivity) { private val navigatorHosts = mutableMapOf() - private val lazyNavigatorHostIds = mutableSetOf() private val onBackPressedCallback = object : OnBackPressedCallback(enabled = true) { @@ -68,20 +67,16 @@ class HotwireActivityDelegate(val activity: HotwireActivity) { val navigatorHost = navigatorHosts[currentNavigatorHostId] if (navigatorHost != null) { - // Load the host's start destination on demand the first time it - // becomes the current navigator. This is a no-op if it's already - // been loaded. navigatorHost.initControllerGraphIfNeeded() updateOnBackPressedCallback(navigatorHost) } } /** - * Marks the given navigator hosts as lazy, meaning their start destination + * Sets the given navigator hosts as lazy, meaning their start destination * won't be loaded until the host first becomes the current navigator (e.g. * when its bottom tab is first selected). Any host not marked as lazy is - * loaded eagerly as soon as its view is created. Replaces any previously - * marked hosts. + * loaded eagerly as soon as its view is created. */ internal fun setLazyNavigatorHosts(navigatorHostIds: Collection) { lazyNavigatorHostIds.clear() @@ -101,7 +96,7 @@ class HotwireActivityDelegate(val activity: HotwireActivity) { // Load the host's start destination unless it's a lazy host that // isn't currently selected. Lazy hosts are loaded when they first - // become the current navigator (see setCurrentNavigator). + // become the current navigator. if (host.id !in lazyNavigatorHostIds || currentNavigatorHostId == host.id) { host.initControllerGraphIfNeeded() } From cf293ea877dff3a1ddc42b5d1726695f67ebb1d4 Mon Sep 17 00:00:00 2001 From: Jay Ohms Date: Thu, 2 Jul 2026 12:26:10 -0400 Subject: [PATCH 8/8] Add remaining code documentation for the HotwireBottomNavigationController --- .../navigation/tabs/HotwireBottomNavigationController.kt | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/navigation-fragments/src/main/java/dev/hotwire/navigation/tabs/HotwireBottomNavigationController.kt b/navigation-fragments/src/main/java/dev/hotwire/navigation/tabs/HotwireBottomNavigationController.kt index 7e414c78..b784a931 100644 --- a/navigation-fragments/src/main/java/dev/hotwire/navigation/tabs/HotwireBottomNavigationController.kt +++ b/navigation-fragments/src/main/java/dev/hotwire/navigation/tabs/HotwireBottomNavigationController.kt @@ -22,6 +22,15 @@ import dev.hotwire.navigation.navigator.presentationContext * A [BottomNavigationView] controller that manages multiple [HotwireBottomTab]s, each associated * with its own [NavigatorHost] instance in the Activity layout. * + * @param activity The [HotwireActivity] that hosts the [BottomNavigationView] and its tabs. + * @param view The [BottomNavigationView] in your layout to manage. + * @param initialVisibility The initial [Visibility] mode for the [BottomNavigationView]. Defaults + * to [Visibility.DEFAULT]. + * @param clearNavigationOnTabReselection When `true` (the default), reselecting the already-active + * tab clears its navigation backstack to the start destination. When `false`, reselecting a tab + * has no effect. + * @param animateVisibilityChanges When `true` (the default), the [BottomNavigationView] animates in + * and out when its visibility changes. When `false`, visibility changes are applied instantly. * @param lazyLoadTabs When `false` (the default), every tab's [NavigatorHost] loads its * start location as soon as its view is created, so all tabs load up front. When `true`, a tab's * start location is not loaded until that tab is first selected, avoiding loading (and creating a