From d02ca4cdc452471f45ef030510a4f6b059291b84 Mon Sep 17 00:00:00 2001 From: Isaac Date: Sun, 29 Nov 2020 20:46:09 +1300 Subject: [PATCH 01/24] Remove type arguments from NavigationHandle and NavigationContext, move tests into "enro" (for creating tests that include results/viewmodels/etc in the future) --- .github/workflows/ci.yml | 2 +- .../nav/enro/core/NavigationAnimations.kt | 12 ++-- .../java/nav/enro/core/NavigationHandle.kt | 42 ++++++++---- .../nav/enro/core/NavigationHandleProperty.kt | 64 +++++++++++++------ .../nav/enro/core/NavigationInstruction.kt | 16 ++--- .../java/nav/enro/core/context/Extensions.kt | 23 ++++--- .../enro/core/context/NavigationContext.kt | 55 +++++----------- .../controller/NavigationComponentBuilder.kt | 2 +- .../core/controller/NavigationController.kt | 30 ++++----- .../core/executors/DefaultActivityExecutor.kt | 4 +- .../core/executors/DefaultFragmentExecutor.kt | 27 ++++---- .../enro/core/executors/ExecutorOverride.kt | 12 ++-- .../enro/core/executors/NavigationExecutor.kt | 10 +-- .../java/nav/enro/core/internal/Extensions.kt | 11 ++-- .../core/internal/SingleFragmentActivity.kt | 2 +- .../handle/NavigationHandleActivityBinder.kt | 15 +++-- .../handle/NavigationHandleFragmentBinder.kt | 17 +++-- .../handle/NavigationHandleViewModel.kt | 45 ++++++------- .../java/nav/enro/core/navigator/Navigator.kt | 4 -- .../core/navigator/NavigatorDefinition.kt | 7 -- .../java/nav/enro/core/navigator/Synthetic.kt | 4 +- .../java/nav/enro/core/plugins/EnroLogger.kt | 13 ++-- .../java/nav/enro/core/plugins/EnroPlugin.kt | 6 +- .../masterdetail/MasterDetailComponent.kt | 9 ++- .../MultistackControllerFragment.kt | 2 +- .../main/java/nav/enro/result/EnroResult.kt | 7 +- .../nav/enro/result/EnroResultExtensions.kt | 22 ++++++- .../internal/LazyResultChannelProperty.kt | 10 +-- .../enro/result/internal/ResultChannelImpl.kt | 20 ++++-- .../enro/viewmodel/EnroViewModelExtensions.kt | 21 +++--- .../enro/viewmodel/EnroViewModelFactory.kt | 3 +- .../EnroViewModelNavigationHandleProvider.kt | 10 ++- enro/build.gradle | 10 +++ enro/src/androidTest/AndroidManifest.xml | 10 +++ .../java/nav/enro}/TestApplication.kt | 12 +--- .../java/nav/enro}/TestDestinations.kt | 13 +++- .../androidTest/java/nav/enro}/TestViews.kt | 8 ++- .../nav/enro/core/ActivityToActivityTests.kt | 24 +++---- .../nav/enro/core/ActivityToFragmentTests.kt | 31 +++++---- .../java/nav/enro/core/Extensions.kt | 12 ++-- .../ActivityToActivityOverrideTests.kt | 8 ++- .../ActivityToFragmentOverrideTests.kt | 8 +-- .../FragmentToActivityOverrideTests.kt | 14 ++-- .../FragmentToFragmentOverrideTests.kt | 20 +++--- .../java/nav/enro/example/detail/Detail.kt | 21 +++--- 45 files changed, 388 insertions(+), 330 deletions(-) create mode 100644 enro/src/androidTest/AndroidManifest.xml rename {enro-core/src/androidTest/java/nav/enro/core => enro/src/androidTest/java/nav/enro}/TestApplication.kt (81%) rename {enro-core/src/androidTest/java/nav/enro/core => enro/src/androidTest/java/nav/enro}/TestDestinations.kt (71%) rename {enro-core/src/androidTest/java/nav/enro/core => enro/src/androidTest/java/nav/enro}/TestViews.kt (92%) rename {enro-core => enro}/src/androidTest/java/nav/enro/core/ActivityToActivityTests.kt (86%) rename {enro-core => enro}/src/androidTest/java/nav/enro/core/ActivityToFragmentTests.kt (76%) rename {enro-core => enro}/src/androidTest/java/nav/enro/core/Extensions.kt (91%) rename {enro-core => enro}/src/androidTest/java/nav/enro/core/overrides/ActivityToActivityOverrideTests.kt (95%) rename {enro-core => enro}/src/androidTest/java/nav/enro/core/overrides/ActivityToFragmentOverrideTests.kt (96%) rename {enro-core => enro}/src/androidTest/java/nav/enro/core/overrides/FragmentToActivityOverrideTests.kt (91%) rename {enro-core => enro}/src/androidTest/java/nav/enro/core/overrides/FragmentToFragmentOverrideTests.kt (91%) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 53a48e94..3f290553 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,4 +18,4 @@ jobs: uses: reactivecircus/android-emulator-runner@v2 with: api-level: 29 - script: ./gradlew :enro-core:connectedCheck \ No newline at end of file + script: ./gradlew :enro:connectedCheck \ No newline at end of file diff --git a/enro-core/src/main/java/nav/enro/core/NavigationAnimations.kt b/enro-core/src/main/java/nav/enro/core/NavigationAnimations.kt index aee232db..bf254266 100644 --- a/enro-core/src/main/java/nav/enro/core/NavigationAnimations.kt +++ b/enro-core/src/main/java/nav/enro/core/NavigationAnimations.kt @@ -66,22 +66,22 @@ fun animationsFor(context: Fragment, navigationInstruction: NavigationInstructio animationsFor(context.navigationContext, navigationInstruction) fun animationsFor( - context: NavigationContext<*, *>, + context: NavigationContext<*>, navigationInstruction: NavigationInstruction ): AnimationPair { - if (navigationInstruction is NavigationInstruction.Open<*> && navigationInstruction.children.isNotEmpty()) { + if (navigationInstruction is NavigationInstruction.Open && navigationInstruction.children.isNotEmpty()) { return AnimationPair(0, 0) } return when (navigationInstruction) { - is NavigationInstruction.Open<*> -> animationsForOpen(context, navigationInstruction) + is NavigationInstruction.Open -> animationsForOpen(context, navigationInstruction) is NavigationInstruction.Close -> animationsForClose(context, navigationInstruction) } } private fun animationsForOpen( - context: NavigationContext<*, *>, - navigationInstruction: NavigationInstruction.Open<*> + context: NavigationContext<*>, + navigationInstruction: NavigationInstruction.Open ): AnimationPair { val theme = context.activity.theme val navigator = context.navigator @@ -112,7 +112,7 @@ private fun animationsForOpen( } private fun animationsForClose( - context: NavigationContext<*, *>, + context: NavigationContext<*>, navigationInstruction: NavigationInstruction.Close ): AnimationPair { val theme = context.activity.theme diff --git a/enro-core/src/main/java/nav/enro/core/NavigationHandle.kt b/enro-core/src/main/java/nav/enro/core/NavigationHandle.kt index e14db238..7311d6c8 100644 --- a/enro-core/src/main/java/nav/enro/core/NavigationHandle.kt +++ b/enro-core/src/main/java/nav/enro/core/NavigationHandle.kt @@ -1,32 +1,50 @@ package nav.enro.core import android.os.Bundle -import androidx.activity.viewModels -import androidx.fragment.app.Fragment -import androidx.fragment.app.FragmentActivity -import androidx.fragment.app.viewModels import androidx.lifecycle.LifecycleOwner import nav.enro.core.controller.NavigationController -import nav.enro.core.internal.handle.NavigationHandleViewModel -interface NavigationHandle : LifecycleOwner { +interface NavigationHandle : LifecycleOwner { val id: String - val key: T val controller: NavigationController val additionalData: Bundle + fun key(): T fun executeInstruction(navigationInstruction: NavigationInstruction) - fun onCloseRequested(onCloseRequested: () -> Unit) } -fun NavigationHandle<*>.forward(key: NavigationKey, vararg childKeys: NavigationKey) = +class TypedNavigationHandle(private val navigationHandle: NavigationHandle) { + val id: String get() = navigationHandle.id + val key: T get() = navigationHandle.key() + val additionalData: Bundle get() = navigationHandle.additionalData + val controller: NavigationController get() = navigationHandle.controller + + fun executeInstruction(navigationInstruction: NavigationInstruction) = navigationHandle.executeInstruction(navigationInstruction) +} + +fun NavigationHandle.asTyped(): TypedNavigationHandle { + return TypedNavigationHandle(this) +} + +fun NavigationHandle.forward(key: NavigationKey, vararg childKeys: NavigationKey) = executeInstruction(NavigationInstruction.Open(NavigationDirection.FORWARD, key, childKeys.toList())) -fun NavigationHandle<*>.replace(key: NavigationKey, vararg childKeys: NavigationKey) = +fun NavigationHandle.replace(key: NavigationKey, vararg childKeys: NavigationKey) = executeInstruction(NavigationInstruction.Open(NavigationDirection.REPLACE, key, childKeys.toList())) -fun NavigationHandle<*>.replaceRoot(key: NavigationKey, vararg childKeys: NavigationKey) = +fun NavigationHandle.replaceRoot(key: NavigationKey, vararg childKeys: NavigationKey) = executeInstruction(NavigationInstruction.Open(NavigationDirection.REPLACE_ROOT, key, childKeys.toList())) -fun NavigationHandle<*>.close() = +fun NavigationHandle.close() = executeInstruction(NavigationInstruction.Close) +fun TypedNavigationHandle<*>.forward(key: NavigationKey, vararg childKeys: NavigationKey) = + executeInstruction(NavigationInstruction.Open(NavigationDirection.FORWARD, key, childKeys.toList())) + +fun TypedNavigationHandle<*>.replace(key: NavigationKey, vararg childKeys: NavigationKey) = + executeInstruction(NavigationInstruction.Open(NavigationDirection.REPLACE, key, childKeys.toList())) + +fun TypedNavigationHandle<*>.replaceRoot(key: NavigationKey, vararg childKeys: NavigationKey) = + executeInstruction(NavigationInstruction.Open(NavigationDirection.REPLACE_ROOT, key, childKeys.toList())) + +fun TypedNavigationHandle<*>.close() = + executeInstruction(NavigationInstruction.Close) \ No newline at end of file diff --git a/enro-core/src/main/java/nav/enro/core/NavigationHandleProperty.kt b/enro-core/src/main/java/nav/enro/core/NavigationHandleProperty.kt index daea0548..8cff0893 100644 --- a/enro-core/src/main/java/nav/enro/core/NavigationHandleProperty.kt +++ b/enro-core/src/main/java/nav/enro/core/NavigationHandleProperty.kt @@ -5,9 +5,13 @@ import androidx.annotation.IdRes import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentActivity import androidx.fragment.app.viewModels -import androidx.lifecycle.* +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.ViewModelStoreOwner import nav.enro.core.context.ChildContainer import nav.enro.core.internal.handle.NavigationHandleViewModel +import java.lang.ref.WeakReference +import kotlin.collections.set import kotlin.properties.ReadOnlyProperty import kotlin.reflect.KProperty @@ -16,46 +20,66 @@ class NavigationHandleProperty @PublishedApi internal const private val lifecycleOwner: LifecycleOwner, private val viewModelStoreOwner: ViewModelStoreOwner, private val configBuilder: NavigationHandleConfiguration.() -> Unit = {} -) : ReadOnlyProperty> { +) : ReadOnlyProperty> { private val config = NavigationHandleConfiguration().apply(configBuilder) - private val navigationHandle by lazy { + private val navigationHandle: TypedNavigationHandle by lazy { val navigationHandle = ViewModelProvider(viewModelStoreOwner, ViewModelProvider.NewInstanceFactory()) .get(NavigationHandleViewModel::class.java) - as NavigationHandleViewModel config.applyTo(navigationHandle) - return@lazy navigationHandle + return@lazy navigationHandle.asTyped() } init { - lifecycleOwner.lifecycle.addObserver(object : LifecycleEventObserver { - override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) { - if(event == Lifecycle.Event.ON_CREATE) { - // trigger the lazy creation of the navigationHandle property - navigationHandle.hashCode() - } - } - }) + pendingProperties[lifecycleOwner.hashCode()] = WeakReference(this) } - override fun getValue(thisRef: Any, property: KProperty<*>): NavigationHandle { + override fun getValue(thisRef: Any, property: KProperty<*>): TypedNavigationHandle { return navigationHandle } + + companion object { + internal val pendingProperties = mutableMapOf>>() + + fun applyPending(activity: FragmentActivity) { + val pending = pendingProperties[activity.hashCode()] ?: return + pending.get()?.navigationHandle.hashCode() + pendingProperties.remove(activity.hashCode()) + } + + fun applyPending(fragment: Fragment) { + val pending = pendingProperties[fragment.hashCode()] ?: return + pending.get()?.navigationHandle.hashCode() + pendingProperties.remove(fragment.hashCode()) + } + } } class NavigationHandleConfiguration @PublishedApi internal constructor() { - private var childContainers = listOf() + private var childContainers: List = listOf() + private var defaultKey: T? = null + private var onCloseRequested: TypedNavigationHandle.() -> Unit = { close() } fun container(@IdRes containerId: Int, accept: (NavigationKey) -> Boolean = { true }) { childContainers = childContainers + ChildContainer(containerId, accept) } - internal fun applyTo(navigationHandleViewModel: NavigationHandleViewModel) { + fun defaultKey(navigationKey: T) { + defaultKey = navigationKey + } + + fun onCloseRequested(block: TypedNavigationHandle.() -> Unit) { + onCloseRequested = block + } + + internal fun applyTo(navigationHandleViewModel: NavigationHandleViewModel) { navigationHandleViewModel.childContainers = childContainers + navigationHandleViewModel.defaultKey = defaultKey + navigationHandleViewModel.internalOnCloseRequested = { onCloseRequested(navigationHandleViewModel.asTyped()) } } } @@ -76,8 +100,8 @@ fun Fragment.navigationHandle( configBuilder = config ) -fun FragmentActivity.getNavigationHandle(): NavigationHandle = - viewModels> { ViewModelProvider.NewInstanceFactory() } .value +fun FragmentActivity.getNavigationHandle(): NavigationHandle = + viewModels { ViewModelProvider.NewInstanceFactory() } .value -fun Fragment.getNavigationHandle(): NavigationHandle = - viewModels> { ViewModelProvider.NewInstanceFactory() } .value \ No newline at end of file +fun Fragment.getNavigationHandle(): NavigationHandle = + viewModels { ViewModelProvider.NewInstanceFactory() } .value \ No newline at end of file diff --git a/enro-core/src/main/java/nav/enro/core/NavigationInstruction.kt b/enro-core/src/main/java/nav/enro/core/NavigationInstruction.kt index 1dec46e2..f180d322 100644 --- a/enro-core/src/main/java/nav/enro/core/NavigationInstruction.kt +++ b/enro-core/src/main/java/nav/enro/core/NavigationInstruction.kt @@ -5,7 +5,7 @@ import android.os.Bundle import android.os.Parcelable import androidx.fragment.app.Fragment import kotlinx.android.parcel.Parcelize -import java.util.UUID +import java.util.* enum class NavigationDirection { FORWARD, @@ -18,11 +18,11 @@ internal const val CONTEXT_ID_ARG = "nav.enro.core.CONTEXT_ID" sealed class NavigationInstruction { @Parcelize - data class Open( + data class Open( val navigationDirection: NavigationDirection, - val navigationKey: T, + val navigationKey: NavigationKey, val children: List = emptyList(), - val parentInstruction: Open<*>? = null, + val parentInstruction: Open? = null, val animations: NavigationAnimations? = null, val additionalData: Bundle = Bundle(), val instructionId: String = UUID.randomUUID().toString() @@ -32,23 +32,23 @@ sealed class NavigationInstruction { } -fun Intent.addOpenInstruction(instruction: NavigationInstruction.Open<*>): Intent { +fun Intent.addOpenInstruction(instruction: NavigationInstruction.Open): Intent { putExtra(OPEN_ARG, instruction) return this } -fun Bundle.addOpenInstruction(instruction: NavigationInstruction.Open<*>): Bundle { +fun Bundle.addOpenInstruction(instruction: NavigationInstruction.Open): Bundle { putParcelable(OPEN_ARG, instruction) return this } -fun Fragment.addOpenInstruction(instruction: NavigationInstruction.Open<*>): Fragment { +fun Fragment.addOpenInstruction(instruction: NavigationInstruction.Open): Fragment { arguments = (arguments ?: Bundle()).apply { putParcelable(OPEN_ARG, instruction) } return this } -fun Bundle.readOpenInstruction(): NavigationInstruction.Open? { +fun Bundle.readOpenInstruction(): NavigationInstruction.Open? { return getParcelable(OPEN_ARG) } \ No newline at end of file diff --git a/enro-core/src/main/java/nav/enro/core/context/Extensions.kt b/enro-core/src/main/java/nav/enro/core/context/Extensions.kt index 488e36ad..555e77e5 100644 --- a/enro-core/src/main/java/nav/enro/core/context/Extensions.kt +++ b/enro-core/src/main/java/nav/enro/core/context/Extensions.kt @@ -10,17 +10,16 @@ import androidx.lifecycle.ViewModelProvider import nav.enro.core.NavigationKey import nav.enro.core.internal.handle.NavigationHandleViewModel import nav.enro.core.navigator.FragmentHost -import java.lang.IllegalStateException -val NavigationContext.fragment get() = contextReference -val NavigationContext<*, *>.activity: FragmentActivity +val NavigationContext.fragment get() = contextReference +val NavigationContext<*>.activity: FragmentActivity get() = when (contextReference) { is FragmentActivity -> contextReference is Fragment -> contextReference.requireActivity() else -> throw IllegalStateException() } -internal fun NavigationContext<*, *>.fragmentHostFor(key: NavigationKey): FragmentHost? { +internal fun NavigationContext<*>.fragmentHostFor(key: NavigationKey): FragmentHost? { val primaryFragment = childFragmentManager.primaryNavigationFragment val activeContainerId = primaryFragment?.getContainerId() @@ -48,7 +47,7 @@ internal fun NavigationContext<*, *>.fragmentHostFor(key: NavigationKey): Fragme internal fun Fragment.getContainerId() = (requireView().parent as View).id -fun NavigationContext<*, *>.rootContext(): NavigationContext<*, *> { +fun NavigationContext<*>.rootContext(): NavigationContext<*> { var parent = this while (true) { val currentContext = parent @@ -56,10 +55,10 @@ fun NavigationContext<*, *>.rootContext(): NavigationContext<*, *> { } } -fun NavigationContext<*, *>.parentContext(): NavigationContext<*, *>? { +fun NavigationContext<*>.parentContext(): NavigationContext<*>? { return when (this) { is ActivityContext -> null - is FragmentContext -> + is FragmentContext -> when (val parentFragment = fragment.parentFragment) { null -> fragment.requireActivity().navigationContext else -> parentFragment.navigationContext @@ -67,7 +66,7 @@ fun NavigationContext<*, *>.parentContext(): NavigationContext<*, *>? { } } -fun NavigationContext<*, out NavigationKey>.leafContext(): NavigationContext<*, out NavigationKey> { +fun NavigationContext<*>.leafContext(): NavigationContext<*> { val primaryNavigationFragment = childFragmentManager.primaryNavigationFragment ?: return this primaryNavigationFragment.view ?: return this val childContext = primaryNavigationFragment.navigationContext @@ -75,9 +74,9 @@ fun NavigationContext<*, out NavigationKey>.leafContext(): NavigationContext<*, } @Suppress("UNCHECKED_CAST") // Higher level logic dictates this cast will pass -internal val T.navigationContext: ActivityContext - get() = viewModels> { ViewModelProvider.NewInstanceFactory() } .value.navigationContext as ActivityContext +internal val T.navigationContext: ActivityContext + get() = viewModels { ViewModelProvider.NewInstanceFactory() } .value.navigationContext as ActivityContext @Suppress("UNCHECKED_CAST") // Higher level logic dictates this cast will pass -internal val T.navigationContext: FragmentContext - get() = viewModels> { ViewModelProvider.NewInstanceFactory() } .value.navigationContext as FragmentContext \ No newline at end of file +internal val T.navigationContext: FragmentContext + get() = viewModels { ViewModelProvider.NewInstanceFactory() } .value.navigationContext as FragmentContext \ No newline at end of file diff --git a/enro-core/src/main/java/nav/enro/core/context/NavigationContext.kt b/enro-core/src/main/java/nav/enro/core/context/NavigationContext.kt index 369a4e0d..ba93f155 100644 --- a/enro-core/src/main/java/nav/enro/core/context/NavigationContext.kt +++ b/enro-core/src/main/java/nav/enro/core/context/NavigationContext.kt @@ -1,61 +1,38 @@ package nav.enro.core.context -import android.os.Bundle -import android.view.View -import androidx.activity.viewModels import androidx.annotation.IdRes import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentManager -import androidx.fragment.app.viewModels import androidx.lifecycle.Lifecycle -import nav.enro.core.* +import nav.enro.core.NavigationInstruction +import nav.enro.core.NavigationKey import nav.enro.core.controller.NavigationController -import nav.enro.core.internal.handle.NavigationHandleViewModel import nav.enro.core.controller.navigationController import nav.enro.core.navigator.ActivityNavigator import nav.enro.core.navigator.FragmentNavigator import nav.enro.core.navigator.Navigator -import java.lang.IllegalStateException -import kotlin.reflect.KClass data class ChildContainer( @IdRes val containerId: Int, val accept: (NavigationKey) -> Boolean ) -sealed class NavigationContext( - val contextReference: ContextType +sealed class NavigationContext( + val contextReference: ContextType, + val instruction: NavigationInstruction.Open? ) { abstract val controller: NavigationController abstract val lifecycle: Lifecycle abstract val childFragmentManager: FragmentManager abstract val id: String - protected abstract val arguments: Bundle? - val instruction by lazy { - arguments?.readOpenInstruction() ?: defaultKey?.let { - NavigationInstruction.Open(NavigationDirection.FORWARD, it) - } - } - - val key: T by lazy { + val key: NavigationKey? by lazy { instruction?.navigationKey - ?: defaultKey - ?: throw IllegalStateException("Navigation Context's bound arguments did not contain a NavigationKey!") - } - - internal val isEnroContext by lazy { - val key = instruction?.navigationKey ?: defaultKey - return@lazy key != null - } - - internal val defaultKey: T? by lazy { - controller.navigatorForContextType(contextReference::class)?.defaultKey as? T } - internal open val navigator: Navigator by lazy { - controller.navigatorForKeyType(key::class) as Navigator + internal open val navigator: Navigator by lazy { + controller.navigatorForContextType(contextReference::class) as Navigator } internal val pendingKeys: List by lazy { @@ -69,25 +46,25 @@ sealed class NavigationContext( internal var childContainers = listOf() } -internal class ActivityContext( +internal class ActivityContext( contextReference: ContextType, + instruction: NavigationInstruction.Open?, override val id: String -) : NavigationContext(contextReference) { +) : NavigationContext(contextReference, instruction) { override val controller get() = contextReference.application.navigationController override val lifecycle get() = contextReference.lifecycle - override val arguments get() = contextReference.intent.extras - override val navigator get() = super.navigator as ActivityNavigator + override val navigator get() = super.navigator as ActivityNavigator override val childFragmentManager get() = contextReference.supportFragmentManager } -internal class FragmentContext( +internal class FragmentContext( contextReference: ContextType, + instruction: NavigationInstruction.Open?, override val id: String -) : NavigationContext(contextReference) { +) : NavigationContext(contextReference, instruction) { override val controller get() = contextReference.requireActivity().application.navigationController override val lifecycle get() = contextReference.lifecycle - override val arguments get() = contextReference.arguments - override val navigator get() = super.navigator as FragmentNavigator + override val navigator get() = super.navigator as FragmentNavigator override val childFragmentManager get() = contextReference.childFragmentManager } diff --git a/enro-core/src/main/java/nav/enro/core/controller/NavigationComponentBuilder.kt b/enro-core/src/main/java/nav/enro/core/controller/NavigationComponentBuilder.kt index 27d96ae0..fe410dee 100644 --- a/enro-core/src/main/java/nav/enro/core/controller/NavigationComponentBuilder.kt +++ b/enro-core/src/main/java/nav/enro/core/controller/NavigationComponentBuilder.kt @@ -40,7 +40,7 @@ class NavigationComponentBuilder { inline fun override( noinline launch: ((ExecutorArgs) -> Unit) = defaultLaunch(), - noinline close: (NavigationContext) -> Unit = defaultClose() + noinline close: (NavigationContext) -> Unit = defaultClose() ) { overrides.add(createOverride(launch, close)) } diff --git a/enro-core/src/main/java/nav/enro/core/controller/NavigationController.kt b/enro-core/src/main/java/nav/enro/core/controller/NavigationController.kt index 1e2daafa..ed933756 100644 --- a/enro-core/src/main/java/nav/enro/core/controller/NavigationController.kt +++ b/enro-core/src/main/java/nav/enro/core/controller/NavigationController.kt @@ -31,7 +31,7 @@ class NavigationController( overrides: List> = listOf(), private val plugins: List = listOf() ) { - internal var active: NavigationHandle<*>? = null + internal var active: NavigationHandle? = null set(value) { if(value == field) return field = value @@ -77,15 +77,15 @@ class NavigationController( private val temporaryOverrides = mutableMapOf, KClass>, NavigationExecutor<*,*,*>>() - internal val handles = mutableMapOf>() + internal val handles = mutableMapOf() init { plugins.forEach { it.onAttached(this) } } internal fun open( - navigationContext: NavigationContext, - instruction: NavigationInstruction.Open<*> + navigationContext: NavigationContext, + instruction: NavigationInstruction.Open ) { val navigator = navigatorForKeyType(instruction.navigationKey::class) ?: throw IllegalStateException("Attempted to execute $instruction but could not find a valid navigator for the key type on this instruction") @@ -107,12 +107,12 @@ class NavigationController( ) ) is SyntheticNavigator -> (navigator.destination as SyntheticDestination) - .process(navigationContext, instruction as NavigationInstruction.Open) + .process(navigationContext, instruction as NavigationInstruction.Open) } } internal fun close( - navigationContext: NavigationContext + navigationContext: NavigationContext ) { if (!closeOverrideFor(navigationContext)) { when (navigationContext) { @@ -122,11 +122,11 @@ class NavigationController( } } - internal fun onOpened(navigationHandle: NavigationHandle<*>) { + internal fun onOpened(navigationHandle: NavigationHandle) { plugins.forEach { it.onOpened(navigationHandle) } } - internal fun onClosed(navigationHandle: NavigationHandle<*>) { + internal fun onClosed(navigationHandle: NavigationHandle) { plugins.forEach { it.onClosed(navigationHandle) } } @@ -147,9 +147,9 @@ class NavigationController( } private fun openOverrideFor( - fromContext: NavigationContext, + fromContext: NavigationContext, navigator: Navigator, - instruction: NavigationInstruction.Open + instruction: NavigationInstruction.Open ): Boolean { val override = overrideFor(fromContext.contextReference::class to navigator.contextType) @@ -189,7 +189,7 @@ class NavigationController( } } - private fun closeOverrideFor(navigationContext: NavigationContext): Boolean { + private fun closeOverrideFor(navigationContext: NavigationContext): Boolean { val parentType = navigationContext.parentInstruction ?.let { if(it.navigationKey is SingleFragmentKey) { @@ -210,13 +210,13 @@ class NavigationController( return true } - private fun NavigationInstruction.Open<*>.setParentInstruction( - parentContext: NavigationContext<*, *>, + private fun NavigationInstruction.Open.setParentInstruction( + parentContext: NavigationContext<*>, navigator: Navigator - ): NavigationInstruction.Open<*> { + ): NavigationInstruction.Open { if (parentInstruction != null) return this - fun findCorrectParentInstructionFor(instruction: NavigationInstruction.Open<*>?): NavigationInstruction.Open<*>? { + fun findCorrectParentInstructionFor(instruction: NavigationInstruction.Open?): NavigationInstruction.Open? { if (navigator is FragmentNavigator) { return instruction } diff --git a/enro-core/src/main/java/nav/enro/core/executors/DefaultActivityExecutor.kt b/enro-core/src/main/java/nav/enro/core/executors/DefaultActivityExecutor.kt index 4a1f5a36..42acfc6b 100644 --- a/enro-core/src/main/java/nav/enro/core/executors/DefaultActivityExecutor.kt +++ b/enro-core/src/main/java/nav/enro/core/executors/DefaultActivityExecutor.kt @@ -39,9 +39,9 @@ object DefaultActivityExecutor : NavigationExecutor) { + override fun close(context: NavigationContext) { context.activity.supportFinishAfterTransition() - if (!context.isEnroContext) return + context.controller.navigatorForContextType(context.contextReference::class) ?: return val animations = animationsFor(context, NavigationInstruction.Close) context.activity.overridePendingTransition(animations.enter, animations.exit) diff --git a/enro-core/src/main/java/nav/enro/core/executors/DefaultFragmentExecutor.kt b/enro-core/src/main/java/nav/enro/core/executors/DefaultFragmentExecutor.kt index d32b7b93..8bd322ed 100644 --- a/enro-core/src/main/java/nav/enro/core/executors/DefaultFragmentExecutor.kt +++ b/enro-core/src/main/java/nav/enro/core/executors/DefaultFragmentExecutor.kt @@ -7,10 +7,11 @@ import androidx.core.view.ViewCompat import androidx.fragment.app.* import nav.enro.core.* import nav.enro.core.context.* -import nav.enro.core.context.ActivityContext -import nav.enro.core.context.FragmentContext -import nav.enro.core.internal.* -import nav.enro.core.navigator.* +import nav.enro.core.internal.AbstractSingleFragmentActivity +import nav.enro.core.internal.SingleFragmentKey +import nav.enro.core.navigator.ActivityNavigator +import nav.enro.core.navigator.FragmentNavigator +import nav.enro.core.navigator.Navigator object DefaultFragmentExecutor : NavigationExecutor( fromType = Any::class, @@ -93,7 +94,7 @@ object DefaultFragmentExecutor : NavigationExecutor) { + override fun close(context: NavigationContext) { if (context.contextReference is DialogFragment) { context.contextReference.dismiss() return @@ -124,7 +125,7 @@ object DefaultFragmentExecutor : NavigationExecutor, - instruction: NavigationInstruction.Open<*> + instruction: NavigationInstruction.Open ): Fragment { val fragment = fragmentManager.fragmentFactory.instantiate( navigator.contextType.java.classLoader!!, @@ -139,8 +140,8 @@ object DefaultFragmentExecutor : NavigationExecutor, - fromContext: NavigationContext, - instruction: NavigationInstruction.Open<*> + fromContext: NavigationContext, + instruction: NavigationInstruction.Open ): Boolean { try { fromContext.fragmentHostFor(instruction.navigationKey)?.fragmentManager?.executePendingTransactions() @@ -148,10 +149,10 @@ object DefaultFragmentExecutor : NavigationExecutor fromContext.activity.getNavigationHandle().executeInstruction( + is ActivityContext -> fromContext.activity.getNavigationHandle().executeInstruction( instruction ) - is FragmentContext -> fromContext.fragment.getNavigationHandle().executeInstruction( + is FragmentContext -> fromContext.fragment.getNavigationHandle().executeInstruction( instruction ) } @@ -161,8 +162,8 @@ object DefaultFragmentExecutor : NavigationExecutor, - instruction: NavigationInstruction.Open<*> + fromContext: NavigationContext, + instruction: NavigationInstruction.Open ) { if(fromContext.contextReference is DialogFragment && instruction.navigationDirection == NavigationDirection.REPLACE) { // If we attempt to openFragmentAsActivity into a DialogFragment using the REPLACE direction, @@ -186,7 +187,7 @@ object DefaultFragmentExecutor : NavigationExecutor.getParentFragment(): Fragment? { +fun NavigationContext.getParentFragment(): Fragment? { val containerView = contextReference.getContainerId() val parentInstruction = parentInstruction parentInstruction ?: return null diff --git a/enro-core/src/main/java/nav/enro/core/executors/ExecutorOverride.kt b/enro-core/src/main/java/nav/enro/core/executors/ExecutorOverride.kt index 053ba633..2acf464f 100644 --- a/enro-core/src/main/java/nav/enro/core/executors/ExecutorOverride.kt +++ b/enro-core/src/main/java/nav/enro/core/executors/ExecutorOverride.kt @@ -10,7 +10,7 @@ fun createOverride( fromClass: KClass, opensClass: KClass, launch: ((ExecutorArgs) -> Unit), - close: ((context: NavigationContext) -> Unit) + close: ((context: NavigationContext) -> Unit) ): NavigationExecutor = object : NavigationExecutor( fromType = fromClass, @@ -21,14 +21,14 @@ fun createOverride( launch(args) } - override fun close(context: NavigationContext) { + override fun close(context: NavigationContext) { close(context) } } inline fun createOverride( noinline launch: ((ExecutorArgs) -> Unit) = defaultLaunch(), - noinline close: (NavigationContext) -> Unit = defaultClose() + noinline close: (NavigationContext) -> Unit = defaultClose() ): NavigationExecutor = createOverride(From::class, Opens::class, launch, close) @@ -46,13 +46,13 @@ inline fun defaultLaunch(): ((ExecutorArgs defaultClose(): (NavigationContext) -> Unit { +inline fun defaultClose(): (NavigationContext) -> Unit { return when { FragmentActivity::class.java.isAssignableFrom(Opens::class.java) -> - DefaultActivityExecutor::close as (NavigationContext) -> Unit + DefaultActivityExecutor::close as (NavigationContext) -> Unit Fragment::class.java.isAssignableFrom(Opens::class.java) -> - DefaultFragmentExecutor::close as (NavigationContext) -> Unit + DefaultFragmentExecutor::close as (NavigationContext) -> Unit else -> throw IllegalArgumentException("No default close executor found for ${Opens::class}") } diff --git a/enro-core/src/main/java/nav/enro/core/executors/NavigationExecutor.kt b/enro-core/src/main/java/nav/enro/core/executors/NavigationExecutor.kt index d93a5172..fc153e19 100644 --- a/enro-core/src/main/java/nav/enro/core/executors/NavigationExecutor.kt +++ b/enro-core/src/main/java/nav/enro/core/executors/NavigationExecutor.kt @@ -1,16 +1,16 @@ package nav.enro.core.executors -import nav.enro.core.* +import nav.enro.core.NavigationInstruction +import nav.enro.core.NavigationKey import nav.enro.core.context.NavigationContext import nav.enro.core.navigator.Navigator import kotlin.reflect.KClass -import kotlin.reflect.KType // This class is used primarily to simplify the lambda signature of NavigationExecutor.open class ExecutorArgs( - val fromContext: NavigationContext, + val fromContext: NavigationContext, val navigator: Navigator, - val instruction: NavigationInstruction.Open + val instruction: NavigationInstruction.Open ) @@ -24,7 +24,7 @@ abstract class NavigationExecutor + context: NavigationContext ) } diff --git a/enro-core/src/main/java/nav/enro/core/internal/Extensions.kt b/enro-core/src/main/java/nav/enro/core/internal/Extensions.kt index 87dceff8..77c6fe2c 100644 --- a/enro-core/src/main/java/nav/enro/core/internal/Extensions.kt +++ b/enro-core/src/main/java/nav/enro/core/internal/Extensions.kt @@ -8,10 +8,7 @@ import androidx.fragment.app.FragmentActivity import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.LifecycleOwner -import nav.enro.core.NavigationKey import nav.enro.core.context.* -import nav.enro.core.context.ActivityContext -import nav.enro.core.context.FragmentContext import nav.enro.core.getNavigationHandle import nav.enro.core.internal.handle.NavigationHandleViewModel @@ -33,11 +30,11 @@ internal fun FragmentActivity.addOnBackPressedListener(block: () -> Unit) { }) } -internal fun NavigationContext.navigationHandle(): NavigationHandleViewModel<*> { +internal fun NavigationContext.navigationHandle(): NavigationHandleViewModel { return when (this) { - is FragmentContext -> fragment.getNavigationHandle() - is ActivityContext -> activity.getNavigationHandle() - } as NavigationHandleViewModel<*> + is FragmentContext -> fragment.getNavigationHandle() + is ActivityContext -> activity.getNavigationHandle() + } as NavigationHandleViewModel } internal fun Resources.Theme.getAttributeResourceId(attr: Int) = TypedValue().let { diff --git a/enro-core/src/main/java/nav/enro/core/internal/SingleFragmentActivity.kt b/enro-core/src/main/java/nav/enro/core/internal/SingleFragmentActivity.kt index 2f20bdfb..627f576d 100644 --- a/enro-core/src/main/java/nav/enro/core/internal/SingleFragmentActivity.kt +++ b/enro-core/src/main/java/nav/enro/core/internal/SingleFragmentActivity.kt @@ -11,7 +11,7 @@ import nav.enro.core.navigationHandle @Parcelize internal data class SingleFragmentKey( - val instruction: NavigationInstruction.Open<*> + val instruction: NavigationInstruction.Open ) : NavigationKey internal abstract class AbstractSingleFragmentActivity : AppCompatActivity() { diff --git a/enro-core/src/main/java/nav/enro/core/internal/handle/NavigationHandleActivityBinder.kt b/enro-core/src/main/java/nav/enro/core/internal/handle/NavigationHandleActivityBinder.kt index 1aeb8ded..bac6448b 100644 --- a/enro-core/src/main/java/nav/enro/core/internal/handle/NavigationHandleActivityBinder.kt +++ b/enro-core/src/main/java/nav/enro/core/internal/handle/NavigationHandleActivityBinder.kt @@ -6,16 +6,13 @@ import android.os.Bundle import android.view.ViewGroup import androidx.activity.viewModels import androidx.fragment.app.FragmentActivity -import androidx.lifecycle.SavedStateViewModelFactory import androidx.lifecycle.ViewModelProvider -import nav.enro.core.CONTEXT_ID_ARG -import nav.enro.core.NavigationKey +import nav.enro.core.* import nav.enro.core.context.ActivityContext import nav.enro.core.context.leafContext import nav.enro.core.context.navigationContext import nav.enro.core.controller.navigationController import nav.enro.core.internal.navigationHandle -import nav.enro.core.readOpenInstruction import java.util.* internal object NavigationHandleActivityBinder : Application.ActivityLifecycleCallbacks { @@ -27,12 +24,16 @@ internal object NavigationHandleActivityBinder : Application.ActivityLifecycleCa NavigationHandleFragmentBinder, true ) - val handle by activity.viewModels> { ViewModelProvider.NewInstanceFactory() } - val contextId = activity.intent.extras?.readOpenInstruction()?.instructionId + val handle by activity.viewModels { ViewModelProvider.NewInstanceFactory() } + val contextId = activity.intent.extras?.readOpenInstruction()?.instructionId ?: savedInstanceState?.getString(CONTEXT_ID_ARG) ?: UUID.randomUUID().toString() - handle.navigationContext = ActivityContext(activity, contextId) + NavigationHandleProperty.applyPending(activity) + val instruction = activity.intent.extras?.readOpenInstruction() ?: handle.defaultKey?.let { + NavigationInstruction.Open(NavigationDirection.FORWARD, it) + } + handle.navigationContext = ActivityContext(activity, instruction, contextId) if(savedInstanceState == null) handle.executeDeeplink() activity.findViewById(android.R.id.content).viewTreeObserver.addOnGlobalLayoutListener { activity.application.navigationController.active = activity.navigationContext.leafContext().navigationHandle() diff --git a/enro-core/src/main/java/nav/enro/core/internal/handle/NavigationHandleFragmentBinder.kt b/enro-core/src/main/java/nav/enro/core/internal/handle/NavigationHandleFragmentBinder.kt index 783e4991..c11d48d3 100644 --- a/enro-core/src/main/java/nav/enro/core/internal/handle/NavigationHandleFragmentBinder.kt +++ b/enro-core/src/main/java/nav/enro/core/internal/handle/NavigationHandleFragmentBinder.kt @@ -1,32 +1,31 @@ package nav.enro.core.internal.handle import android.os.Bundle -import android.util.Log -import android.view.View -import androidx.fragment.app.DialogFragment import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentManager import androidx.fragment.app.viewModels import androidx.lifecycle.ViewModelProvider -import nav.enro.core.CONTEXT_ID_ARG +import nav.enro.core.* import nav.enro.core.context.FragmentContext -import nav.enro.core.NavigationKey import nav.enro.core.context.leafContext import nav.enro.core.context.navigationContext import nav.enro.core.controller.navigationController import nav.enro.core.internal.navigationHandle -import nav.enro.core.readOpenInstruction import java.util.* internal object NavigationHandleFragmentBinder: FragmentManager.FragmentLifecycleCallbacks() { override fun onFragmentCreated(fm: FragmentManager, fragment: Fragment, savedInstanceState: Bundle?) { - val handle = fragment.viewModels> { ViewModelProvider.NewInstanceFactory() } .value + val handle = fragment.viewModels { ViewModelProvider.NewInstanceFactory() } .value - val contextId = fragment.arguments?.readOpenInstruction()?.instructionId + val contextId = fragment.arguments?.readOpenInstruction()?.instructionId ?: savedInstanceState?.getString(CONTEXT_ID_ARG) ?: UUID.randomUUID().toString() - handle.navigationContext = FragmentContext(fragment, contextId) + NavigationHandleProperty.applyPending(fragment) + val instruction = fragment.arguments?.readOpenInstruction() ?: handle.defaultKey?.let { + NavigationInstruction.Open(NavigationDirection.FORWARD, it) + } + handle.navigationContext = FragmentContext(fragment, instruction, contextId) if(savedInstanceState == null) handle.executeDeeplink() } diff --git a/enro-core/src/main/java/nav/enro/core/internal/handle/NavigationHandleViewModel.kt b/enro-core/src/main/java/nav/enro/core/internal/handle/NavigationHandleViewModel.kt index 93418c82..1ee929ad 100644 --- a/enro-core/src/main/java/nav/enro/core/internal/handle/NavigationHandleViewModel.kt +++ b/enro-core/src/main/java/nav/enro/core/internal/handle/NavigationHandleViewModel.kt @@ -3,29 +3,24 @@ package nav.enro.core.internal.handle import android.os.Bundle import android.os.Handler import android.os.Looper -import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentActivity import androidx.lifecycle.* -import nav.enro.core.internal.addOnBackPressedListener -import nav.enro.core.internal.onEvent import nav.enro.core.* import nav.enro.core.context.* -import nav.enro.core.context.ActivityContext -import nav.enro.core.context.FragmentContext -import nav.enro.core.context.NavigationContext -import nav.enro.core.context.leafContext import nav.enro.core.controller.NavigationController +import nav.enro.core.internal.addOnBackPressedListener import nav.enro.core.internal.navigationHandle -import java.lang.IllegalStateException +import nav.enro.core.internal.onEvent -internal class NavigationHandleViewModel : ViewModel(), NavigationHandle { +internal class NavigationHandleViewModel : ViewModel(), NavigationHandle { private var pendingInstruction: NavigationInstruction? = null - private var internalOnCloseRequested: () -> Unit = { close() } + internal var internalOnCloseRequested: () -> Unit = { close() } - internal val hasKey get() = ::key.isInitialized - override lateinit var key: T + internal val hasKey get() = rawKey != null + internal var rawKey: NavigationKey? = null + internal var defaultKey: NavigationKey? = null override lateinit var id: String override lateinit var controller: NavigationController override lateinit var additionalData: Bundle @@ -50,7 +45,7 @@ internal class NavigationHandleViewModel : ViewModel(), Navig return lifecycle } - internal var navigationContext: NavigationContext<*, T>? = null + internal var navigationContext: NavigationContext<*>? = null set(value) { field?.let { val id = it.id @@ -63,14 +58,7 @@ internal class NavigationHandleViewModel : ViewModel(), Navig additionalData = it.instruction?.additionalData ?: Bundle() id = it.id it.controller.handles[id] = this - - if (it.instruction == null) { - navigationContext?.defaultKey?.let { defaultKey -> - key = defaultKey - } - return@let - } - key = it.key + rawKey = it.key ?: return@let } if (value == null) return registerLifecycleObservers(value) @@ -83,7 +71,7 @@ internal class NavigationHandleViewModel : ViewModel(), Navig } - private fun registerLifecycleObservers(context: NavigationContext) { + private fun registerLifecycleObservers(context: NavigationContext) { context.lifecycle.addObserver(object : LifecycleEventObserver { override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) { if (event == Lifecycle.Event.ON_DESTROY || event == Lifecycle.Event.ON_CREATE) return @@ -95,8 +83,8 @@ internal class NavigationHandleViewModel : ViewModel(), Navig } } - private fun registerOnBackPressedListener(context: NavigationContext) { - if (context is ActivityContext) { + private fun registerOnBackPressedListener(context: NavigationContext) { + if (context is ActivityContext) { context.activity.addOnBackPressedListener { context.leafContext().navigationHandle().internalOnCloseRequested() } @@ -108,8 +96,11 @@ internal class NavigationHandleViewModel : ViewModel(), Navig executePendingInstruction() } - override fun onCloseRequested(onCloseRequested: () -> Unit) { - internalOnCloseRequested = onCloseRequested + override fun key(): T { + if(rawKey == null) { + throw IllegalStateException("This NavigationHandle has no NavigationKey bound") + } + return rawKey as T } private fun executePendingInstruction() { @@ -123,7 +114,7 @@ internal class NavigationHandleViewModel : ViewModel(), Navig when (instruction) { NavigationInstruction.Close -> context.controller.close(context.leafContext()) - is NavigationInstruction.Open<*> -> { + is NavigationInstruction.Open -> { context.controller.open(context, instruction) } } diff --git a/enro-core/src/main/java/nav/enro/core/navigator/Navigator.kt b/enro-core/src/main/java/nav/enro/core/navigator/Navigator.kt index 53606e88..5324cdf6 100644 --- a/enro-core/src/main/java/nav/enro/core/navigator/Navigator.kt +++ b/enro-core/src/main/java/nav/enro/core/navigator/Navigator.kt @@ -7,7 +7,6 @@ import kotlin.reflect.KClass interface Navigator { val keyType: KClass - val defaultKey: NavigationKey? val contextType: KClass val animations: NavigatorAnimations } @@ -15,14 +14,12 @@ interface Navigator { class ActivityNavigator @PublishedApi internal constructor( override val keyType: KClass, override val contextType: KClass, - override val defaultKey: NavigationKey?, override val animations: NavigatorAnimations = NavigatorAnimations.default ) : Navigator class FragmentNavigator @PublishedApi internal constructor( override val keyType: KClass, override val contextType: KClass, - override val defaultKey: NavigationKey?, override val animations: NavigatorAnimations = NavigatorAnimations.default ) : Navigator @@ -32,5 +29,4 @@ class SyntheticNavigator @PublishedApi internal constructor( ) : Navigator { override val contextType: KClass = Any::class override val animations: NavigatorAnimations = NavigatorAnimations.default - override val defaultKey: NavigationKey? = null } diff --git a/enro-core/src/main/java/nav/enro/core/navigator/NavigatorDefinition.kt b/enro-core/src/main/java/nav/enro/core/navigator/NavigatorDefinition.kt index 209201a5..3879212d 100644 --- a/enro-core/src/main/java/nav/enro/core/navigator/NavigatorDefinition.kt +++ b/enro-core/src/main/java/nav/enro/core/navigator/NavigatorDefinition.kt @@ -30,17 +30,11 @@ class ActivityNavigatorBuilder( @PublishedApi internal val contextType: KClass ) { @PublishedApi internal val executors = mutableListOf>() - private var defaultKey: NavigationKey? = null - - fun defaultKey(key: T) { - defaultKey = key - } fun build() = NavigatorDefinition( navigator = ActivityNavigator( keyType = keyType, contextType = contextType, - defaultKey = defaultKey ), executors = executors ) @@ -71,7 +65,6 @@ class FragmentNavigatorBuilder( navigator = FragmentNavigator( keyType = keyType, contextType = contextType, - defaultKey = null ), executors = executors ) diff --git a/enro-core/src/main/java/nav/enro/core/navigator/Synthetic.kt b/enro-core/src/main/java/nav/enro/core/navigator/Synthetic.kt index 6fb39c6e..956cf6c3 100644 --- a/enro-core/src/main/java/nav/enro/core/navigator/Synthetic.kt +++ b/enro-core/src/main/java/nav/enro/core/navigator/Synthetic.kt @@ -6,7 +6,7 @@ import nav.enro.core.context.NavigationContext interface SyntheticDestination { fun process( - navigationContext: NavigationContext, - instruction: NavigationInstruction.Open + navigationContext: NavigationContext, + instruction: NavigationInstruction.Open ) } diff --git a/enro-core/src/main/java/nav/enro/core/plugins/EnroLogger.kt b/enro-core/src/main/java/nav/enro/core/plugins/EnroLogger.kt index f07cc8be..ad62f3ce 100644 --- a/enro-core/src/main/java/nav/enro/core/plugins/EnroLogger.kt +++ b/enro-core/src/main/java/nav/enro/core/plugins/EnroLogger.kt @@ -2,17 +2,18 @@ package nav.enro.core.plugins import android.util.Log import nav.enro.core.NavigationHandle +import nav.enro.core.NavigationKey class EnroLogger : EnroPlugin() { - override fun onOpened(navigationHandle: NavigationHandle<*>) { - Log.d("Enro", "Opened: ${navigationHandle.key}") + override fun onOpened(navigationHandle: NavigationHandle) { + Log.d("Enro", "Opened: ${navigationHandle.key()}") } - override fun onActive(navigationHandle: NavigationHandle<*>) { - Log.d("Enro", "Active: ${navigationHandle.key}") + override fun onActive(navigationHandle: NavigationHandle) { + Log.d("Enro", "Active: ${navigationHandle.key()}") } - override fun onClosed(navigationHandle: NavigationHandle<*>) { - Log.d("Enro", "Closed: ${navigationHandle.key}") + override fun onClosed(navigationHandle: NavigationHandle) { + Log.d("Enro", "Closed: ${navigationHandle.key()}") } } \ No newline at end of file diff --git a/enro-core/src/main/java/nav/enro/core/plugins/EnroPlugin.kt b/enro-core/src/main/java/nav/enro/core/plugins/EnroPlugin.kt index b0ed4e9e..2ac198b6 100644 --- a/enro-core/src/main/java/nav/enro/core/plugins/EnroPlugin.kt +++ b/enro-core/src/main/java/nav/enro/core/plugins/EnroPlugin.kt @@ -5,7 +5,7 @@ import nav.enro.core.controller.NavigationController abstract class EnroPlugin { open fun onAttached(navigationController: NavigationController) {} - open fun onOpened(navigationHandle: NavigationHandle<*>) {} - open fun onActive(navigationHandle: NavigationHandle<*>) {} - open fun onClosed(navigationHandle: NavigationHandle<*>) {} + open fun onOpened(navigationHandle: NavigationHandle) {} + open fun onActive(navigationHandle: NavigationHandle) {} + open fun onClosed(navigationHandle: NavigationHandle) {} } \ No newline at end of file diff --git a/enro-masterdetail/src/main/java/nav/enro/masterdetail/MasterDetailComponent.kt b/enro-masterdetail/src/main/java/nav/enro/masterdetail/MasterDetailComponent.kt index 89b9e3f0..648429e8 100644 --- a/enro-masterdetail/src/main/java/nav/enro/masterdetail/MasterDetailComponent.kt +++ b/enro-masterdetail/src/main/java/nav/enro/masterdetail/MasterDetailComponent.kt @@ -7,14 +7,17 @@ import androidx.fragment.app.FragmentActivity import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.LifecycleOwner -import nav.enro.core.* -import nav.enro.core.context.fragment +import nav.enro.core.NavigationKey +import nav.enro.core.addOpenInstruction import nav.enro.core.context.activity +import nav.enro.core.context.fragment import nav.enro.core.controller.NavigationController import nav.enro.core.controller.navigationController import nav.enro.core.executors.DefaultActivityExecutor import nav.enro.core.executors.ExecutorArgs import nav.enro.core.executors.createOverride +import nav.enro.core.forward +import nav.enro.core.getNavigationHandle import kotlin.properties.ReadOnlyProperty import kotlin.reflect.KClass import kotlin.reflect.KProperty @@ -100,7 +103,7 @@ class MasterDetailProperty( navigationController.addOverride(masterOverride) navigationController.addOverride(detailOverride) - (lifecycleOwner as FragmentActivity).getNavigationHandle().forward(initialMasterKey()) + (lifecycleOwner as FragmentActivity).getNavigationHandle().forward(initialMasterKey()) } if(event == Lifecycle.Event.ON_START) { diff --git a/enro-multistack/src/main/java/nav/enro/multistack/MultistackControllerFragment.kt b/enro-multistack/src/main/java/nav/enro/multistack/MultistackControllerFragment.kt index 50d37f9c..2c39a066 100644 --- a/enro-multistack/src/main/java/nav/enro/multistack/MultistackControllerFragment.kt +++ b/enro-multistack/src/main/java/nav/enro/multistack/MultistackControllerFragment.kt @@ -147,7 +147,7 @@ internal class MultistackControllerFragment : Fragment(), ViewTreeObserver.OnGlo private fun onStackClosed(container: MultistackContainer) { listenForEvents = false if (container == containers.first()) { - requireActivity().getNavigationHandle().close() + requireActivity().getNavigationHandle().close() } else { openStack(containers.first()) } diff --git a/enro-result/src/main/java/nav/enro/result/EnroResult.kt b/enro-result/src/main/java/nav/enro/result/EnroResult.kt index bfd90a54..154e7033 100644 --- a/enro-result/src/main/java/nav/enro/result/EnroResult.kt +++ b/enro-result/src/main/java/nav/enro/result/EnroResult.kt @@ -3,10 +3,9 @@ package nav.enro.result import nav.enro.core.NavigationHandle import nav.enro.core.controller.NavigationController import nav.enro.core.plugins.EnroPlugin -import nav.enro.result.internal.ResultChannelImpl -import nav.enro.result.internal.ResultChannelId import nav.enro.result.internal.PendingResult -import java.lang.IllegalStateException +import nav.enro.result.internal.ResultChannelId +import nav.enro.result.internal.ResultChannelImpl class EnroResult: EnroPlugin() { private val channels = mutableMapOf>() @@ -16,7 +15,7 @@ class EnroResult: EnroPlugin() { controllerBindings[navigationController] = this } - override fun onActive(navigationHandle: NavigationHandle<*>) { + override fun onActive(navigationHandle: NavigationHandle) { channels.values .filter { channel -> pendingResults.any { it.key == channel.id } diff --git a/enro-result/src/main/java/nav/enro/result/EnroResultExtensions.kt b/enro-result/src/main/java/nav/enro/result/EnroResultExtensions.kt index 43209381..03d8635d 100644 --- a/enro-result/src/main/java/nav/enro/result/EnroResultExtensions.kt +++ b/enro-result/src/main/java/nav/enro/result/EnroResultExtensions.kt @@ -4,14 +4,29 @@ import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentActivity import androidx.lifecycle.ViewModel import nav.enro.core.NavigationHandle +import nav.enro.core.TypedNavigationHandle import nav.enro.core.close -import nav.enro.result.internal.ResultChannelImpl import nav.enro.result.internal.LazyResultChannelProperty import nav.enro.result.internal.PendingResult +import nav.enro.result.internal.ResultChannelImpl import kotlin.properties.ReadOnlyProperty -fun NavigationHandle>.closeWithResult(result: T) { +fun NavigationHandle.closeWithResult(result: T) { + val resultId = ResultChannelImpl.getResultId(this) + if(resultId != null) { + EnroResult.from(controller).addPendingResult( + PendingResult( + resultChannelId = resultId, + resultType = result::class, + result = result + ) + ) + } + close() +} + +fun TypedNavigationHandle>.closeWithResult(result: T) { val resultId = ResultChannelImpl.getResultId(this) if(resultId != null) { EnroResult.from(controller).addPendingResult( @@ -25,7 +40,8 @@ fun NavigationHandle>.closeWithResult(result close() } -inline fun ViewModel.registerForNavigationResult(navigationHandle: NavigationHandle<*>, +inline fun ViewModel.registerForNavigationResult( + navigationHandle: NavigationHandle, noinline onResult: (T) -> Unit ): ReadOnlyProperty> = LazyResultChannelProperty( owner = navigationHandle, diff --git a/enro-result/src/main/java/nav/enro/result/internal/LazyResultChannelProperty.kt b/enro-result/src/main/java/nav/enro/result/internal/LazyResultChannelProperty.kt index 63eb3c4f..a830633d 100644 --- a/enro-result/src/main/java/nav/enro/result/internal/LazyResultChannelProperty.kt +++ b/enro-result/src/main/java/nav/enro/result/internal/LazyResultChannelProperty.kt @@ -6,9 +6,9 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.LifecycleOwner import nav.enro.core.NavigationHandle +import nav.enro.core.TypedNavigationHandle import nav.enro.core.getNavigationHandle import nav.enro.result.EnroResult -import java.lang.IllegalArgumentException import kotlin.properties.ReadOnlyProperty import kotlin.reflect.KProperty @@ -23,10 +23,10 @@ internal class LazyResultChannelProperty( init { val handle = when (owner) { - is FragmentActivity -> lazy { owner.getNavigationHandle() } - is Fragment ->lazy { owner.getNavigationHandle() } - is NavigationHandle<*> -> lazy { owner as NavigationHandle } - else -> throw IllegalArgumentException("Owner must be a Fragment or FragmentActivity") + is FragmentActivity -> lazy { owner.getNavigationHandle() } + is Fragment ->lazy { owner.getNavigationHandle() } + is NavigationHandle -> lazy { owner as NavigationHandle } + else -> throw IllegalArgumentException("Owner must be a Fragment, FragmentActivity, or NavigationHandle") } val lifecycle = owner as LifecycleOwner diff --git a/enro-result/src/main/java/nav/enro/result/internal/ResultChannelImpl.kt b/enro-result/src/main/java/nav/enro/result/internal/ResultChannelImpl.kt index ed269890..ef670d29 100644 --- a/enro-result/src/main/java/nav/enro/result/internal/ResultChannelImpl.kt +++ b/enro-result/src/main/java/nav/enro/result/internal/ResultChannelImpl.kt @@ -3,17 +3,19 @@ package nav.enro.result.internal import android.os.Bundle import android.os.Handler import android.os.Looper -import androidx.lifecycle.* +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleObserver +import androidx.lifecycle.OnLifecycleEvent import nav.enro.core.NavigationDirection import nav.enro.core.NavigationHandle import nav.enro.core.NavigationInstruction +import nav.enro.core.TypedNavigationHandle import nav.enro.result.EnroResult import nav.enro.result.EnroResultChannel import nav.enro.result.ResultNavigationKey -import java.lang.IllegalArgumentException class ResultChannelImpl internal constructor( - private val navigationHandle: NavigationHandle<*>, + private val navigationHandle: NavigationHandle, private val resultType: Class, private val onResult: (T) -> Unit ): EnroResultChannel { @@ -59,7 +61,17 @@ class ResultChannelImpl internal constructor( private val handler = Handler(Looper.getMainLooper()) private const val EXTRA_RESULT_CHANNEL_ID = "com.enro.core.RESULT_CHANNEL_ID" - internal fun getResultId(navigationHandle: NavigationHandle<*>): ResultChannelId? { + internal fun getResultId(navigationHandle: NavigationHandle): ResultChannelId? { + val classLoader = navigationHandle.additionalData.classLoader + navigationHandle.additionalData.classLoader = ResultChannelId::class.java.classLoader + val resultId = navigationHandle.additionalData.getParcelable( + EXTRA_RESULT_CHANNEL_ID + ) + navigationHandle.additionalData.classLoader = classLoader + return resultId + } + + internal fun getResultId(navigationHandle: TypedNavigationHandle<*>): ResultChannelId? { val classLoader = navigationHandle.additionalData.classLoader navigationHandle.additionalData.classLoader = ResultChannelId::class.java.classLoader val resultId = navigationHandle.additionalData.getParcelable( diff --git a/enro-viewmodel/src/main/java/nav/enro/viewmodel/EnroViewModelExtensions.kt b/enro-viewmodel/src/main/java/nav/enro/viewmodel/EnroViewModelExtensions.kt index 59458782..e181ed05 100644 --- a/enro-viewmodel/src/main/java/nav/enro/viewmodel/EnroViewModelExtensions.kt +++ b/enro-viewmodel/src/main/java/nav/enro/viewmodel/EnroViewModelExtensions.kt @@ -1,29 +1,30 @@ package nav.enro.viewmodel -import androidx.activity.ComponentActivity import androidx.annotation.MainThread import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentActivity import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelStore -import nav.enro.core.* +import nav.enro.core.NavigationHandle +import nav.enro.core.NavigationKey +import nav.enro.core.getNavigationHandle import kotlin.properties.ReadOnlyProperty import kotlin.reflect.KClass import kotlin.reflect.KProperty -class ViewModelNavigationHandleProperty internal constructor( +class ViewModelNavigationHandleProperty internal constructor( viewModelType: KClass -) : ReadOnlyProperty> { +) : ReadOnlyProperty { - private val navigationHandle = EnroViewModelNavigationHandleProvider.get(viewModelType.java) + private val navigationHandle = EnroViewModelNavigationHandleProvider.get(viewModelType.java) - override fun getValue(thisRef: ViewModel, property: KProperty<*>): NavigationHandle { + override fun getValue(thisRef: ViewModel, property: KProperty<*>): NavigationHandle { return navigationHandle } } -fun ViewModel.navigationHandle(): ViewModelNavigationHandleProperty = +fun ViewModel.navigationHandle(): ViewModelNavigationHandleProperty = ViewModelNavigationHandleProperty(this::class) @@ -37,7 +38,7 @@ inline fun FragmentActivity.enroViewModels( } val navigationHandle = { - getNavigationHandle() + getNavigationHandle() } return enroViewModels({viewModelStore}, navigationHandle, factory) @@ -53,7 +54,7 @@ inline fun Fragment.enroViewModels( } val navigationHandle = { - getNavigationHandle() + getNavigationHandle() } return enroViewModels({viewModelStore}, navigationHandle, factory) @@ -63,7 +64,7 @@ inline fun Fragment.enroViewModels( @PublishedApi internal inline fun enroViewModels( noinline viewModelStore: (() -> ViewModelStore), - noinline navigationHandle: (() -> NavigationHandle), + noinline navigationHandle: (() -> NavigationHandle), noinline factoryProducer: (() -> ViewModelProvider.Factory) ): Lazy { diff --git a/enro-viewmodel/src/main/java/nav/enro/viewmodel/EnroViewModelFactory.kt b/enro-viewmodel/src/main/java/nav/enro/viewmodel/EnroViewModelFactory.kt index a718eea3..1f1f1dba 100644 --- a/enro-viewmodel/src/main/java/nav/enro/viewmodel/EnroViewModelFactory.kt +++ b/enro-viewmodel/src/main/java/nav/enro/viewmodel/EnroViewModelFactory.kt @@ -2,12 +2,11 @@ package nav.enro.viewmodel import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider -import androidx.lifecycle.ViewModelStoreOwner import nav.enro.core.NavigationHandle @PublishedApi internal class EnroViewModelFactory( - private val navigationHandle: NavigationHandle<*>, + private val navigationHandle: NavigationHandle, private val delegate: ViewModelProvider.Factory ) : ViewModelProvider.Factory { diff --git a/enro-viewmodel/src/main/java/nav/enro/viewmodel/EnroViewModelNavigationHandleProvider.kt b/enro-viewmodel/src/main/java/nav/enro/viewmodel/EnroViewModelNavigationHandleProvider.kt index ea8b9d76..a3094a3e 100644 --- a/enro-viewmodel/src/main/java/nav/enro/viewmodel/EnroViewModelNavigationHandleProvider.kt +++ b/enro-viewmodel/src/main/java/nav/enro/viewmodel/EnroViewModelNavigationHandleProvider.kt @@ -1,13 +1,11 @@ package nav.enro.viewmodel import nav.enro.core.NavigationHandle -import nav.enro.core.NavigationKey -import kotlin.reflect.KClass internal object EnroViewModelNavigationHandleProvider { - private val navigationHandles = mutableMapOf, NavigationHandle<*>>() + private val navigationHandles = mutableMapOf, NavigationHandle>() - fun put(modelClass: Class<*>, navigationHandle: NavigationHandle<*>) { + fun put(modelClass: Class<*>, navigationHandle: NavigationHandle) { navigationHandles[modelClass] = navigationHandle } @@ -15,7 +13,7 @@ internal object EnroViewModelNavigationHandleProvider { navigationHandles.remove(modelClass) } - fun get(modelClass: Class<*>): NavigationHandle { - return navigationHandles[modelClass] as NavigationHandle + fun get(modelClass: Class<*>): NavigationHandle { + return navigationHandles[modelClass] as NavigationHandle } } \ No newline at end of file diff --git a/enro/build.gradle b/enro/build.gradle index 89a32fb9..bfcc0d4b 100644 --- a/enro/build.gradle +++ b/enro/build.gradle @@ -19,6 +19,16 @@ dependencies { releaseApi "nav.enro:enro-annotations:$versionName" debugApi project(":enro-annotations") + + androidTestImplementation 'androidx.core:core-ktx:1.3.2' + androidTestImplementation 'androidx.appcompat:appcompat:1.2.0' + androidTestImplementation 'androidx.fragment:fragment-ktx:1.2.5' + androidTestImplementation 'androidx.activity:activity:1.1.0' + + androidTestImplementation 'androidx.test.ext:junit:1.1.2' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' + androidTestImplementation 'androidx.test.espresso:espresso-intents:3.3.0' + androidTestImplementation 'androidx.test:runner:1.3.0' } afterEvaluate { diff --git a/enro/src/androidTest/AndroidManifest.xml b/enro/src/androidTest/AndroidManifest.xml new file mode 100644 index 00000000..33ee775a --- /dev/null +++ b/enro/src/androidTest/AndroidManifest.xml @@ -0,0 +1,10 @@ + + + + + + + \ No newline at end of file diff --git a/enro-core/src/androidTest/java/nav/enro/core/TestApplication.kt b/enro/src/androidTest/java/nav/enro/TestApplication.kt similarity index 81% rename from enro-core/src/androidTest/java/nav/enro/core/TestApplication.kt rename to enro/src/androidTest/java/nav/enro/TestApplication.kt index 99f76317..7d8dd2a3 100644 --- a/enro-core/src/androidTest/java/nav/enro/core/TestApplication.kt +++ b/enro/src/androidTest/java/nav/enro/TestApplication.kt @@ -1,6 +1,5 @@ -package nav.enro.core +package nav.enro -import android.R import android.app.Application import nav.enro.core.controller.NavigationApplication import nav.enro.core.controller.NavigationController @@ -13,19 +12,14 @@ class TestApplication : Application(), override val navigationController = NavigationController( navigators = listOf( - createActivityNavigator { - defaultKey(defaultKey) - }, + createActivityNavigator(), createActivityNavigator(), createFragmentNavigator(), - createActivityNavigator { - defaultKey(ActivityWithFragmentsKey("default")) - }, + createActivityNavigator(), createFragmentNavigator(), createFragmentNavigator() - ) ) diff --git a/enro-core/src/androidTest/java/nav/enro/core/TestDestinations.kt b/enro/src/androidTest/java/nav/enro/TestDestinations.kt similarity index 71% rename from enro-core/src/androidTest/java/nav/enro/core/TestDestinations.kt rename to enro/src/androidTest/java/nav/enro/TestDestinations.kt index b4477dfe..b9b33255 100644 --- a/enro-core/src/androidTest/java/nav/enro/core/TestDestinations.kt +++ b/enro/src/androidTest/java/nav/enro/TestDestinations.kt @@ -1,11 +1,17 @@ -package nav.enro.core +package nav.enro import android.R import kotlinx.android.parcel.Parcelize +import nav.enro.core.NavigationKey +import nav.enro.core.navigationHandle @Parcelize data class DefaultActivityKey(val id: String) : NavigationKey val defaultKey = DefaultActivityKey("default") -class DefaultActivity : TestActivity() +class DefaultActivity : TestActivity() { + private val navigation by navigationHandle { + defaultKey(defaultKey) + } +} @Parcelize data class GenericActivityKey(val id: String) : NavigationKey @@ -14,7 +20,8 @@ class GenericActivity : TestActivity() @Parcelize data class ActivityWithFragmentsKey(val id: String) : NavigationKey class ActivityWithFragments : TestActivity() { - private val navigation by navigationHandle() { + private val navigation by navigationHandle { + defaultKey(ActivityWithFragmentsKey("default")) container(R.id.content) { it is ActivityChildFragmentKey || it is ActivityChildFragmentTwoKey } diff --git a/enro-core/src/androidTest/java/nav/enro/core/TestViews.kt b/enro/src/androidTest/java/nav/enro/TestViews.kt similarity index 92% rename from enro-core/src/androidTest/java/nav/enro/core/TestViews.kt rename to enro/src/androidTest/java/nav/enro/TestViews.kt index be8f3548..29cc5940 100644 --- a/enro-core/src/androidTest/java/nav/enro/core/TestViews.kt +++ b/enro/src/androidTest/java/nav/enro/TestViews.kt @@ -1,4 +1,4 @@ -package nav.enro.core +package nav.enro import android.os.Bundle import android.util.Log @@ -11,12 +11,14 @@ import android.widget.LinearLayout import android.widget.TextView import androidx.appcompat.app.AppCompatActivity import androidx.fragment.app.Fragment +import nav.enro.core.NavigationKey +import nav.enro.core.getNavigationHandle abstract class TestActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val key = try { - getNavigationHandle().key + getNavigationHandle().key() } catch (t: Throwable) { Log.e("TestActivity", "Failed to open!", t) return @@ -53,7 +55,7 @@ abstract class TestFragment : Fragment() { savedInstanceState: Bundle? ): View? { val key = try { - getNavigationHandle().key + getNavigationHandle().key() } catch (t: Throwable) { Log.e("TestFragment", "Failed to open!", t) return null diff --git a/enro-core/src/androidTest/java/nav/enro/core/ActivityToActivityTests.kt b/enro/src/androidTest/java/nav/enro/core/ActivityToActivityTests.kt similarity index 86% rename from enro-core/src/androidTest/java/nav/enro/core/ActivityToActivityTests.kt rename to enro/src/androidTest/java/nav/enro/core/ActivityToActivityTests.kt index a7658332..69e36564 100644 --- a/enro-core/src/androidTest/java/nav/enro/core/ActivityToActivityTests.kt +++ b/enro/src/androidTest/java/nav/enro/core/ActivityToActivityTests.kt @@ -3,7 +3,9 @@ package nav.enro.core import android.content.Intent import androidx.test.core.app.ActivityScenario import androidx.test.platform.app.InstrumentationRegistry -import junit.framework.TestCase.* +import junit.framework.TestCase.assertEquals +import junit.framework.TestCase.assertNotNull +import nav.enro.* import org.junit.Test import java.util.* @@ -33,7 +35,7 @@ class ActivityToActivityTests { handle.forward(GenericActivityKey(id)) val next = expectActivity() - val nextHandle = next.getNavigationHandle() + val nextHandle = next.getNavigationHandle().asTyped() assertEquals(id, nextHandle.key.id) } @@ -58,7 +60,7 @@ class ActivityToActivityTests { ) expectActivity { - it.getNavigationHandle().key.id == id + it.getNavigationHandle().key().id == id } } @@ -69,15 +71,15 @@ class ActivityToActivityTests { handle.forward(GenericActivityKey("close")) val next = expectActivity() - val nextHandle = next.getNavigationHandle() + val nextHandle = next.getNavigationHandle() nextHandle.close() val activeActivity = expectActivity() - val activeHandle = activeActivity.getNavigationHandle() + val activeHandle = activeActivity.getNavigationHandle().asTyped() assertEquals(defaultKey, activeHandle.key) } - @Test(expected = Throwable::class) + @Test(expected = IllegalStateException::class) fun givenActivityDoesNotHaveDefaultKey_whenActivityOpenedWithoutNavigationKeySet_thenNavigationHandleCannotRetrieveKey() { val scenario = ActivityScenario.launch(GenericActivity::class.java) val handle = scenario.getNavigationHandle() @@ -114,7 +116,7 @@ class ActivityToActivityTests { handle.replace(GenericActivityKey(id)) val next = expectActivity() - val nextHandle = next.getNavigationHandle() + val nextHandle = next.getNavigationHandle() nextHandle.close() expectNoActivity() @@ -129,11 +131,11 @@ class ActivityToActivityTests { val handle = scenario.getNavigationHandle() handle.forward(GenericActivityKey(first)) - val firstActivity = expectActivity { it.getNavigationHandle().key.id == first } - firstActivity.getNavigationHandle().replace(GenericActivityKey(second)) + val firstActivity = expectActivity { it.getNavigationHandle().key().id == first } + firstActivity.getNavigationHandle().replace(GenericActivityKey(second)) - val secondActivity = expectActivity { it.getNavigationHandle().key.id == second } - secondActivity.getNavigationHandle().close() + val secondActivity = expectActivity { it.getNavigationHandle().key().id == second } + secondActivity.getNavigationHandle().close() expectActivity() } diff --git a/enro-core/src/androidTest/java/nav/enro/core/ActivityToFragmentTests.kt b/enro/src/androidTest/java/nav/enro/core/ActivityToFragmentTests.kt similarity index 76% rename from enro-core/src/androidTest/java/nav/enro/core/ActivityToFragmentTests.kt rename to enro/src/androidTest/java/nav/enro/core/ActivityToFragmentTests.kt index 22024073..700e6822 100644 --- a/enro-core/src/androidTest/java/nav/enro/core/ActivityToFragmentTests.kt +++ b/enro/src/androidTest/java/nav/enro/core/ActivityToFragmentTests.kt @@ -1,11 +1,16 @@ package nav.enro.core +import androidx.fragment.app.FragmentActivity import androidx.test.core.app.ActivityScenario -import junit.framework.TestCase.* -import nav.enro.core.internal.SingleFragmentActivity +import junit.framework.TestCase.assertEquals +import nav.enro.* import org.junit.Test import java.util.* +private fun expectSingleFragmentActivity(): FragmentActivity { + return expectActivity { it::class.java.simpleName == "SingleFragmentActivity"} +} + class ActivityToFragmentTests { @Test @@ -16,9 +21,9 @@ class ActivityToFragmentTests { val id = UUID.randomUUID().toString() handle.forward(GenericFragmentKey(id)) - val activity = expectActivity() + val activity = expectSingleFragmentActivity() val activeFragment = activity.supportFragmentManager.primaryNavigationFragment!! - val fragmentHandle = activeFragment.getNavigationHandle() + val fragmentHandle = activeFragment.getNavigationHandle().asTyped() assertEquals(id, fragmentHandle.key.id) } @@ -34,10 +39,10 @@ class ActivityToFragmentTests { GenericFragmentKey(id) ) - val activity = expectActivity() + val activity = expectSingleFragmentActivity() val fragment = expectFragment() - val fragmentHandle = fragment.getNavigationHandle() + val fragmentHandle = fragment.getNavigationHandle().asTyped() assertEquals(id, fragmentHandle.key.id) assertEquals(fragment, activity.supportFragmentManager.primaryNavigationFragment!!) } @@ -53,7 +58,7 @@ class ActivityToFragmentTests { expectActivity() val activeFragment = expectFragment() - val fragmentHandle = activeFragment.getNavigationHandle() + val fragmentHandle = activeFragment.getNavigationHandle().asTyped() assertEquals(id, fragmentHandle.key.id) } @@ -65,9 +70,9 @@ class ActivityToFragmentTests { val id = UUID.randomUUID().toString() handle.replace(ActivityChildFragmentKey(id)) - expectActivity() + val activity = expectSingleFragmentActivity() val activeFragment = expectFragment() - val fragmentHandle = activeFragment.getNavigationHandle() + val fragmentHandle = activeFragment.getNavigationHandle().asTyped() assertEquals(id, fragmentHandle.key.id) fragmentHandle.close() @@ -83,9 +88,9 @@ class ActivityToFragmentTests { val id = UUID.randomUUID().toString() handle.forward(GenericFragmentKey(id)) - val activity = expectActivity() + val activity = expectSingleFragmentActivity() val activeFragment = activity.supportFragmentManager.primaryNavigationFragment!! - val fragmentHandle = activeFragment.getNavigationHandle() + val fragmentHandle = activeFragment.getNavigationHandle().asTyped() assertEquals(id, fragmentHandle.key.id) } @@ -97,9 +102,9 @@ class ActivityToFragmentTests { val id = UUID.randomUUID().toString() handle.replace(ActivityChildFragmentKey(id)) - val activity = expectActivity() + val activity = expectSingleFragmentActivity() val activeFragment = activity.supportFragmentManager.primaryNavigationFragment!! - val fragmentHandle = activeFragment.getNavigationHandle() + val fragmentHandle = activeFragment.getNavigationHandle().asTyped() assertEquals(id, fragmentHandle.key.id) } } \ No newline at end of file diff --git a/enro-core/src/androidTest/java/nav/enro/core/Extensions.kt b/enro/src/androidTest/java/nav/enro/core/Extensions.kt similarity index 91% rename from enro-core/src/androidTest/java/nav/enro/core/Extensions.kt rename to enro/src/androidTest/java/nav/enro/core/Extensions.kt index 28784b50..0f5ea321 100644 --- a/enro-core/src/androidTest/java/nav/enro/core/Extensions.kt +++ b/enro/src/androidTest/java/nav/enro/core/Extensions.kt @@ -8,16 +8,16 @@ import androidx.test.platform.app.InstrumentationRegistry import androidx.test.runner.lifecycle.ActivityLifecycleMonitorRegistry import androidx.test.runner.lifecycle.Stage -inline fun ActivityScenario.getNavigationHandle(): NavigationHandle { - var result: NavigationHandle? = null +inline fun ActivityScenario.getNavigationHandle(): TypedNavigationHandle { + var result: NavigationHandle? = null onActivity{ - result = it.getNavigationHandle() + result = it.getNavigationHandle() } val handle = result ?: throw IllegalStateException("Could not retrieve NavigationHandle from Activity") - val key = handle.key as? T - ?: throw IllegalStateException("Handle was of incorrect type. Expected ${T::class.java.name} but was ${handle.key::class.java.name}") - return handle + val key = handle.key() as? T + ?: throw IllegalStateException("Handle was of incorrect type. Expected ${T::class.java.name} but was ${handle.key()::class.java.name}") + return handle.asTyped() } inline fun expectActivity(crossinline selector: (FragmentActivity) -> Boolean = { it is T }): T { diff --git a/enro-core/src/androidTest/java/nav/enro/core/overrides/ActivityToActivityOverrideTests.kt b/enro/src/androidTest/java/nav/enro/core/overrides/ActivityToActivityOverrideTests.kt similarity index 95% rename from enro-core/src/androidTest/java/nav/enro/core/overrides/ActivityToActivityOverrideTests.kt rename to enro/src/androidTest/java/nav/enro/core/overrides/ActivityToActivityOverrideTests.kt index 5c6df15b..7cf83275 100644 --- a/enro-core/src/androidTest/java/nav/enro/core/overrides/ActivityToActivityOverrideTests.kt +++ b/enro/src/androidTest/java/nav/enro/core/overrides/ActivityToActivityOverrideTests.kt @@ -3,6 +3,10 @@ package nav.enro.core.overrides import android.content.Intent import androidx.test.core.app.ActivityScenario import junit.framework.Assert.assertTrue +import nav.enro.DefaultActivity +import nav.enro.DefaultActivityKey +import nav.enro.GenericActivity +import nav.enro.GenericActivityKey import nav.enro.core.* import nav.enro.core.controller.navigationController import nav.enro.core.executors.createOverride @@ -49,7 +53,7 @@ class ActivityToActivityOverrideTests() { .forward(GenericActivityKey("override test")) expectActivity() - .getNavigationHandle() + .getNavigationHandle() .close() expectActivity() @@ -110,7 +114,7 @@ class ActivityToActivityOverrideTests() { .forward(GenericActivityKey("override test 2")) expectActivity() - .getNavigationHandle() + .getNavigationHandle() .close() expectActivity() diff --git a/enro-core/src/androidTest/java/nav/enro/core/overrides/ActivityToFragmentOverrideTests.kt b/enro/src/androidTest/java/nav/enro/core/overrides/ActivityToFragmentOverrideTests.kt similarity index 96% rename from enro-core/src/androidTest/java/nav/enro/core/overrides/ActivityToFragmentOverrideTests.kt rename to enro/src/androidTest/java/nav/enro/core/overrides/ActivityToFragmentOverrideTests.kt index 5eedfbcb..330699e7 100644 --- a/enro-core/src/androidTest/java/nav/enro/core/overrides/ActivityToFragmentOverrideTests.kt +++ b/enro/src/androidTest/java/nav/enro/core/overrides/ActivityToFragmentOverrideTests.kt @@ -3,12 +3,12 @@ package nav.enro.core.overrides import android.content.Intent import androidx.test.core.app.ActivityScenario import junit.framework.Assert.assertTrue +import nav.enro.* import nav.enro.core.* import nav.enro.core.controller.navigationController import nav.enro.core.executors.createOverride import nav.enro.core.executors.defaultClose import nav.enro.core.executors.defaultLaunch -import nav.enro.core.expectFragment import org.junit.Test class ActivityToFragmentOverrideTests() { @@ -50,7 +50,7 @@ class ActivityToFragmentOverrideTests() { .forward(GenericFragmentKey("override test")) expectFragment() - .getNavigationHandle().close() + .getNavigationHandle().close() expectActivity() @@ -110,7 +110,7 @@ class ActivityToFragmentOverrideTests() { .forward(GenericFragmentKey("override test 2")) expectFragment() - .getNavigationHandle() + .getNavigationHandle() .close() expectActivity() @@ -170,7 +170,7 @@ class ActivityToFragmentOverrideTests() { .forward(ActivityChildFragmentKey("override test 2")) expectFragment() - .getNavigationHandle() + .getNavigationHandle() .close() expectActivity() diff --git a/enro-core/src/androidTest/java/nav/enro/core/overrides/FragmentToActivityOverrideTests.kt b/enro/src/androidTest/java/nav/enro/core/overrides/FragmentToActivityOverrideTests.kt similarity index 91% rename from enro-core/src/androidTest/java/nav/enro/core/overrides/FragmentToActivityOverrideTests.kt rename to enro/src/androidTest/java/nav/enro/core/overrides/FragmentToActivityOverrideTests.kt index 6dec76f4..64fb076d 100644 --- a/enro-core/src/androidTest/java/nav/enro/core/overrides/FragmentToActivityOverrideTests.kt +++ b/enro/src/androidTest/java/nav/enro/core/overrides/FragmentToActivityOverrideTests.kt @@ -3,11 +3,11 @@ package nav.enro.core.overrides import android.content.Intent import androidx.test.core.app.ActivityScenario import junit.framework.Assert.assertTrue +import nav.enro.* import nav.enro.core.* import nav.enro.core.controller.navigationController import nav.enro.core.executors.createOverride import nav.enro.core.executors.defaultLaunch -import nav.enro.core.expectFragment import org.junit.Before import org.junit.Test @@ -44,7 +44,7 @@ class FragmentToActivityOverrideTests() { .forward(GenericFragmentKey("override test")) expectFragment() - .getNavigationHandle() + .getNavigationHandle() .forward(GenericActivityKey("override test 2")) expectActivity() @@ -68,11 +68,11 @@ class FragmentToActivityOverrideTests() { .forward(GenericFragmentKey("override test")) expectFragment() - .getNavigationHandle() + .getNavigationHandle() .forward(GenericActivityKey("override test 2")) expectActivity() - .getNavigationHandle() + .getNavigationHandle() .close() expectFragment() @@ -97,7 +97,7 @@ class FragmentToActivityOverrideTests() { .forward(ActivityChildFragmentKey("override test")) expectFragment() - .getNavigationHandle() + .getNavigationHandle() .forward(GenericActivityKey("override test 2")) expectActivity() @@ -121,11 +121,11 @@ class FragmentToActivityOverrideTests() { .forward(ActivityChildFragmentKey("override test")) expectFragment() - .getNavigationHandle() + .getNavigationHandle() .forward(GenericActivityKey("override test 2")) expectActivity() - .getNavigationHandle() + .getNavigationHandle() .close() expectFragment() diff --git a/enro-core/src/androidTest/java/nav/enro/core/overrides/FragmentToFragmentOverrideTests.kt b/enro/src/androidTest/java/nav/enro/core/overrides/FragmentToFragmentOverrideTests.kt similarity index 91% rename from enro-core/src/androidTest/java/nav/enro/core/overrides/FragmentToFragmentOverrideTests.kt rename to enro/src/androidTest/java/nav/enro/core/overrides/FragmentToFragmentOverrideTests.kt index f0582267..7eb2d160 100644 --- a/enro-core/src/androidTest/java/nav/enro/core/overrides/FragmentToFragmentOverrideTests.kt +++ b/enro/src/androidTest/java/nav/enro/core/overrides/FragmentToFragmentOverrideTests.kt @@ -3,11 +3,11 @@ package nav.enro.core.overrides import android.content.Intent import androidx.test.core.app.ActivityScenario import junit.framework.Assert.assertTrue +import nav.enro.* import nav.enro.core.* import nav.enro.core.controller.navigationController import nav.enro.core.executors.createOverride import nav.enro.core.executors.defaultLaunch -import nav.enro.core.expectFragment import org.junit.Before import org.junit.Test @@ -44,7 +44,7 @@ class FragmentToFragmentOverrideTests() { .forward(GenericFragmentKey("override test")) expectFragment() - .getNavigationHandle() + .getNavigationHandle() .forward(ActivityChildFragmentKey("override test 2")) expectFragment() @@ -68,11 +68,11 @@ class FragmentToFragmentOverrideTests() { .forward(GenericFragmentKey("override test")) expectFragment() - .getNavigationHandle() + .getNavigationHandle() .forward(ActivityChildFragmentKey("override test 2")) expectFragment() - .getNavigationHandle() + .getNavigationHandle() .close() expectFragment() @@ -97,7 +97,7 @@ class FragmentToFragmentOverrideTests() { .forward(ActivityChildFragmentKey("override test")) expectFragment() - .getNavigationHandle() + .getNavigationHandle() .forward(GenericFragmentKey("override test 2")) expectFragment() @@ -121,11 +121,11 @@ class FragmentToFragmentOverrideTests() { .forward(ActivityChildFragmentKey("override test")) expectFragment() - .getNavigationHandle() + .getNavigationHandle() .forward(GenericFragmentKey("override test 2")) expectFragment() - .getNavigationHandle() + .getNavigationHandle() .close() expectFragment() @@ -150,7 +150,7 @@ class FragmentToFragmentOverrideTests() { .forward(ActivityChildFragmentKey("override test")) expectFragment() - .getNavigationHandle() + .getNavigationHandle() .forward(ActivityChildFragmentTwoKey("override test 2")) expectFragment() @@ -174,11 +174,11 @@ class FragmentToFragmentOverrideTests() { .forward(ActivityChildFragmentKey("override test")) expectFragment() - .getNavigationHandle() + .getNavigationHandle() .forward(ActivityChildFragmentTwoKey("override test 2")) expectFragment() - .getNavigationHandle() + .getNavigationHandle() .close() expectFragment() diff --git a/modularised-example/feature/detail/src/main/java/nav/enro/example/detail/Detail.kt b/modularised-example/feature/detail/src/main/java/nav/enro/example/detail/Detail.kt index 421714ce..aa764748 100644 --- a/modularised-example/feature/detail/src/main/java/nav/enro/example/detail/Detail.kt +++ b/modularised-example/feature/detail/src/main/java/nav/enro/example/detail/Detail.kt @@ -8,13 +8,16 @@ import android.widget.TextView import androidx.appcompat.app.AppCompatActivity import androidx.fragment.app.Fragment import nav.enro.annotations.NavigationDestination -import nav.enro.core.getNavigationHandle import nav.enro.core.navigationHandle import nav.enro.example.core.navigation.DetailKey import nav.enro.result.closeWithResult class DetailActivity : AppCompatActivity() { - private val navigation by navigationHandle() + private val navigation by navigationHandle { + onCloseRequested { + closeWithResult(false) + } + } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -23,16 +26,16 @@ class DetailActivity : AppCompatActivity() { text = "Detail View ${navigation.key.id}" setBackgroundColor(0xFFFFFFFF.toInt()) }) - - navigation.onCloseRequested { - navigation.closeWithResult(false) - } } } @NavigationDestination(DetailKey::class) class DetailFragment : Fragment() { - private val navigation by navigationHandle() + private val navigation by navigationHandle { + onCloseRequested { + closeWithResult(false) + } + } override fun onCreateView( inflater: LayoutInflater, @@ -47,9 +50,5 @@ class DetailFragment : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - - navigation.onCloseRequested { - navigation.closeWithResult(false) - } } } From fd09fb4c5c18713c317b86ad6b81c6064e6366a5 Mon Sep 17 00:00:00 2001 From: Isaac Date: Sun, 29 Nov 2020 20:53:08 +1300 Subject: [PATCH 02/24] Update the examples to match the changes to navigation typing --- .../java/nav/enro/annotations/Annotations.kt | 4 +--- .../processor/NavigationDestinationProcessor.kt | 2 -- .../enro/viewmodel/EnroViewModelExtensions.kt | 3 +-- example/src/main/java/nav/enro/example/Home.kt | 3 +-- .../main/java/nav/enro/example/ResultExample.kt | 4 +--- .../main/java/nav/enro/example/SimpleMessage.kt | 17 +++++++++-------- .../main/java/nav/enro/example/SplashScreen.kt | 6 ++++-- .../main/java/nav/enro/example/MainActivity.kt | 6 ++++-- .../main/java/nav/enro/example/login/Login.kt | 16 +++++++++------- 9 files changed, 30 insertions(+), 31 deletions(-) diff --git a/enro-annotations/src/main/java/nav/enro/annotations/Annotations.kt b/enro-annotations/src/main/java/nav/enro/annotations/Annotations.kt index 812176db..7feda4ea 100644 --- a/enro-annotations/src/main/java/nav/enro/annotations/Annotations.kt +++ b/enro-annotations/src/main/java/nav/enro/annotations/Annotations.kt @@ -1,13 +1,11 @@ package nav.enro.annotations import kotlin.reflect.KClass -import java.lang.annotation.RetentionPolicy; @Retention(AnnotationRetention.BINARY) @Target(AnnotationTarget.CLASS) annotation class NavigationDestination( - val key: KClass, - val allowDefault: Boolean = false + val key: KClass ) @Retention(AnnotationRetention.BINARY) diff --git a/enro-processor/src/main/java/nav/enro/processor/NavigationDestinationProcessor.kt b/enro-processor/src/main/java/nav/enro/processor/NavigationDestinationProcessor.kt index b62e4778..d9da6671 100644 --- a/enro-processor/src/main/java/nav/enro/processor/NavigationDestinationProcessor.kt +++ b/enro-processor/src/main/java/nav/enro/processor/NavigationDestinationProcessor.kt @@ -118,7 +118,6 @@ class NavigationDestinationProcessor : BaseProcessor() { getKotlinClass($1T.class), getKotlinClass($2T.class), it -> { - ${if (annotation.allowDefault) "it.defaultKey(new $1T());" else ""} return $3T.INSTANCE; } ) @@ -136,7 +135,6 @@ class NavigationDestinationProcessor : BaseProcessor() { getKotlinClass($1T.class), getKotlinClass($2T.class), it -> { - ${if (annotation.allowDefault) "it.defaultKey(new $1T());" else ""} return $3T.INSTANCE; } ) diff --git a/enro-viewmodel/src/main/java/nav/enro/viewmodel/EnroViewModelExtensions.kt b/enro-viewmodel/src/main/java/nav/enro/viewmodel/EnroViewModelExtensions.kt index e181ed05..a0532447 100644 --- a/enro-viewmodel/src/main/java/nav/enro/viewmodel/EnroViewModelExtensions.kt +++ b/enro-viewmodel/src/main/java/nav/enro/viewmodel/EnroViewModelExtensions.kt @@ -7,7 +7,6 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelStore import nav.enro.core.NavigationHandle -import nav.enro.core.NavigationKey import nav.enro.core.getNavigationHandle import kotlin.properties.ReadOnlyProperty import kotlin.reflect.KClass @@ -24,7 +23,7 @@ class ViewModelNavigationHandleProperty internal constructor( } } -fun ViewModel.navigationHandle(): ViewModelNavigationHandleProperty = +fun ViewModel.navigationHandle(): ViewModelNavigationHandleProperty = ViewModelNavigationHandleProperty(this::class) diff --git a/example/src/main/java/nav/enro/example/Home.kt b/example/src/main/java/nav/enro/example/Home.kt index 2d1007a9..a70497ae 100644 --- a/example/src/main/java/nav/enro/example/Home.kt +++ b/example/src/main/java/nav/enro/example/Home.kt @@ -4,7 +4,6 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import androidx.appcompat.app.AppCompatActivity import androidx.fragment.app.Fragment import kotlinx.android.parcel.Parcelize import kotlinx.android.synthetic.main.fragment_home.* @@ -30,7 +29,7 @@ class HomeFragment : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { launchExample.setOnClickListener { - getNavigationHandle() + getNavigationHandle() .forward(SimpleExampleKey("Start", "Home", listOf("Home"))) } } diff --git a/example/src/main/java/nav/enro/example/ResultExample.kt b/example/src/main/java/nav/enro/example/ResultExample.kt index 62522cb3..b42aa91d 100644 --- a/example/src/main/java/nav/enro/example/ResultExample.kt +++ b/example/src/main/java/nav/enro/example/ResultExample.kt @@ -6,7 +6,6 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment -import androidx.fragment.app.viewModels import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.Observer @@ -15,7 +14,6 @@ import kotlinx.android.parcel.Parcelize import kotlinx.android.synthetic.main.fragment_request_string.* import kotlinx.android.synthetic.main.fragment_result_example.* import nav.enro.annotations.NavigationDestination -import nav.enro.core.NavigationHandle import nav.enro.core.NavigationKey import nav.enro.core.navigationHandle import nav.enro.result.ResultNavigationKey @@ -57,7 +55,7 @@ class RequestExampleFragment : Fragment() { class RequestExampleViewModel() : ViewModel() { - private val navigation by navigationHandle() + private val navigation by navigationHandle() private val mutableResults = MutableLiveData>().apply { emptyList() } val results = mutableResults as LiveData> diff --git a/example/src/main/java/nav/enro/example/SimpleMessage.kt b/example/src/main/java/nav/enro/example/SimpleMessage.kt index 03444c7f..3cf7f2dd 100644 --- a/example/src/main/java/nav/enro/example/SimpleMessage.kt +++ b/example/src/main/java/nav/enro/example/SimpleMessage.kt @@ -14,26 +14,27 @@ import nav.enro.core.navigator.SyntheticDestination data class SimpleMessage( val title: String, val message: String, - val positiveActionInstruction: NavigationInstruction.Open<*>? = null + val positiveActionInstruction: NavigationInstruction.Open? = null ) : NavigationKey @NavigationDestination(SimpleMessage::class) class SimpleMessageDestination : SyntheticDestination { override fun process( - navigationContext: NavigationContext, - instruction: NavigationInstruction.Open + navigationContext: NavigationContext, + instruction: NavigationInstruction.Open ) { + val key = instruction.navigationKey as SimpleMessage val activity = navigationContext.activity AlertDialog.Builder(activity).apply { - setTitle(instruction.navigationKey.title) - setMessage(instruction.navigationKey.message) + setTitle(key.title) + setMessage(key.message) setNegativeButton("Close") { _, _ -> } - if(instruction.navigationKey.positiveActionInstruction != null) { + if(key.positiveActionInstruction != null) { setPositiveButton("Launch") {_, _ -> navigationContext.activity - .getNavigationHandle() - .executeInstruction(instruction.navigationKey.positiveActionInstruction ?: return@setPositiveButton) + .getNavigationHandle() + .executeInstruction(key.positiveActionInstruction) } } diff --git a/example/src/main/java/nav/enro/example/SplashScreen.kt b/example/src/main/java/nav/enro/example/SplashScreen.kt index 46e45891..e031fd04 100644 --- a/example/src/main/java/nav/enro/example/SplashScreen.kt +++ b/example/src/main/java/nav/enro/example/SplashScreen.kt @@ -10,10 +10,12 @@ import nav.enro.core.* @Parcelize class SplashScreenKey : NavigationKey -@NavigationDestination(SplashScreenKey::class, allowDefault = true) +@NavigationDestination(SplashScreenKey::class) class SplashScreenActivity : AppCompatActivity() { - private val navigation by navigationHandle() + private val navigation by navigationHandle { + defaultKey(SplashScreenKey()) + } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) diff --git a/modularised-example/app/src/main/java/nav/enro/example/MainActivity.kt b/modularised-example/app/src/main/java/nav/enro/example/MainActivity.kt index 358e2409..3ae4aacf 100644 --- a/modularised-example/app/src/main/java/nav/enro/example/MainActivity.kt +++ b/modularised-example/app/src/main/java/nav/enro/example/MainActivity.kt @@ -27,10 +27,12 @@ class MainKey : NavigationKey class HiltViewModel @ViewModelInject constructor(): ViewModel() @AndroidEntryPoint -@NavigationDestination(MainKey::class, allowDefault = true) +@NavigationDestination(MainKey::class) class MainActivity : AppCompatActivity() { private val hiltViewModel by viewModels() - private val navigation by navigationHandle() + private val navigation by navigationHandle { + defaultKey(MainKey()) + } override fun onResume() { super.onResume() diff --git a/modularised-example/feature/login/src/main/java/nav/enro/example/login/Login.kt b/modularised-example/feature/login/src/main/java/nav/enro/example/login/Login.kt index f3a02795..e08d26cb 100644 --- a/modularised-example/feature/login/src/main/java/nav/enro/example/login/Login.kt +++ b/modularised-example/feature/login/src/main/java/nav/enro/example/login/Login.kt @@ -1,15 +1,15 @@ package nav.enro.example.login import android.os.Bundle -import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity import androidx.core.widget.doOnTextChanged import androidx.lifecycle.observe -import nav.enro.example.core.data.UserRepository -import kotlinx.android.parcel.Parcelize import kotlinx.android.synthetic.main.login.* import nav.enro.annotations.NavigationDestination -import nav.enro.core.* +import nav.enro.core.forward +import nav.enro.core.navigationHandle +import nav.enro.core.replaceRoot +import nav.enro.example.core.data.UserRepository import nav.enro.example.core.navigation.DashboardKey import nav.enro.example.core.navigation.LoginErrorKey import nav.enro.example.core.navigation.LoginKey @@ -17,12 +17,14 @@ import nav.enro.viewmodel.enroViewModels import nav.enro.viewmodel.navigationHandle @NavigationDestination( - key = LoginKey::class, - allowDefault = true + key = LoginKey::class ) class LoginActivity : AppCompatActivity() { private val viewModel by enroViewModels() + private val navigation by navigationHandle { + defaultKey(LoginKey()) + } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -50,7 +52,7 @@ data class LoginState( class LoginViewModel : nav.enro.example.core.base.SingleStateViewModel() { - private val navigationHandle by navigationHandle() + private val navigationHandle by navigationHandle() private val userRepo = UserRepository.instance From f474c6e96539e11c9bdf87ebad06875849cf14b4 Mon Sep 17 00:00:00 2001 From: Isaac Date: Sun, 29 Nov 2020 23:15:28 +1300 Subject: [PATCH 03/24] Get the multi module example working again --- .../java/nav/enro/example/MainActivity.kt | 8 ++--- .../nav/enro/example/dashboard/Dashboard.kt | 30 +++++++++---------- .../main/java/nav/enro/example/list/List.kt | 18 +++++------ .../java/nav/enro/example/login/LoginError.kt | 9 +++--- 4 files changed, 30 insertions(+), 35 deletions(-) diff --git a/modularised-example/app/src/main/java/nav/enro/example/MainActivity.kt b/modularised-example/app/src/main/java/nav/enro/example/MainActivity.kt index 3ae4aacf..a2913254 100644 --- a/modularised-example/app/src/main/java/nav/enro/example/MainActivity.kt +++ b/modularised-example/app/src/main/java/nav/enro/example/MainActivity.kt @@ -16,10 +16,8 @@ import nav.enro.core.context.activity import nav.enro.core.navigator.SyntheticDestination import nav.enro.example.core.data.UserRepository import nav.enro.example.core.navigation.DashboardKey -import nav.enro.example.core.navigation.DetailKey import nav.enro.example.core.navigation.LaunchKey import nav.enro.example.core.navigation.LoginKey -import nav.enro.result.registerForNavigationResult @Parcelize class MainKey : NavigationKey @@ -57,10 +55,10 @@ class MainActivity : AppCompatActivity() { @NavigationDestination(LaunchKey::class) class LaunchDestination : SyntheticDestination { override fun process( - navigationContext: NavigationContext, - instruction: NavigationInstruction.Open + navigationContext: NavigationContext, + instruction: NavigationInstruction.Open ) { - val navigation = navigationContext.activity.getNavigationHandle() + val navigation = navigationContext.activity.getNavigationHandle() val userRepo = UserRepository.instance val user = userRepo.activeUser val key = when (user) { diff --git a/modularised-example/feature/dashboard/src/main/java/nav/enro/example/dashboard/Dashboard.kt b/modularised-example/feature/dashboard/src/main/java/nav/enro/example/dashboard/Dashboard.kt index 9c4ddc4a..abc97ada 100644 --- a/modularised-example/feature/dashboard/src/main/java/nav/enro/example/dashboard/Dashboard.kt +++ b/modularised-example/feature/dashboard/src/main/java/nav/enro/example/dashboard/Dashboard.kt @@ -3,15 +3,14 @@ package nav.enro.example.dashboard import android.app.Dialog import android.os.Bundle import android.util.Log -import androidx.activity.viewModels import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity import androidx.lifecycle.observe -import nav.enro.example.core.data.SimpleDataRepository -import kotlinx.android.parcel.Parcelize import kotlinx.android.synthetic.main.dashboard.* import nav.enro.annotations.NavigationDestination -import nav.enro.core.* +import nav.enro.core.close +import nav.enro.core.forward +import nav.enro.example.core.data.SimpleDataRepository import nav.enro.example.core.navigation.* import nav.enro.result.registerForNavigationResult import nav.enro.viewmodel.enroViewModels @@ -79,7 +78,8 @@ class DashboardViewModel( private val repo = SimpleDataRepository() - private val navigationHandle by navigationHandle() + private val navigationHandle by navigationHandle() + private val key = navigationHandle.key() private val viewDetail by registerForNavigationResult(navigationHandle) { state = state.copy(userId = "${state.userId} FIRST($it)") @@ -90,19 +90,19 @@ class DashboardViewModel( } init { - val userId = navigationHandle.key.userId + val userId = key.userId val data = repo.getList(userId) state = DashboardState( - userId = navigationHandle.key.userId, + userId = key.userId, closeRequested = false, myPrivateMessageCount = data.count { !it.isPublic && it.ownerId == userId }, myPublicMessageCount = data.count { it.isPublic && it.ownerId == userId }, otherPublicMessageCount = data.count { it.isPublic && it.ownerId != userId } ) - navigationHandle.onCloseRequested { - state = state.copy(closeRequested = true) - } +// navigationHandle.onCloseRequested { +// state = state.copy(closeRequested = true) +// } } fun test(boolean: Boolean) { @@ -112,7 +112,7 @@ class DashboardViewModel( fun onMyPrivateMessagesSelected() { viewDetail.open( ListKey( - userId = navigationHandle.key.userId, + userId = key.userId, filter = ListFilterType.MY_PRIVATE ) ) @@ -121,7 +121,7 @@ class DashboardViewModel( fun onMyPublicMessagesSelected() { viewDetail.open( ListKey( - userId = navigationHandle.key.userId, + userId = key.userId, filter = ListFilterType.MY_PUBLIC ) ) @@ -130,7 +130,7 @@ class DashboardViewModel( fun onOtherMessagesSelected() { viewDetail2.open( ListKey( - userId = navigationHandle.key.userId, + userId = key.userId, filter = ListFilterType.NOT_MY_PUBLIC ) ) @@ -139,7 +139,7 @@ class DashboardViewModel( fun onAllMessagesSelected() { navigationHandle.forward( MasterDetailKey( - userId = navigationHandle.key.userId, + userId = key.userId, filter = ListFilterType.ALL ) ) @@ -148,7 +148,7 @@ class DashboardViewModel( fun onUserInfoSelected() { navigationHandle.forward( UserKey( - userId = navigationHandle.key.userId + userId = key.userId ) ) } diff --git a/modularised-example/feature/list/src/main/java/nav/enro/example/list/List.kt b/modularised-example/feature/list/src/main/java/nav/enro/example/list/List.kt index 0a69ceb0..72cc9011 100644 --- a/modularised-example/feature/list/src/main/java/nav/enro/example/list/List.kt +++ b/modularised-example/feature/list/src/main/java/nav/enro/example/list/List.kt @@ -7,7 +7,6 @@ import android.view.View import android.view.ViewGroup import android.widget.TextView import androidx.fragment.app.Fragment -import androidx.fragment.app.viewModels import androidx.hilt.lifecycle.ViewModelInject import androidx.lifecycle.observe import androidx.recyclerview.widget.DiffUtil @@ -17,14 +16,12 @@ import androidx.recyclerview.widget.RecyclerView import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.scopes.ActivityRetainedScoped import nav.enro.annotations.NavigationDestination -import nav.enro.core.NavigationHandle import nav.enro.example.core.base.SingleStateViewModel import nav.enro.example.core.data.SimpleData import nav.enro.example.core.data.SimpleDataRepository import nav.enro.example.core.navigation.DetailKey import nav.enro.example.core.navigation.ListFilterType import nav.enro.example.core.navigation.ListKey -import nav.enro.result.closeWithResult import nav.enro.result.registerForNavigationResult import nav.enro.viewmodel.enroViewModels import nav.enro.viewmodel.navigationHandle @@ -76,18 +73,19 @@ class ListViewModel @ViewModelInject constructor( ) : SingleStateViewModel() { private val repo = SimpleDataRepository() - private val navigation by navigationHandle() + private val navigation by navigationHandle() + private val key = navigation.key() init { hiltDependency.doSomething() - val userId = navigation.key.userId + val userId = key.userId state = ListState( userId = userId, - filter = navigation.key.filter, + filter = key.filter, items = repo.getList(userId) .filter { - when (navigation.key.filter) { + when (key.filter) { ListFilterType.ALL -> true ListFilterType.MY_PUBLIC -> it.ownerId == userId && it.isPublic ListFilterType.MY_PRIVATE -> it.ownerId == userId && !it.isPublic @@ -97,9 +95,9 @@ class ListViewModel @ViewModelInject constructor( } ) - navigation.onCloseRequested { - navigation.closeWithResult(state.result) - } +// navigation.onCloseRequested { +// navigation.closeWithResult(state.result) +// } } fun setResult(it: Boolean) { diff --git a/modularised-example/feature/login/src/main/java/nav/enro/example/login/LoginError.kt b/modularised-example/feature/login/src/main/java/nav/enro/example/login/LoginError.kt index ccbe7abd..03bad0a1 100644 --- a/modularised-example/feature/login/src/main/java/nav/enro/example/login/LoginError.kt +++ b/modularised-example/feature/login/src/main/java/nav/enro/example/login/LoginError.kt @@ -6,10 +6,8 @@ import android.os.Bundle import androidx.fragment.app.DialogFragment import nav.enro.annotations.NavigationDestination import nav.enro.core.NavigationInstruction -import nav.enro.core.NavigationKey import nav.enro.core.context.NavigationContext import nav.enro.core.context.activity -import nav.enro.core.getNavigationHandle import nav.enro.core.navigationHandle import nav.enro.core.navigator.SyntheticDestination import nav.enro.example.core.navigation.LoginErrorKey @@ -30,13 +28,14 @@ class LoginErrorFragment : DialogFragment() { @NavigationDestination(LoginErrorKey::class) class LoginErrorDestination : SyntheticDestination { override fun process( - navigationContext: NavigationContext, - instruction: NavigationInstruction.Open + navigationContext: NavigationContext, + instruction: NavigationInstruction.Open ) { val activity = navigationContext.activity + val key = instruction.navigationKey as LoginErrorKey AlertDialog.Builder(activity) .setTitle("Error!") - .setMessage("Whoops! It looks like '${instruction.navigationKey.errorUser}' isn't a valid user.\n\nPlease try again.") + .setMessage("Whoops! It looks like '${key.errorUser}' isn't a valid user.\n\nPlease try again.") .setNegativeButton("Close") { _, _ -> } .show() } From c9a4f0229f3c5c71f4a7f86e00115af7b8d8ffb5 Mon Sep 17 00:00:00 2001 From: Isaac Date: Mon, 30 Nov 2020 00:07:59 +1300 Subject: [PATCH 04/24] Make TypedNavigationHandle an extension of NavigationHandle --- .../java/nav/enro/core/NavigationHandle.kt | 57 ++++++++++++------- .../nav/enro/core/NavigationHandleProperty.kt | 28 +++++---- .../core/controller/NavigationController.kt | 9 ++- .../enro/core/executors/NavigationExecutor.kt | 1 + .../handle/NavigationHandleViewModel.kt | 12 ++-- .../java/nav/enro/core/navigator/Synthetic.kt | 1 + .../java/nav/enro/core/plugins/EnroLogger.kt | 7 +-- .../androidTest/java/nav/enro/TestViews.kt | 5 +- .../nav/enro/core/ActivityToActivityTests.kt | 6 +- .../java/nav/enro/core/Extensions.kt | 4 +- 10 files changed, 76 insertions(+), 54 deletions(-) diff --git a/enro-core/src/main/java/nav/enro/core/NavigationHandle.kt b/enro-core/src/main/java/nav/enro/core/NavigationHandle.kt index 7311d6c8..32e6ec3d 100644 --- a/enro-core/src/main/java/nav/enro/core/NavigationHandle.kt +++ b/enro-core/src/main/java/nav/enro/core/NavigationHandle.kt @@ -1,28 +1,55 @@ package nav.enro.core import android.os.Bundle +import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleOwner import nav.enro.core.controller.NavigationController +import kotlin.reflect.KClass interface NavigationHandle : LifecycleOwner { val id: String val controller: NavigationController val additionalData: Bundle - fun key(): T + val key: NavigationKey fun executeInstruction(navigationInstruction: NavigationInstruction) } -class TypedNavigationHandle(private val navigationHandle: NavigationHandle) { - val id: String get() = navigationHandle.id - val key: T get() = navigationHandle.key() - val additionalData: Bundle get() = navigationHandle.additionalData - val controller: NavigationController get() = navigationHandle.controller +interface TypedNavigationHandle : NavigationHandle { + override val id: String + override val controller: NavigationController + override val additionalData: Bundle + override val key: T + override fun executeInstruction(navigationInstruction: NavigationInstruction) +} + +@PublishedApi +internal class TypedNavigationHandleImpl( + private val navigationHandle: NavigationHandle +): TypedNavigationHandle { + override val id: String get() = navigationHandle.id + override val controller: NavigationController get() = navigationHandle.controller + override val additionalData: Bundle get() = navigationHandle.additionalData + override val key: T get() = navigationHandle.key as T + + override fun getLifecycle(): Lifecycle = navigationHandle.lifecycle - fun executeInstruction(navigationInstruction: NavigationInstruction) = navigationHandle.executeInstruction(navigationInstruction) + override fun executeInstruction(navigationInstruction: NavigationInstruction) = navigationHandle.executeInstruction(navigationInstruction) } -fun NavigationHandle.asTyped(): TypedNavigationHandle { - return TypedNavigationHandle(this) +fun NavigationHandle.asTyped(type: KClass): TypedNavigationHandle { + val keyType = key::class + val isValidType = type.java.isAssignableFrom(keyType.java) + if(!isValidType) { + throw IllegalStateException("Failed to cast NavigationHandle with key $key to TypedNavigationHandle<${type.simpleName}>") + } + return TypedNavigationHandleImpl(this) +} + +inline fun NavigationHandle.asTyped(): TypedNavigationHandle { + if(key !is T) { + throw IllegalStateException("Failed to cast NavigationHandle with key $key to TypedNavigationHandle<${T::class.java.simpleName}>") + } + return TypedNavigationHandleImpl(this) } fun NavigationHandle.forward(key: NavigationKey, vararg childKeys: NavigationKey) = @@ -35,16 +62,4 @@ fun NavigationHandle.replaceRoot(key: NavigationKey, vararg childKeys: Navigatio executeInstruction(NavigationInstruction.Open(NavigationDirection.REPLACE_ROOT, key, childKeys.toList())) fun NavigationHandle.close() = - executeInstruction(NavigationInstruction.Close) - -fun TypedNavigationHandle<*>.forward(key: NavigationKey, vararg childKeys: NavigationKey) = - executeInstruction(NavigationInstruction.Open(NavigationDirection.FORWARD, key, childKeys.toList())) - -fun TypedNavigationHandle<*>.replace(key: NavigationKey, vararg childKeys: NavigationKey) = - executeInstruction(NavigationInstruction.Open(NavigationDirection.REPLACE, key, childKeys.toList())) - -fun TypedNavigationHandle<*>.replaceRoot(key: NavigationKey, vararg childKeys: NavigationKey) = - executeInstruction(NavigationInstruction.Open(NavigationDirection.REPLACE_ROOT, key, childKeys.toList())) - -fun TypedNavigationHandle<*>.close() = executeInstruction(NavigationInstruction.Close) \ No newline at end of file diff --git a/enro-core/src/main/java/nav/enro/core/NavigationHandleProperty.kt b/enro-core/src/main/java/nav/enro/core/NavigationHandleProperty.kt index 8cff0893..e4870b2f 100644 --- a/enro-core/src/main/java/nav/enro/core/NavigationHandleProperty.kt +++ b/enro-core/src/main/java/nav/enro/core/NavigationHandleProperty.kt @@ -13,16 +13,18 @@ import nav.enro.core.internal.handle.NavigationHandleViewModel import java.lang.ref.WeakReference import kotlin.collections.set import kotlin.properties.ReadOnlyProperty +import kotlin.reflect.KClass import kotlin.reflect.KProperty class NavigationHandleProperty @PublishedApi internal constructor( private val lifecycleOwner: LifecycleOwner, private val viewModelStoreOwner: ViewModelStoreOwner, - private val configBuilder: NavigationHandleConfiguration.() -> Unit = {} + private val configBuilder: NavigationHandleConfiguration.() -> Unit = {}, + private val keyType: KClass ) : ReadOnlyProperty> { - private val config = NavigationHandleConfiguration().apply(configBuilder) + private val config = NavigationHandleConfiguration(keyType).apply(configBuilder) private val navigationHandle: TypedNavigationHandle by lazy { val navigationHandle = ViewModelProvider(viewModelStoreOwner, ViewModelProvider.NewInstanceFactory()) @@ -30,7 +32,7 @@ class NavigationHandleProperty @PublishedApi internal const config.applyTo(navigationHandle) - return@lazy navigationHandle.asTyped() + return@lazy TypedNavigationHandleImpl(navigationHandle) } init { @@ -58,7 +60,9 @@ class NavigationHandleProperty @PublishedApi internal const } } -class NavigationHandleConfiguration @PublishedApi internal constructor() { +class NavigationHandleConfiguration @PublishedApi internal constructor( + private val keyType: KClass +) { private var childContainers: List = listOf() private var defaultKey: T? = null @@ -79,25 +83,27 @@ class NavigationHandleConfiguration @PublishedApi internal co internal fun applyTo(navigationHandleViewModel: NavigationHandleViewModel) { navigationHandleViewModel.childContainers = childContainers navigationHandleViewModel.defaultKey = defaultKey - navigationHandleViewModel.internalOnCloseRequested = { onCloseRequested(navigationHandleViewModel.asTyped()) } + navigationHandleViewModel.internalOnCloseRequested = { onCloseRequested(navigationHandleViewModel.asTyped(keyType)) } } } -fun FragmentActivity.navigationHandle( - config: NavigationHandleConfiguration.() -> Unit = {} +inline fun FragmentActivity.navigationHandle( + noinline config: NavigationHandleConfiguration.() -> Unit = {} ): NavigationHandleProperty = NavigationHandleProperty( lifecycleOwner = this, viewModelStoreOwner = this, - configBuilder = config + configBuilder = config, + keyType = T::class ) -fun Fragment.navigationHandle( - config: NavigationHandleConfiguration.() -> Unit = {} +inline fun Fragment.navigationHandle( + noinline config: NavigationHandleConfiguration.() -> Unit = {} ): NavigationHandleProperty = NavigationHandleProperty( lifecycleOwner = this, viewModelStoreOwner = this, - configBuilder = config + configBuilder = config, + keyType = T::class ) fun FragmentActivity.getNavigationHandle(): NavigationHandle = diff --git a/enro-core/src/main/java/nav/enro/core/controller/NavigationController.kt b/enro-core/src/main/java/nav/enro/core/controller/NavigationController.kt index ed933756..51567140 100644 --- a/enro-core/src/main/java/nav/enro/core/controller/NavigationController.kt +++ b/enro-core/src/main/java/nav/enro/core/controller/NavigationController.kt @@ -93,21 +93,23 @@ class NavigationController( if (openOverrideFor(navigationContext, navigator, instruction)) return when (navigator) { is ActivityNavigator -> DefaultActivityExecutor.open( - ExecutorArgs( + ExecutorArgs( navigationContext, navigator, + instruction.navigationKey, instruction.setParentInstruction(navigationContext, navigator) ) ) is FragmentNavigator -> DefaultFragmentExecutor.open( - ExecutorArgs( + ExecutorArgs( navigationContext, navigator, + instruction.navigationKey, instruction.setParentInstruction(navigationContext, navigator) ) ) is SyntheticNavigator -> (navigator.destination as SyntheticDestination) - .process(navigationContext, instruction as NavigationInstruction.Open) + .process(navigationContext, instruction.navigationKey, instruction) } } @@ -173,6 +175,7 @@ class NavigationController( ExecutorArgs( fromContext, navigator, + instruction.navigationKey, instruction.setParentInstruction(fromContext, navigator) ) ) diff --git a/enro-core/src/main/java/nav/enro/core/executors/NavigationExecutor.kt b/enro-core/src/main/java/nav/enro/core/executors/NavigationExecutor.kt index fc153e19..3d08583b 100644 --- a/enro-core/src/main/java/nav/enro/core/executors/NavigationExecutor.kt +++ b/enro-core/src/main/java/nav/enro/core/executors/NavigationExecutor.kt @@ -10,6 +10,7 @@ import kotlin.reflect.KClass class ExecutorArgs( val fromContext: NavigationContext, val navigator: Navigator, + val key: KeyType, val instruction: NavigationInstruction.Open ) diff --git a/enro-core/src/main/java/nav/enro/core/internal/handle/NavigationHandleViewModel.kt b/enro-core/src/main/java/nav/enro/core/internal/handle/NavigationHandleViewModel.kt index 1ee929ad..2a3cfdd4 100644 --- a/enro-core/src/main/java/nav/enro/core/internal/handle/NavigationHandleViewModel.kt +++ b/enro-core/src/main/java/nav/enro/core/internal/handle/NavigationHandleViewModel.kt @@ -21,6 +21,11 @@ internal class NavigationHandleViewModel : ViewModel(), NavigationHandle { internal val hasKey get() = rawKey != null internal var rawKey: NavigationKey? = null internal var defaultKey: NavigationKey? = null + + override val key: NavigationKey get() { + return rawKey + ?: throw IllegalStateException("This NavigationHandle has no NavigationKey bound") + } override lateinit var id: String override lateinit var controller: NavigationController override lateinit var additionalData: Bundle @@ -96,13 +101,6 @@ internal class NavigationHandleViewModel : ViewModel(), NavigationHandle { executePendingInstruction() } - override fun key(): T { - if(rawKey == null) { - throw IllegalStateException("This NavigationHandle has no NavigationKey bound") - } - return rawKey as T - } - private fun executePendingInstruction() { if (Looper.getMainLooper() != Looper.myLooper()) { Handler(Looper.getMainLooper()).post { executePendingInstruction() } diff --git a/enro-core/src/main/java/nav/enro/core/navigator/Synthetic.kt b/enro-core/src/main/java/nav/enro/core/navigator/Synthetic.kt index 956cf6c3..b63d102b 100644 --- a/enro-core/src/main/java/nav/enro/core/navigator/Synthetic.kt +++ b/enro-core/src/main/java/nav/enro/core/navigator/Synthetic.kt @@ -7,6 +7,7 @@ import nav.enro.core.context.NavigationContext interface SyntheticDestination { fun process( navigationContext: NavigationContext, + key: T, instruction: NavigationInstruction.Open ) } diff --git a/enro-core/src/main/java/nav/enro/core/plugins/EnroLogger.kt b/enro-core/src/main/java/nav/enro/core/plugins/EnroLogger.kt index ad62f3ce..442e088b 100644 --- a/enro-core/src/main/java/nav/enro/core/plugins/EnroLogger.kt +++ b/enro-core/src/main/java/nav/enro/core/plugins/EnroLogger.kt @@ -2,18 +2,17 @@ package nav.enro.core.plugins import android.util.Log import nav.enro.core.NavigationHandle -import nav.enro.core.NavigationKey class EnroLogger : EnroPlugin() { override fun onOpened(navigationHandle: NavigationHandle) { - Log.d("Enro", "Opened: ${navigationHandle.key()}") + Log.d("Enro", "Opened: ${navigationHandle.key}") } override fun onActive(navigationHandle: NavigationHandle) { - Log.d("Enro", "Active: ${navigationHandle.key()}") + Log.d("Enro", "Active: ${navigationHandle.key}") } override fun onClosed(navigationHandle: NavigationHandle) { - Log.d("Enro", "Closed: ${navigationHandle.key()}") + Log.d("Enro", "Closed: ${navigationHandle.key}") } } \ No newline at end of file diff --git a/enro/src/androidTest/java/nav/enro/TestViews.kt b/enro/src/androidTest/java/nav/enro/TestViews.kt index 29cc5940..712f4dfd 100644 --- a/enro/src/androidTest/java/nav/enro/TestViews.kt +++ b/enro/src/androidTest/java/nav/enro/TestViews.kt @@ -11,14 +11,13 @@ import android.widget.LinearLayout import android.widget.TextView import androidx.appcompat.app.AppCompatActivity import androidx.fragment.app.Fragment -import nav.enro.core.NavigationKey import nav.enro.core.getNavigationHandle abstract class TestActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val key = try { - getNavigationHandle().key() + getNavigationHandle().key } catch (t: Throwable) { Log.e("TestActivity", "Failed to open!", t) return @@ -55,7 +54,7 @@ abstract class TestFragment : Fragment() { savedInstanceState: Bundle? ): View? { val key = try { - getNavigationHandle().key() + getNavigationHandle().key } catch (t: Throwable) { Log.e("TestFragment", "Failed to open!", t) return null diff --git a/enro/src/androidTest/java/nav/enro/core/ActivityToActivityTests.kt b/enro/src/androidTest/java/nav/enro/core/ActivityToActivityTests.kt index 69e36564..2c74e5d0 100644 --- a/enro/src/androidTest/java/nav/enro/core/ActivityToActivityTests.kt +++ b/enro/src/androidTest/java/nav/enro/core/ActivityToActivityTests.kt @@ -60,7 +60,7 @@ class ActivityToActivityTests { ) expectActivity { - it.getNavigationHandle().key().id == id + it.getNavigationHandle().asTyped().key.id == id } } @@ -131,10 +131,10 @@ class ActivityToActivityTests { val handle = scenario.getNavigationHandle() handle.forward(GenericActivityKey(first)) - val firstActivity = expectActivity { it.getNavigationHandle().key().id == first } + val firstActivity = expectActivity { it.getNavigationHandle().asTyped().key.id == first } firstActivity.getNavigationHandle().replace(GenericActivityKey(second)) - val secondActivity = expectActivity { it.getNavigationHandle().key().id == second } + val secondActivity = expectActivity { it.getNavigationHandle().asTyped().key.id == second } secondActivity.getNavigationHandle().close() expectActivity() diff --git a/enro/src/androidTest/java/nav/enro/core/Extensions.kt b/enro/src/androidTest/java/nav/enro/core/Extensions.kt index 0f5ea321..295bc8a3 100644 --- a/enro/src/androidTest/java/nav/enro/core/Extensions.kt +++ b/enro/src/androidTest/java/nav/enro/core/Extensions.kt @@ -15,8 +15,8 @@ inline fun ActivityScenario.get } val handle = result ?: throw IllegalStateException("Could not retrieve NavigationHandle from Activity") - val key = handle.key() as? T - ?: throw IllegalStateException("Handle was of incorrect type. Expected ${T::class.java.name} but was ${handle.key()::class.java.name}") + val key = handle.key as? T + ?: throw IllegalStateException("Handle was of incorrect type. Expected ${T::class.java.name} but was ${handle.key::class.java.name}") return handle.asTyped() } From c7eaa6c27311d5fd73624649892d90d10a670717 Mon Sep 17 00:00:00 2001 From: Isaac Date: Mon, 30 Nov 2020 02:02:17 +1300 Subject: [PATCH 05/24] Continue overhaul, slimmed down the navigation context, add additional tests, improve support for unbound navigation contexts (i.e. non-enro activities or fragments) --- .../nav/enro/core/NavigationAnimations.kt | 10 +- .../nav/enro/core/NavigationHandleProperty.kt | 41 ++++--- .../main/java/nav/enro/core/NavigationKey.kt | 10 +- .../java/nav/enro/core/context/Extensions.kt | 3 +- .../enro/core/context/NavigationContext.kt | 35 ++---- .../core/controller/NavigationController.kt | 33 ++++-- .../core/executors/DefaultFragmentExecutor.kt | 5 +- .../java/nav/enro/core/internal/Extensions.kt | 2 +- .../handle/NavigationHandleActivityBinder.kt | 27 +++-- .../handle/NavigationHandleFragmentBinder.kt | 28 +++-- .../handle/NavigationHandleViewModel.kt | 88 +++++++++------ .../java/nav/enro/core/navigator/Navigator.kt | 7 ++ enro/src/androidTest/AndroidManifest.xml | 1 + .../java/nav/enro/TestDestinations.kt | 18 ++- .../nav/enro/core/ActivityToActivityTests.kt | 19 +++- .../java/nav/enro/core/Extensions.kt | 8 ++ .../nav/enro/core/UnboundActivitiesTest.kt | 92 +++++++++++++++ .../nav/enro/core/UnboundFragmentsTest.kt | 105 ++++++++++++++++++ .../ActivityToActivityOverrideTests.kt | 51 ++++++++- 19 files changed, 451 insertions(+), 132 deletions(-) create mode 100644 enro/src/androidTest/java/nav/enro/core/UnboundActivitiesTest.kt create mode 100644 enro/src/androidTest/java/nav/enro/core/UnboundFragmentsTest.kt diff --git a/enro-core/src/main/java/nav/enro/core/NavigationAnimations.kt b/enro-core/src/main/java/nav/enro/core/NavigationAnimations.kt index bf254266..79b91785 100644 --- a/enro-core/src/main/java/nav/enro/core/NavigationAnimations.kt +++ b/enro-core/src/main/java/nav/enro/core/NavigationAnimations.kt @@ -9,6 +9,8 @@ import nav.enro.core.context.NavigationContext import nav.enro.core.context.activity import nav.enro.core.context.navigationContext import nav.enro.core.internal.getAttributeResourceId +import nav.enro.core.internal.navigationHandle +import nav.enro.core.navigator.NavigatorAnimations import nav.enro.core.navigator.toResource sealed class NavigationAnimations : Parcelable { @@ -86,7 +88,8 @@ private fun animationsForOpen( val theme = context.activity.theme val navigator = context.navigator - val navigatorAnimations = navigator.animations.toResource(theme) + val navigatorAnimations = navigator?.animations?.toResource(theme) + ?: NavigatorAnimations.default.toResource(theme) val instructionAnimations = navigationInstruction.animations?.toResource(theme) return when { @@ -118,8 +121,9 @@ private fun animationsForClose( val theme = context.activity.theme val navigator = context.navigator - val navigatorAnimations = navigator.animations.toResource(theme) - val animations = context.instruction?.animations?.toResource(theme) + val navigatorAnimations = navigator?.animations?.toResource(theme) + ?: NavigatorAnimations.default.toResource(theme) + val animations = context.navigationHandle().instruction.animations?.toResource(theme) return when { animations != null -> AnimationPair( diff --git a/enro-core/src/main/java/nav/enro/core/NavigationHandleProperty.kt b/enro-core/src/main/java/nav/enro/core/NavigationHandleProperty.kt index e4870b2f..9f60fc98 100644 --- a/enro-core/src/main/java/nav/enro/core/NavigationHandleProperty.kt +++ b/enro-core/src/main/java/nav/enro/core/NavigationHandleProperty.kt @@ -1,15 +1,13 @@ package nav.enro.core -import androidx.activity.viewModels import androidx.annotation.IdRes import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentActivity -import androidx.fragment.app.viewModels import androidx.lifecycle.LifecycleOwner -import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelStoreOwner import nav.enro.core.context.ChildContainer import nav.enro.core.internal.handle.NavigationHandleViewModel +import nav.enro.core.internal.handle.getNavigationHandleViewModel import java.lang.ref.WeakReference import kotlin.collections.set import kotlin.properties.ReadOnlyProperty @@ -27,11 +25,7 @@ class NavigationHandleProperty @PublishedApi internal const private val config = NavigationHandleConfiguration(keyType).apply(configBuilder) private val navigationHandle: TypedNavigationHandle by lazy { - val navigationHandle = ViewModelProvider(viewModelStoreOwner, ViewModelProvider.NewInstanceFactory()) - .get(NavigationHandleViewModel::class.java) - - config.applyTo(navigationHandle) - + val navigationHandle = viewModelStoreOwner.getNavigationHandleViewModel() return@lazy TypedNavigationHandleImpl(navigationHandle) } @@ -46,16 +40,18 @@ class NavigationHandleProperty @PublishedApi internal const companion object { internal val pendingProperties = mutableMapOf>>() - fun applyPending(activity: FragmentActivity) { - val pending = pendingProperties[activity.hashCode()] ?: return - pending.get()?.navigationHandle.hashCode() + fun getPendingConfig(activity: FragmentActivity): NavigationHandleConfiguration<*>? { + val pending = pendingProperties[activity.hashCode()] ?: return null + val config = pending.get()?.config pendingProperties.remove(activity.hashCode()) + return config } - fun applyPending(fragment: Fragment) { - val pending = pendingProperties[fragment.hashCode()] ?: return - pending.get()?.navigationHandle.hashCode() + fun getPendingConfig(fragment: Fragment): NavigationHandleConfiguration<*>? { + val pending = pendingProperties[fragment.hashCode()] ?: return null + val config = pending.get()?.config pendingProperties.remove(fragment.hashCode()) + return config } } } @@ -63,10 +59,14 @@ class NavigationHandleProperty @PublishedApi internal const class NavigationHandleConfiguration @PublishedApi internal constructor( private val keyType: KClass ) { + internal var childContainers: List = listOf() + private set + + internal var defaultKey: T? = null + private set - private var childContainers: List = listOf() - private var defaultKey: T? = null - private var onCloseRequested: TypedNavigationHandle.() -> Unit = { close() } + internal var onCloseRequested: TypedNavigationHandle.() -> Unit = { close() } + private set fun container(@IdRes containerId: Int, accept: (NavigationKey) -> Boolean = { true }) { childContainers = childContainers + ChildContainer(containerId, accept) @@ -82,7 +82,6 @@ class NavigationHandleConfiguration @PublishedApi internal co internal fun applyTo(navigationHandleViewModel: NavigationHandleViewModel) { navigationHandleViewModel.childContainers = childContainers - navigationHandleViewModel.defaultKey = defaultKey navigationHandleViewModel.internalOnCloseRequested = { onCloseRequested(navigationHandleViewModel.asTyped(keyType)) } } } @@ -106,8 +105,6 @@ inline fun Fragment.navigationHandle( keyType = T::class ) -fun FragmentActivity.getNavigationHandle(): NavigationHandle = - viewModels { ViewModelProvider.NewInstanceFactory() } .value +fun FragmentActivity.getNavigationHandle(): NavigationHandle = getNavigationHandleViewModel() -fun Fragment.getNavigationHandle(): NavigationHandle = - viewModels { ViewModelProvider.NewInstanceFactory() } .value \ No newline at end of file +fun Fragment.getNavigationHandle(): NavigationHandle = getNavigationHandleViewModel() \ No newline at end of file diff --git a/enro-core/src/main/java/nav/enro/core/NavigationKey.kt b/enro-core/src/main/java/nav/enro/core/NavigationKey.kt index e8b33a0b..15973994 100644 --- a/enro-core/src/main/java/nav/enro/core/NavigationKey.kt +++ b/enro-core/src/main/java/nav/enro/core/NavigationKey.kt @@ -1,5 +1,13 @@ package nav.enro.core +import android.os.Bundle import android.os.Parcelable +import kotlinx.android.parcel.Parcelize -interface NavigationKey : Parcelable \ No newline at end of file +interface NavigationKey : Parcelable + +@Parcelize +internal class NoNavigationKeyBound( + val contextType: Class<*>, + val arguments: Bundle? +) : NavigationKey \ No newline at end of file diff --git a/enro-core/src/main/java/nav/enro/core/context/Extensions.kt b/enro-core/src/main/java/nav/enro/core/context/Extensions.kt index 555e77e5..ce845537 100644 --- a/enro-core/src/main/java/nav/enro/core/context/Extensions.kt +++ b/enro-core/src/main/java/nav/enro/core/context/Extensions.kt @@ -9,6 +9,7 @@ import androidx.fragment.app.viewModels import androidx.lifecycle.ViewModelProvider import nav.enro.core.NavigationKey import nav.enro.core.internal.handle.NavigationHandleViewModel +import nav.enro.core.internal.navigationHandle import nav.enro.core.navigator.FragmentHost val NavigationContext.fragment get() = contextReference @@ -23,7 +24,7 @@ internal fun NavigationContext<*>.fragmentHostFor(key: NavigationKey): FragmentH val primaryFragment = childFragmentManager.primaryNavigationFragment val activeContainerId = primaryFragment?.getContainerId() - val visibleContainers = childContainers.filter { + val visibleContainers = navigationHandle().childContainers.filter { when (contextReference) { is FragmentActivity -> contextReference.findViewById(it.containerId).isVisible is Fragment -> contextReference.requireView().findViewById(it.containerId).isVisible diff --git a/enro-core/src/main/java/nav/enro/core/context/NavigationContext.kt b/enro-core/src/main/java/nav/enro/core/context/NavigationContext.kt index ba93f155..0019196b 100644 --- a/enro-core/src/main/java/nav/enro/core/context/NavigationContext.kt +++ b/enro-core/src/main/java/nav/enro/core/context/NavigationContext.kt @@ -5,7 +5,6 @@ import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentManager import androidx.lifecycle.Lifecycle -import nav.enro.core.NavigationInstruction import nav.enro.core.NavigationKey import nav.enro.core.controller.NavigationController import nav.enro.core.controller.navigationController @@ -19,52 +18,32 @@ data class ChildContainer( ) sealed class NavigationContext( - val contextReference: ContextType, - val instruction: NavigationInstruction.Open? + val contextReference: ContextType ) { abstract val controller: NavigationController abstract val lifecycle: Lifecycle abstract val childFragmentManager: FragmentManager - abstract val id: String - val key: NavigationKey? by lazy { - instruction?.navigationKey + internal open val navigator: Navigator? by lazy { + controller.navigatorForContextType(contextReference::class) as? Navigator } - - internal open val navigator: Navigator by lazy { - controller.navigatorForContextType(contextReference::class) as Navigator - } - - internal val pendingKeys: List by lazy { - instruction?.children.orEmpty() - } - - internal val parentInstruction by lazy { - instruction?.parentInstruction - } - - internal var childContainers = listOf() } internal class ActivityContext( contextReference: ContextType, - instruction: NavigationInstruction.Open?, - override val id: String -) : NavigationContext(contextReference, instruction) { +) : NavigationContext(contextReference) { override val controller get() = contextReference.application.navigationController override val lifecycle get() = contextReference.lifecycle - override val navigator get() = super.navigator as ActivityNavigator + override val navigator get() = super.navigator as? ActivityNavigator override val childFragmentManager get() = contextReference.supportFragmentManager } internal class FragmentContext( contextReference: ContextType, - instruction: NavigationInstruction.Open?, - override val id: String -) : NavigationContext(contextReference, instruction) { +) : NavigationContext(contextReference) { override val controller get() = contextReference.requireActivity().application.navigationController override val lifecycle get() = contextReference.lifecycle - override val navigator get() = super.navigator as FragmentNavigator + override val navigator get() = super.navigator as? FragmentNavigator override val childFragmentManager get() = contextReference.childFragmentManager } diff --git a/enro-core/src/main/java/nav/enro/core/controller/NavigationController.kt b/enro-core/src/main/java/nav/enro/core/controller/NavigationController.kt index 51567140..57ca3568 100644 --- a/enro-core/src/main/java/nav/enro/core/controller/NavigationController.kt +++ b/enro-core/src/main/java/nav/enro/core/controller/NavigationController.kt @@ -3,10 +3,7 @@ package nav.enro.core.controller import android.app.Application import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentActivity -import nav.enro.core.NavigationDirection -import nav.enro.core.NavigationHandle -import nav.enro.core.NavigationInstruction -import nav.enro.core.NavigationKey +import nav.enro.core.* import nav.enro.core.context.ActivityContext import nav.enro.core.context.FragmentContext import nav.enro.core.context.NavigationContext @@ -20,6 +17,7 @@ import nav.enro.core.internal.SingleFragmentActivity import nav.enro.core.internal.SingleFragmentKey import nav.enro.core.internal.handle.NavigationHandleActivityBinder import nav.enro.core.internal.handle.NavigationHandleViewModel +import nav.enro.core.internal.navigationHandle import nav.enro.core.navigator.* import nav.enro.core.plugins.EnroHilt import nav.enro.core.plugins.EnroPlugin @@ -53,8 +51,11 @@ class NavigationController( createActivityNavigator() } + val noKeyProvidedNavigator = NavigatorDefinition(NoKeyNavigator(), emptyList()) + listOf( - singleFragmentNavigator + singleFragmentNavigator, + noKeyProvidedNavigator ) } @@ -110,6 +111,8 @@ class NavigationController( ) is SyntheticNavigator -> (navigator.destination as SyntheticDestination) .process(navigationContext, instruction.navigationKey, instruction) + + is NoKeyNavigator -> { throw IllegalArgumentException() } } } @@ -193,7 +196,8 @@ class NavigationController( } private fun closeOverrideFor(navigationContext: NavigationContext): Boolean { - val parentType = navigationContext.parentInstruction + val parentInstruction = navigationContext.navigationHandle().instruction.parentInstruction + val parentNavigator = parentInstruction ?.let { if(it.navigationKey is SingleFragmentKey) { it.parentInstruction @@ -202,10 +206,16 @@ class NavigationController( ?.let { return@let navigatorForKeyType(it.navigationKey::class) } - ?.contextType ?: return false + ?: return false + + val parentType = when(parentInstruction.navigationKey) { + is NoNavigationKeyBound -> parentInstruction.navigationKey.contextType.kotlin + else -> parentNavigator.contextType + } - @Suppress("UNCHECKED_CAST") // higher level logic dictates that this cast should succeed - val override = overrideFor(parentType to navigationContext.navigator.contextType) + @Suppress("UNCHECKED_CAST") + // higher level logic dictates that this cast should succeed + val override = overrideFor(parentType to navigationContext.contextReference::class) as? NavigationExecutor ?: return false @@ -228,12 +238,13 @@ class NavigationController( val keyType = instruction.navigationKey::class val parentNavigator = navigatorForKeyType(keyType) if (parentNavigator is ActivityNavigator) return instruction + if (parentNavigator is NoKeyNavigator) return instruction return findCorrectParentInstructionFor(instruction.parentInstruction) } val parentInstruction = when (navigationDirection) { - NavigationDirection.FORWARD -> findCorrectParentInstructionFor(parentContext.instruction) - NavigationDirection.REPLACE -> findCorrectParentInstructionFor(parentContext.instruction)?.parentInstruction + NavigationDirection.FORWARD -> findCorrectParentInstructionFor(parentContext.navigationHandle().instruction) + NavigationDirection.REPLACE -> findCorrectParentInstructionFor(parentContext.navigationHandle().instruction)?.parentInstruction NavigationDirection.REPLACE_ROOT -> null } diff --git a/enro-core/src/main/java/nav/enro/core/executors/DefaultFragmentExecutor.kt b/enro-core/src/main/java/nav/enro/core/executors/DefaultFragmentExecutor.kt index 8bd322ed..d079273f 100644 --- a/enro-core/src/main/java/nav/enro/core/executors/DefaultFragmentExecutor.kt +++ b/enro-core/src/main/java/nav/enro/core/executors/DefaultFragmentExecutor.kt @@ -9,6 +9,7 @@ import nav.enro.core.* import nav.enro.core.context.* import nav.enro.core.internal.AbstractSingleFragmentActivity import nav.enro.core.internal.SingleFragmentKey +import nav.enro.core.internal.navigationHandle import nav.enro.core.navigator.ActivityNavigator import nav.enro.core.navigator.FragmentNavigator import nav.enro.core.navigator.Navigator @@ -83,7 +84,7 @@ object DefaultFragmentExecutor : NavigationExecutor.getParentFragment(): Fragment? { val containerView = contextReference.getContainerId() - val parentInstruction = parentInstruction + val parentInstruction = navigationHandle().instruction.parentInstruction parentInstruction ?: return null val previousNavigator = controller.navigatorForKeyType(parentInstruction.navigationKey::class) diff --git a/enro-core/src/main/java/nav/enro/core/internal/Extensions.kt b/enro-core/src/main/java/nav/enro/core/internal/Extensions.kt index 77c6fe2c..53e3fe6c 100644 --- a/enro-core/src/main/java/nav/enro/core/internal/Extensions.kt +++ b/enro-core/src/main/java/nav/enro/core/internal/Extensions.kt @@ -30,7 +30,7 @@ internal fun FragmentActivity.addOnBackPressedListener(block: () -> Unit) { }) } -internal fun NavigationContext.navigationHandle(): NavigationHandleViewModel { +internal fun NavigationContext<*>.navigationHandle(): NavigationHandleViewModel { return when (this) { is FragmentContext -> fragment.getNavigationHandle() is ActivityContext -> activity.getNavigationHandle() diff --git a/enro-core/src/main/java/nav/enro/core/internal/handle/NavigationHandleActivityBinder.kt b/enro-core/src/main/java/nav/enro/core/internal/handle/NavigationHandleActivityBinder.kt index bac6448b..2dd56205 100644 --- a/enro-core/src/main/java/nav/enro/core/internal/handle/NavigationHandleActivityBinder.kt +++ b/enro-core/src/main/java/nav/enro/core/internal/handle/NavigationHandleActivityBinder.kt @@ -4,9 +4,7 @@ import android.app.Activity import android.app.Application import android.os.Bundle import android.view.ViewGroup -import androidx.activity.viewModels import androidx.fragment.app.FragmentActivity -import androidx.lifecycle.ViewModelProvider import nav.enro.core.* import nav.enro.core.context.ActivityContext import nav.enro.core.context.leafContext @@ -24,16 +22,25 @@ internal object NavigationHandleActivityBinder : Application.ActivityLifecycleCa NavigationHandleFragmentBinder, true ) - val handle by activity.viewModels { ViewModelProvider.NewInstanceFactory() } - val contextId = activity.intent.extras?.readOpenInstruction()?.instructionId + val instruction = activity.intent.extras?.readOpenInstruction() + val contextId = instruction?.instructionId ?: savedInstanceState?.getString(CONTEXT_ID_ARG) ?: UUID.randomUUID().toString() - NavigationHandleProperty.applyPending(activity) - val instruction = activity.intent.extras?.readOpenInstruction() ?: handle.defaultKey?.let { - NavigationInstruction.Open(NavigationDirection.FORWARD, it) - } - handle.navigationContext = ActivityContext(activity, instruction, contextId) + val config = NavigationHandleProperty.getPendingConfig(activity) + val defaultInstruction = NavigationInstruction.Open( + instructionId = contextId, + navigationDirection = NavigationDirection.FORWARD, + navigationKey = config?.defaultKey ?: NoNavigationKeyBound(activity::class.java, activity.intent.extras) + ) + + val handle = activity.createNavigationHandleViewModel( + activity.application.navigationController, + instruction ?: defaultInstruction + ) + config?.applyTo(handle) + + handle.navigationContext = ActivityContext(activity) if(savedInstanceState == null) handle.executeDeeplink() activity.findViewById(android.R.id.content).viewTreeObserver.addOnGlobalLayoutListener { activity.application.navigationController.active = activity.navigationContext.leafContext().navigationHandle() @@ -42,7 +49,7 @@ internal object NavigationHandleActivityBinder : Application.ActivityLifecycleCa override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) { if (activity !is FragmentActivity) return - outState.putString(CONTEXT_ID_ARG, activity.navigationContext.id) + outState.putString(CONTEXT_ID_ARG, activity.getNavigationHandle().id) } override fun onActivityPaused(activity: Activity) {} diff --git a/enro-core/src/main/java/nav/enro/core/internal/handle/NavigationHandleFragmentBinder.kt b/enro-core/src/main/java/nav/enro/core/internal/handle/NavigationHandleFragmentBinder.kt index c11d48d3..864ade33 100644 --- a/enro-core/src/main/java/nav/enro/core/internal/handle/NavigationHandleFragmentBinder.kt +++ b/enro-core/src/main/java/nav/enro/core/internal/handle/NavigationHandleFragmentBinder.kt @@ -3,8 +3,6 @@ package nav.enro.core.internal.handle import android.os.Bundle import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentManager -import androidx.fragment.app.viewModels -import androidx.lifecycle.ViewModelProvider import nav.enro.core.* import nav.enro.core.context.FragmentContext import nav.enro.core.context.leafContext @@ -15,22 +13,30 @@ import java.util.* internal object NavigationHandleFragmentBinder: FragmentManager.FragmentLifecycleCallbacks() { override fun onFragmentCreated(fm: FragmentManager, fragment: Fragment, savedInstanceState: Bundle?) { - val handle = fragment.viewModels { ViewModelProvider.NewInstanceFactory() } .value - - val contextId = fragment.arguments?.readOpenInstruction()?.instructionId + val instruction = fragment.arguments?.readOpenInstruction() + val contextId = instruction?.instructionId ?: savedInstanceState?.getString(CONTEXT_ID_ARG) ?: UUID.randomUUID().toString() - NavigationHandleProperty.applyPending(fragment) - val instruction = fragment.arguments?.readOpenInstruction() ?: handle.defaultKey?.let { - NavigationInstruction.Open(NavigationDirection.FORWARD, it) - } - handle.navigationContext = FragmentContext(fragment, instruction, contextId) + val config = NavigationHandleProperty.getPendingConfig(fragment) + val defaultInstruction = NavigationInstruction.Open( + instructionId = contextId, + navigationDirection = NavigationDirection.FORWARD, + navigationKey = config?.defaultKey ?: NoNavigationKeyBound(fragment::class.java, fragment.arguments) + ) + + val handle = fragment.createNavigationHandleViewModel( + fragment.requireActivity().application.navigationController, + instruction ?: defaultInstruction + ) + config?.applyTo(handle) + + handle.navigationContext = FragmentContext(fragment) if(savedInstanceState == null) handle.executeDeeplink() } override fun onFragmentSaveInstanceState(fm: FragmentManager, fragment: Fragment, outState: Bundle) { - outState.putString(CONTEXT_ID_ARG, fragment.navigationContext.id) + outState.putString(CONTEXT_ID_ARG, fragment.getNavigationHandle().id) } override fun onFragmentResumed(fm: FragmentManager, f: Fragment) { diff --git a/enro-core/src/main/java/nav/enro/core/internal/handle/NavigationHandleViewModel.kt b/enro-core/src/main/java/nav/enro/core/internal/handle/NavigationHandleViewModel.kt index 2a3cfdd4..b225abf0 100644 --- a/enro-core/src/main/java/nav/enro/core/internal/handle/NavigationHandleViewModel.kt +++ b/enro-core/src/main/java/nav/enro/core/internal/handle/NavigationHandleViewModel.kt @@ -3,7 +3,10 @@ package nav.enro.core.internal.handle import android.os.Bundle import android.os.Handler import android.os.Looper +import androidx.activity.viewModels +import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentActivity +import androidx.fragment.app.viewModels import androidx.lifecycle.* import nav.enro.core.* import nav.enro.core.context.* @@ -12,34 +15,63 @@ import nav.enro.core.internal.addOnBackPressedListener import nav.enro.core.internal.navigationHandle import nav.enro.core.internal.onEvent -internal class NavigationHandleViewModel : ViewModel(), NavigationHandle { +internal class NavigationHandleViewModelFactory( + private val navigationController: NavigationController, + private val instruction: NavigationInstruction.Open +) : ViewModelProvider.Factory { + override fun create(modelClass: Class): T { + return NavigationHandleViewModel( + navigationController, + instruction + ) as T + } +} + +internal fun ViewModelStoreOwner.createNavigationHandleViewModel( + navigationController: NavigationController, + instruction: NavigationInstruction.Open +): NavigationHandleViewModel { + return when(this) { + is FragmentActivity -> viewModels { + NavigationHandleViewModelFactory(navigationController, instruction) + }.value + is Fragment -> viewModels { + NavigationHandleViewModelFactory(navigationController, instruction) + }.value + else -> throw IllegalArgumentException("ViewModelStoreOwner must be a Fragment or Activity") + } +} - private var pendingInstruction: NavigationInstruction? = null +internal fun ViewModelStoreOwner.getNavigationHandleViewModel(): NavigationHandleViewModel { + return when(this) { + is FragmentActivity -> viewModels { ViewModelProvider.NewInstanceFactory() }.value + is Fragment -> viewModels { ViewModelProvider.NewInstanceFactory() }.value + else -> throw IllegalArgumentException("ViewModelStoreOwner must be a Fragment or Activity") + } +} - internal var internalOnCloseRequested: () -> Unit = { close() } +internal class NavigationHandleViewModel( + override val controller: NavigationController, + internal val instruction: NavigationInstruction.Open +) : ViewModel(), NavigationHandle { + + private var pendingInstruction: NavigationInstruction? = null - internal val hasKey get() = rawKey != null - internal var rawKey: NavigationKey? = null - internal var defaultKey: NavigationKey? = null + internal val hasKey get() = instruction.navigationKey !is NoNavigationKeyBound override val key: NavigationKey get() { - return rawKey - ?: throw IllegalStateException("This NavigationHandle has no NavigationKey bound") + if(instruction.navigationKey is NoNavigationKeyBound) throw IllegalStateException("This NavigationHandle has no NavigationKey") + return instruction.navigationKey } - override lateinit var id: String - override lateinit var controller: NavigationController - override lateinit var additionalData: Bundle + override val id: String get() = instruction.instructionId + override val additionalData: Bundle get() = instruction.additionalData internal var childContainers = listOf() - set(value) { - field = value - navigationContext?.childContainers = value - } + internal var internalOnCloseRequested: () -> Unit = { close() } private val lifecycle = LifecycleRegistry(this).apply { addObserver(object : LifecycleEventObserver { override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) { - if (navigationContext?.instruction == null) return if (event == Lifecycle.Event.ON_CREATE) controller.onOpened(this@NavigationHandleViewModel) if (event == Lifecycle.Event.ON_DESTROY) controller.onClosed(this@NavigationHandleViewModel) } @@ -52,19 +84,7 @@ internal class NavigationHandleViewModel : ViewModel(), NavigationHandle { internal var navigationContext: NavigationContext<*>? = null set(value) { - field?.let { - val id = it.id - it.controller.handles.remove(id) - } field = value - value?.let { - it.childContainers = childContainers - controller = it.controller - additionalData = it.instruction?.additionalData ?: Bundle() - id = it.id - it.controller.handles[id] = this - rawKey = it.key ?: return@let - } if (value == null) return registerLifecycleObservers(value) registerOnBackPressedListener(value) @@ -75,6 +95,9 @@ internal class NavigationHandleViewModel : ViewModel(), NavigationHandle { } } + init { + controller.handles[id] = this + } private fun registerLifecycleObservers(context: NavigationContext) { context.lifecycle.addObserver(object : LifecycleEventObserver { @@ -119,19 +142,18 @@ internal class NavigationHandleViewModel : ViewModel(), NavigationHandle { } internal fun executeDeeplink() { - val context = navigationContext ?: throw IllegalStateException("The NavigationHandle must be attached to a NavigationContext") - - if (context.pendingKeys.isEmpty()) return + if (instruction.children.isEmpty()) return executeInstruction( NavigationInstruction.Open( NavigationDirection.FORWARD, - context.pendingKeys.first(), - context.pendingKeys.drop(1) + instruction.children.first(), + instruction.children.drop(1) ) ) } override fun onCleared() { + controller.handles.remove(id) lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY) } } \ No newline at end of file diff --git a/enro-core/src/main/java/nav/enro/core/navigator/Navigator.kt b/enro-core/src/main/java/nav/enro/core/navigator/Navigator.kt index 5324cdf6..83dc191f 100644 --- a/enro-core/src/main/java/nav/enro/core/navigator/Navigator.kt +++ b/enro-core/src/main/java/nav/enro/core/navigator/Navigator.kt @@ -3,6 +3,7 @@ package nav.enro.core.navigator import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentActivity import nav.enro.core.NavigationKey +import nav.enro.core.NoNavigationKeyBound import kotlin.reflect.KClass interface Navigator { @@ -30,3 +31,9 @@ class SyntheticNavigator @PublishedApi internal constructor( override val contextType: KClass = Any::class override val animations: NavigatorAnimations = NavigatorAnimations.default } + +internal class NoKeyNavigator: Navigator { + override val keyType: KClass = NoNavigationKeyBound::class + override val contextType: KClass = Any::class + override val animations: NavigatorAnimations = NavigatorAnimations.default +} \ No newline at end of file diff --git a/enro/src/androidTest/AndroidManifest.xml b/enro/src/androidTest/AndroidManifest.xml index 33ee775a..01160a3a 100644 --- a/enro/src/androidTest/AndroidManifest.xml +++ b/enro/src/androidTest/AndroidManifest.xml @@ -6,5 +6,6 @@ + \ No newline at end of file diff --git a/enro/src/androidTest/java/nav/enro/TestDestinations.kt b/enro/src/androidTest/java/nav/enro/TestDestinations.kt index b9b33255..06423807 100644 --- a/enro/src/androidTest/java/nav/enro/TestDestinations.kt +++ b/enro/src/androidTest/java/nav/enro/TestDestinations.kt @@ -1,24 +1,31 @@ package nav.enro import android.R import kotlinx.android.parcel.Parcelize +import nav.enro.annotations.NavigationDestination import nav.enro.core.NavigationKey import nav.enro.core.navigationHandle @Parcelize data class DefaultActivityKey(val id: String) : NavigationKey -val defaultKey = DefaultActivityKey("default") +@NavigationDestination(DefaultActivityKey::class) class DefaultActivity : TestActivity() { private val navigation by navigationHandle { defaultKey(defaultKey) } + + companion object { + val defaultKey = DefaultActivityKey("default") + } } @Parcelize data class GenericActivityKey(val id: String) : NavigationKey +@NavigationDestination(GenericActivityKey::class) class GenericActivity : TestActivity() @Parcelize data class ActivityWithFragmentsKey(val id: String) : NavigationKey +@NavigationDestination(ActivityWithFragmentsKey::class) class ActivityWithFragments : TestActivity() { private val navigation by navigationHandle { defaultKey(ActivityWithFragmentsKey("default")) @@ -30,12 +37,19 @@ class ActivityWithFragments : TestActivity() { @Parcelize data class ActivityChildFragmentKey(val id: String) : NavigationKey +@NavigationDestination(ActivityChildFragmentKey::class) class ActivityChildFragment : TestFragment() @Parcelize data class ActivityChildFragmentTwoKey(val id: String) : NavigationKey +@NavigationDestination(ActivityChildFragmentTwoKey::class) class ActivityChildFragmentTwo : TestFragment() @Parcelize data class GenericFragmentKey(val id: String) : NavigationKey -class GenericFragment : TestFragment() \ No newline at end of file +@NavigationDestination(GenericFragmentKey::class) +class GenericFragment : TestFragment() + +class UnboundActivity : TestActivity() + +class UnboundFragment : TestFragment() \ No newline at end of file diff --git a/enro/src/androidTest/java/nav/enro/core/ActivityToActivityTests.kt b/enro/src/androidTest/java/nav/enro/core/ActivityToActivityTests.kt index 2c74e5d0..7f854168 100644 --- a/enro/src/androidTest/java/nav/enro/core/ActivityToActivityTests.kt +++ b/enro/src/androidTest/java/nav/enro/core/ActivityToActivityTests.kt @@ -5,7 +5,10 @@ import androidx.test.core.app.ActivityScenario import androidx.test.platform.app.InstrumentationRegistry import junit.framework.TestCase.assertEquals import junit.framework.TestCase.assertNotNull -import nav.enro.* +import nav.enro.DefaultActivity +import nav.enro.DefaultActivityKey +import nav.enro.GenericActivity +import nav.enro.GenericActivityKey import org.junit.Test import java.util.* @@ -15,7 +18,17 @@ class ActivityToActivityTests { fun givenDefaultActivityOpenedWithoutNavigationKeySet_thenDefaultKeyIsUsed() { val scenario = ActivityScenario.launch(DefaultActivity::class.java) val handle = scenario.getNavigationHandle() - assertEquals(defaultKey, handle.key) + assertEquals(DefaultActivity.defaultKey, handle.key) + } + + @Test + fun givenDefaultActivityRecreated_thenNavigationHandleIdIsStable() { + val scenario = ActivityScenario.launch(DefaultActivity::class.java) + val id = scenario.getNavigationHandle().id + scenario.recreate() + + val recreatedId = expectActivity().getNavigationHandle().id + assertEquals(id, recreatedId) } @Test @@ -76,7 +89,7 @@ class ActivityToActivityTests { val activeActivity = expectActivity() val activeHandle = activeActivity.getNavigationHandle().asTyped() - assertEquals(defaultKey, activeHandle.key) + assertEquals(DefaultActivity.defaultKey, activeHandle.key) } @Test(expected = IllegalStateException::class) diff --git a/enro/src/androidTest/java/nav/enro/core/Extensions.kt b/enro/src/androidTest/java/nav/enro/core/Extensions.kt index 295bc8a3..dba01ec8 100644 --- a/enro/src/androidTest/java/nav/enro/core/Extensions.kt +++ b/enro/src/androidTest/java/nav/enro/core/Extensions.kt @@ -42,6 +42,14 @@ internal inline fun expectFragment(crossinline selector: ( } } +internal inline fun expectNoFragment(crossinline selector: (Fragment) -> Boolean = { it is T }): Boolean { + val activity = expectActivity() + return waitOnMain { + val fragment = activity.supportFragmentManager.primaryNavigationFragment ?: return@waitOnMain true + if(selector(fragment)) return@waitOnMain null else true + } +} + fun expectNoActivity() { waitOnMain { val activities = ActivityLifecycleMonitorRegistry.getInstance().getActivitiesInStage(Stage.PRE_ON_CREATE).toList() + diff --git a/enro/src/androidTest/java/nav/enro/core/UnboundActivitiesTest.kt b/enro/src/androidTest/java/nav/enro/core/UnboundActivitiesTest.kt new file mode 100644 index 00000000..3952e290 --- /dev/null +++ b/enro/src/androidTest/java/nav/enro/core/UnboundActivitiesTest.kt @@ -0,0 +1,92 @@ +package nav.enro.core + +import android.content.Intent +import androidx.test.core.app.ActivityScenario +import junit.framework.Assert.* +import nav.enro.* +import org.junit.Test + +class UnboundActivitiesTest { + + @Test + fun whenUnboundActivityIsOpened_thenNavigationKeyIsUnbound() { + val scenario = ActivityScenario.launch(DefaultActivity::class.java) + scenario.onActivity { + it.startActivity(Intent(it, UnboundActivity::class.java)) + } + val unboundActivity = expectActivity() + val unboundHandle = unboundActivity.getNavigationHandle() + + lateinit var caught: Throwable + try { + val key = unboundHandle.key + } + catch (t: Throwable) { + caught = t + } + assertTrue(caught is IllegalStateException) + assertEquals("This NavigationHandle has no NavigationKey", caught.message) + } + + @Test + fun whenUnboundActivityIsOpened_thenUnboundActivityHasAnId() { + val scenario = ActivityScenario.launch(DefaultActivity::class.java) + scenario.onActivity { + it.startActivity(Intent(it, UnboundActivity::class.java)) + } + val unboundActivity = expectActivity() + val unboundHandle = unboundActivity.getNavigationHandle() + + assertNotNull(unboundHandle.id) + } + + @Test + fun whenUnboundActivityIsRecreated_thenUnboundActivityIdIsStable() { + val scenario = ActivityScenario.launch(UnboundActivity::class.java) + val id = expectActivity().getNavigationHandle().id + scenario.recreate() + + val recreatedId = expectActivity().getNavigationHandle().id + + assertEquals(id, recreatedId) + } + + @Test + fun givenUnboundActivity_whenNavigationHandleIsUsedToClose_thenActivityClosesCorrectly() { + val scenario = ActivityScenario.launch(DefaultActivity::class.java) + scenario.onActivity { + it.startActivity(Intent(it, UnboundActivity::class.java)) + } + val unboundActivity = expectActivity() + unboundActivity.getNavigationHandle().close() + + val defaultActivity = expectActivity() + assertNotNull(defaultActivity) + } + + @Test + fun givenUnboundActivity_whenNavigationHandleIsUsedToOpenActivityKey_thenActivityIsOpenedCorrectly() { + val scenario = ActivityScenario.launch(DefaultActivity::class.java) + scenario.onActivity { + it.startActivity(Intent(it, UnboundActivity::class.java)) + } + val unboundActivity = expectActivity() + unboundActivity.getNavigationHandle().forward(GenericActivityKey("opened-from-unbound")) + + val genericActivity = expectActivity() + assertEquals("opened-from-unbound", genericActivity.getNavigationHandle().asTyped().key.id) + } + + @Test + fun givenUnboundActivity_whenNavigationHandleIsUsedToOpenFragmentKey_thenFragmentIsOpenedCorrectly() { + val scenario = ActivityScenario.launch(DefaultActivity::class.java) + scenario.onActivity { + it.startActivity(Intent(it, UnboundActivity::class.java)) + } + val unboundActivity = expectActivity() + unboundActivity.getNavigationHandle().forward(GenericFragmentKey("opened-from-unbound")) + + val genericActivity = expectFragment() + assertEquals("opened-from-unbound", genericActivity.getNavigationHandle().asTyped().key.id) + } +} \ No newline at end of file diff --git a/enro/src/androidTest/java/nav/enro/core/UnboundFragmentsTest.kt b/enro/src/androidTest/java/nav/enro/core/UnboundFragmentsTest.kt new file mode 100644 index 00000000..5ebcd70d --- /dev/null +++ b/enro/src/androidTest/java/nav/enro/core/UnboundFragmentsTest.kt @@ -0,0 +1,105 @@ +package nav.enro.core + +import androidx.fragment.app.commitNow +import androidx.test.core.app.ActivityScenario +import junit.framework.Assert.* +import nav.enro.* +import org.junit.Ignore +import org.junit.Test + +@Ignore("Something isn't working with the unbound fragment test environment, tests are failing but are known to pass") +class UnboundFragmentsTest { + + @Test + fun whenUnboundFragmentIsOpened_thenNavigationKeyIsUnbound() { + val scenario = ActivityScenario.launch(DefaultActivity::class.java) + scenario.onActivity { + it.supportFragmentManager.commitNow { + val fragment = UnboundFragment() + replace(android.R.id.content, fragment) + setPrimaryNavigationFragment(fragment) + } + } + val unboundFragment = expectFragment() + val unboundHandle = unboundFragment.getNavigationHandle() + + lateinit var caught: Throwable + try { + val key = unboundHandle.key + } + catch (t: Throwable) { + caught = t + } + assertTrue(caught is IllegalStateException) + assertEquals("This NavigationHandle has no NavigationKey", caught.message) + } + + @Test + fun whenUnboundFragmentIsOpened_thenUnboundActivityHasAnId() { + val scenario = ActivityScenario.launch(DefaultActivity::class.java) + scenario.onActivity { + it.supportFragmentManager.commitNow { + val fragment = UnboundFragment() + replace(android.R.id.content, fragment) + setPrimaryNavigationFragment(fragment) + } + } + val unboundFragment = expectFragment() + val unboundHandle = unboundFragment.getNavigationHandle() + + assertNotNull(unboundHandle.id) + } + + @Test + fun givenUnboundFragment_whenNavigationHandleIsUsedToClose_thenFragmentClosesCorrectly() { + val scenario = ActivityScenario.launch(DefaultActivity::class.java) + scenario.onActivity { + it.supportFragmentManager.commitNow { + val fragment = UnboundFragment() + replace(android.R.id.content, fragment) + setPrimaryNavigationFragment(fragment) + } + } + val unboundFragment = expectFragment() + unboundFragment.getNavigationHandle().close() + + val defaultActivity = expectActivity() + val fragmentWasRemoved = expectNoFragment() + assertNotNull(defaultActivity) + assertTrue(fragmentWasRemoved) + } + + @Test + fun givenUnboundFragment_whenNavigationHandleIsUsedToOpenActivityKey_thenActivityIsOpenedCorrectly() { + val scenario = ActivityScenario.launch(DefaultActivity::class.java) + scenario.onActivity { + it.supportFragmentManager.commitNow { + val fragment = UnboundFragment() + replace(android.R.id.content, fragment) + setPrimaryNavigationFragment(fragment) + } + } + val unboundFragment = expectFragment() + unboundFragment.getNavigationHandle().forward(GenericActivityKey("opened-from-unbound")) + + val genericActivity = expectActivity() + assertEquals("opened-from-unbound", genericActivity.getNavigationHandle().asTyped().key.id) + } + + @Test + fun givenUnboundFragment_whenNavigationHandleIsUsedToOpenFragmentKey_thenFragmentIsOpenedCorrectly() { + val scenario = ActivityScenario.launch(DefaultActivity::class.java) + scenario.onActivity { + it.supportFragmentManager.commitNow { + val fragment = UnboundFragment() + replace(android.R.id.content, fragment) + setPrimaryNavigationFragment(fragment) + } + } + val unboundFragment = expectFragment() + unboundFragment.getNavigationHandle().forward(GenericFragmentKey("opened-from-unbound")) + + val genericActivity = expectFragment() + assertEquals("opened-from-unbound", genericActivity.getNavigationHandle().asTyped().key.id) + } +} \ No newline at end of file diff --git a/enro/src/androidTest/java/nav/enro/core/overrides/ActivityToActivityOverrideTests.kt b/enro/src/androidTest/java/nav/enro/core/overrides/ActivityToActivityOverrideTests.kt index 7cf83275..aba3382d 100644 --- a/enro/src/androidTest/java/nav/enro/core/overrides/ActivityToActivityOverrideTests.kt +++ b/enro/src/androidTest/java/nav/enro/core/overrides/ActivityToActivityOverrideTests.kt @@ -3,10 +3,7 @@ package nav.enro.core.overrides import android.content.Intent import androidx.test.core.app.ActivityScenario import junit.framework.Assert.assertTrue -import nav.enro.DefaultActivity -import nav.enro.DefaultActivityKey -import nav.enro.GenericActivity -import nav.enro.GenericActivityKey +import nav.enro.* import nav.enro.core.* import nav.enro.core.controller.navigationController import nav.enro.core.executors.createOverride @@ -122,4 +119,50 @@ class ActivityToActivityOverrideTests() { assertTrue(closeOverrideCalled) } + + @Test + fun givenUnboundActivityToActivityOverride_whenActivityIsLaunched_thenOverrideIsCalled() { + var launchOverrideCalled = false + application.navigationController.addOverride( + createOverride( + launch = { + launchOverrideCalled = true + defaultLaunch().invoke(it) + } + ) + ) + + ActivityScenario.launch(UnboundActivity::class.java) + expectActivity().getNavigationHandle() + .forward(GenericActivityKey("override test 2")) + + expectActivity() + + assertTrue(launchOverrideCalled) + } + + @Test + fun givenUnboundActivityToActivityOverride_whenActivityIsClosed_thenOverrideIsCalled() { + var closeOverrideCalled = false + application.navigationController.addOverride( + createOverride( + close = { + closeOverrideCalled = true + defaultClose().invoke(it) + } + ) + ) + + ActivityScenario.launch(UnboundActivity::class.java) + expectActivity().getNavigationHandle() + .forward(GenericActivityKey("override test 2")) + + expectActivity() + .getNavigationHandle() + .close() + + expectActivity() + + assertTrue(closeOverrideCalled) + } } \ No newline at end of file From 2d28db37060446f6c361e9752a2200cad2f0e3df Mon Sep 17 00:00:00 2001 From: Isaac Date: Mon, 30 Nov 2020 02:09:32 +1300 Subject: [PATCH 06/24] Remove useless NavigatorDefinition wrapping Navigator --- .../controller/NavigationComponentBuilder.kt | 4 +- .../core/controller/NavigationController.kt | 18 +++--- .../core/navigator/NavigatorDefinition.kt | 57 ++++++------------- 3 files changed, 27 insertions(+), 52 deletions(-) diff --git a/enro-core/src/main/java/nav/enro/core/controller/NavigationComponentBuilder.kt b/enro-core/src/main/java/nav/enro/core/controller/NavigationComponentBuilder.kt index fe410dee..c87490f2 100644 --- a/enro-core/src/main/java/nav/enro/core/controller/NavigationComponentBuilder.kt +++ b/enro-core/src/main/java/nav/enro/core/controller/NavigationComponentBuilder.kt @@ -14,7 +14,7 @@ interface NavigationComponentBuilderCommand { class NavigationComponentBuilder { @PublishedApi - internal val navigators: MutableList> = mutableListOf() + internal val navigators: MutableList> = mutableListOf() @PublishedApi internal val overrides: MutableList> = mutableListOf() @PublishedApi @@ -45,7 +45,7 @@ class NavigationComponentBuilder { overrides.add(createOverride(launch, close)) } - fun add(navigator: NavigatorDefinition<*, *>) { + fun add(navigator: Navigator<*, *>) { navigators.add(navigator) } diff --git a/enro-core/src/main/java/nav/enro/core/controller/NavigationController.kt b/enro-core/src/main/java/nav/enro/core/controller/NavigationController.kt index 57ca3568..5ce2bb7b 100644 --- a/enro-core/src/main/java/nav/enro/core/controller/NavigationController.kt +++ b/enro-core/src/main/java/nav/enro/core/controller/NavigationController.kt @@ -25,7 +25,7 @@ import kotlin.reflect.KClass class NavigationController( - navigators: List>, + navigators: List>, overrides: List> = listOf(), private val plugins: List = listOf() ) { @@ -51,7 +51,7 @@ class NavigationController( createActivityNavigator() } - val noKeyProvidedNavigator = NavigatorDefinition(NoKeyNavigator(), emptyList()) + val noKeyProvidedNavigator = NoKeyNavigator() listOf( singleFragmentNavigator, @@ -61,21 +61,17 @@ class NavigationController( private val navigatorsByKeyType = (navigators + defaultNavigators) .map { - it.navigator.keyType to it + it.keyType to it } .toMap() private val navigatorsByContextType = (navigators + defaultNavigators) .map { - it.navigator.contextType to it + it.contextType to it } .toMap() - private val overrides = (overrides + navigators.let { - val flatMap: (NavigatorDefinition<*,*>) -> List> = { it.executors } - it.flatMap(flatMap) - }).map { (it.fromType to it.opensType) to it }.toMap() - + private val overrides = overrides.map { (it.fromType to it.opensType) to it }.toMap() private val temporaryOverrides = mutableMapOf, KClass>, NavigationExecutor<*,*,*>>() internal val handles = mutableMapOf() @@ -138,13 +134,13 @@ class NavigationController( fun navigatorForContextType( contextType: KClass<*> ): Navigator<*, *>? { - return navigatorsByContextType[contextType]?.navigator + return navigatorsByContextType[contextType] } fun navigatorForKeyType( keyType: KClass ): Navigator<*, *>? { - return navigatorsByKeyType[keyType]?.navigator + return navigatorsByKeyType[keyType] } private fun overrideFor(types: Pair, KClass>): NavigationExecutor? { diff --git a/enro-core/src/main/java/nav/enro/core/navigator/NavigatorDefinition.kt b/enro-core/src/main/java/nav/enro/core/navigator/NavigatorDefinition.kt index 3879212d..96d65496 100644 --- a/enro-core/src/main/java/nav/enro/core/navigator/NavigatorDefinition.kt +++ b/enro-core/src/main/java/nav/enro/core/navigator/NavigatorDefinition.kt @@ -3,19 +3,13 @@ package nav.enro.core.navigator import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentActivity import nav.enro.core.NavigationKey -import nav.enro.core.executors.NavigationExecutor import kotlin.reflect.KClass -class NavigatorDefinition( - val navigator: Navigator, - val executors: List> -) - fun createActivityNavigator( navigationKeyType: KClass, activityType: KClass, block: ActivityNavigatorBuilder.() -> Unit = {} -): NavigatorDefinition = +): Navigator = ActivityNavigatorBuilder( keyType = navigationKeyType, contextType = activityType @@ -23,29 +17,23 @@ fun createActivityNavigator( inline fun createActivityNavigator( noinline block: ActivityNavigatorBuilder.() -> Unit = {} -): NavigatorDefinition = createActivityNavigator(T::class, A::class, block) +): Navigator = createActivityNavigator(T::class, A::class, block) -class ActivityNavigatorBuilder( +class ActivityNavigatorBuilder( private val keyType: KClass, @PublishedApi internal val contextType: KClass ) { - @PublishedApi internal val executors = mutableListOf>() - - fun build() = NavigatorDefinition( - navigator = ActivityNavigator( - keyType = keyType, - contextType = contextType, - ), - executors = executors + fun build() = ActivityNavigator( + keyType = keyType, + contextType = contextType, ) } - fun createFragmentNavigator( navigationKeyType: KClass, fragmentType: KClass, block: FragmentNavigatorBuilder.() -> Unit = {} -): NavigatorDefinition = +): Navigator = FragmentNavigatorBuilder( keyType = navigationKeyType, contextType = fragmentType, @@ -53,45 +41,36 @@ fun createFragmentNavigator( inline fun createFragmentNavigator( noinline block: FragmentNavigatorBuilder.() -> Unit = {} -): NavigatorDefinition = createFragmentNavigator(T::class, A::class, block) +): Navigator = createFragmentNavigator(T::class, A::class, block) -class FragmentNavigatorBuilder( +class FragmentNavigatorBuilder( private val keyType: KClass, private val contextType: KClass ) { - private val executors = mutableListOf>() - - fun build() = NavigatorDefinition( - navigator = FragmentNavigator( - keyType = keyType, - contextType = contextType, - ), - executors = executors + fun build() = FragmentNavigator( + keyType = keyType, + contextType = contextType, ) } - fun createSyntheticNavigator( navigationKeyType: KClass, destination: SyntheticDestination -): NavigatorDefinition = +): Navigator = SyntheticNavigatorBuilder( keyType = navigationKeyType, destination = destination ).build() -inline fun createSyntheticNavigator(destination: SyntheticDestination): NavigatorDefinition = +inline fun createSyntheticNavigator(destination: SyntheticDestination): Navigator = createSyntheticNavigator(T::class, destination) -class SyntheticNavigatorBuilder( +class SyntheticNavigatorBuilder( private val keyType: KClass, private val destination: SyntheticDestination ) { - fun build() = NavigatorDefinition( - navigator = SyntheticNavigator( - keyType = keyType, - destination = destination - ), - executors = emptyList() + fun build() = SyntheticNavigator( + keyType = keyType, + destination = destination ) } \ No newline at end of file From d0eb3105a4cf85279fb61b93dc50193ed09a4a58 Mon Sep 17 00:00:00 2001 From: Isaac Date: Mon, 30 Nov 2020 23:49:41 +1300 Subject: [PATCH 07/24] Started repackaging enro-core --- enro-core/src/main/AndroidManifest.xml | 4 +- .../src/main/java/nav/enro/core/Extensions.kt | 10 +++ .../nav/enro/core/NavigationAnimations.kt | 9 +- .../{controller => }/NavigationApplication.kt | 3 +- .../Extensions.kt => NavigationContext.kt} | 89 +++++++++++-------- .../{executors => }/NavigationExecutor.kt | 10 +-- .../core/NavigationHandleConfiguration.kt | 42 +++++++++ .../nav/enro/core/NavigationHandleProperty.kt | 32 ------- .../nav/enro/core/NavigationInstruction.kt | 3 + .../main/java/nav/enro/core/NavigationKey.kt | 7 -- ...cutorOverride.kt => NavigationOverride.kt} | 16 ++-- .../src/main/java/nav/enro/core/Navigator.kt | 9 ++ .../{navigator => }/NavigatorAnimations.kt | 21 ++--- .../enro/core/activity/ActivityNavigator.kt | 27 ++++++ .../DefaultActivityExecutor.kt | 7 +- .../NavigationHandleActivityBinder.kt | 21 ++--- .../enro/core/context/NavigationContext.kt | 50 ----------- .../controller/NavigationComponentBuilder.kt | 45 ++-------- .../core/controller/NavigationController.kt | 53 ++++++----- .../DefaultFragmentExecutor.kt | 22 +++-- .../enro/core/fragment/FragmentNavigator.kt | 27 ++++++ .../NavigationHandleFragmentBinder.kt | 66 ++++++++++++++ .../core/fragment/internal/FragmentHost.kt | 42 +++++++++ .../internal/SingleFragmentActivity.kt | 5 +- .../java/nav/enro/core/internal/Extensions.kt | 43 --------- .../nav/enro/core/internal/NoNavigationKey.kt | 20 +++++ .../handle/NavigationHandleFragmentBinder.kt | 47 ---------- .../handle/NavigationHandleViewModel.kt | 69 +++++--------- .../NavigationHandleViewModelFactory.kt | 46 ++++++++++ .../nav/enro/core/navigator/FragmentHost.kt | 11 --- .../java/nav/enro/core/navigator/Navigator.kt | 39 -------- .../core/navigator/NavigatorDefinition.kt | 76 ---------------- .../SyntheticDestination.kt} | 4 +- .../enro/core/synthetic/SyntheticNavigator.kt | 32 +++++++ .../masterdetail/MasterDetailComponent.kt | 16 ++-- .../MultistackControllerFragment.kt | 16 ++-- .../java/nav/enro/TestApplication.kt | 6 +- .../core/{Extensions.kt => TestExtensions.kt} | 0 .../ActivityToActivityOverrideTests.kt | 20 ++--- .../ActivityToFragmentOverrideTests.kt | 20 ++--- .../FragmentToActivityOverrideTests.kt | 22 ++--- .../FragmentToFragmentOverrideTests.kt | 30 +++---- .../nav/enro/example/ExampleApplication.kt | 5 +- .../java/nav/enro/example/SimpleMessage.kt | 6 +- .../nav/enro/example/ExampleApplication.kt | 12 +-- .../java/nav/enro/example/MainActivity.kt | 6 +- .../java/nav/enro/example/login/LoginError.kt | 6 +- 47 files changed, 565 insertions(+), 607 deletions(-) create mode 100644 enro-core/src/main/java/nav/enro/core/Extensions.kt rename enro-core/src/main/java/nav/enro/core/{controller => }/NavigationApplication.kt (74%) rename enro-core/src/main/java/nav/enro/core/{context/Extensions.kt => NavigationContext.kt} (51%) rename enro-core/src/main/java/nav/enro/core/{executors => }/NavigationExecutor.kt (73%) create mode 100644 enro-core/src/main/java/nav/enro/core/NavigationHandleConfiguration.kt rename enro-core/src/main/java/nav/enro/core/{executors/ExecutorOverride.kt => NavigationOverride.kt} (79%) create mode 100644 enro-core/src/main/java/nav/enro/core/Navigator.kt rename enro-core/src/main/java/nav/enro/core/{navigator => }/NavigatorAnimations.kt (72%) create mode 100644 enro-core/src/main/java/nav/enro/core/activity/ActivityNavigator.kt rename enro-core/src/main/java/nav/enro/core/{executors => activity}/DefaultActivityExecutor.kt (92%) rename enro-core/src/main/java/nav/enro/core/{internal/handle => activity}/NavigationHandleActivityBinder.kt (79%) delete mode 100644 enro-core/src/main/java/nav/enro/core/context/NavigationContext.kt rename enro-core/src/main/java/nav/enro/core/{executors => fragment}/DefaultFragmentExecutor.kt (93%) create mode 100644 enro-core/src/main/java/nav/enro/core/fragment/FragmentNavigator.kt create mode 100644 enro-core/src/main/java/nav/enro/core/fragment/NavigationHandleFragmentBinder.kt create mode 100644 enro-core/src/main/java/nav/enro/core/fragment/internal/FragmentHost.kt rename enro-core/src/main/java/nav/enro/core/{ => fragment}/internal/SingleFragmentActivity.kt (92%) delete mode 100644 enro-core/src/main/java/nav/enro/core/internal/Extensions.kt create mode 100644 enro-core/src/main/java/nav/enro/core/internal/NoNavigationKey.kt delete mode 100644 enro-core/src/main/java/nav/enro/core/internal/handle/NavigationHandleFragmentBinder.kt create mode 100644 enro-core/src/main/java/nav/enro/core/internal/handle/NavigationHandleViewModelFactory.kt delete mode 100644 enro-core/src/main/java/nav/enro/core/navigator/FragmentHost.kt delete mode 100644 enro-core/src/main/java/nav/enro/core/navigator/Navigator.kt delete mode 100644 enro-core/src/main/java/nav/enro/core/navigator/NavigatorDefinition.kt rename enro-core/src/main/java/nav/enro/core/{navigator/Synthetic.kt => synthetic/SyntheticDestination.kt} (77%) create mode 100644 enro-core/src/main/java/nav/enro/core/synthetic/SyntheticNavigator.kt rename enro/src/androidTest/java/nav/enro/core/{Extensions.kt => TestExtensions.kt} (100%) diff --git a/enro-core/src/main/AndroidManifest.xml b/enro-core/src/main/AndroidManifest.xml index 40169298..2c3c4848 100644 --- a/enro-core/src/main/AndroidManifest.xml +++ b/enro-core/src/main/AndroidManifest.xml @@ -1,6 +1,6 @@ - - + + \ No newline at end of file diff --git a/enro-core/src/main/java/nav/enro/core/Extensions.kt b/enro-core/src/main/java/nav/enro/core/Extensions.kt new file mode 100644 index 00000000..6f93bd2b --- /dev/null +++ b/enro-core/src/main/java/nav/enro/core/Extensions.kt @@ -0,0 +1,10 @@ +package nav.enro.core + +import android.content.res.Resources +import android.util.TypedValue + + +internal fun Resources.Theme.getAttributeResourceId(attr: Int) = TypedValue().let { + resolveAttribute(attr, it, true) + it.resourceId +} \ No newline at end of file diff --git a/enro-core/src/main/java/nav/enro/core/NavigationAnimations.kt b/enro-core/src/main/java/nav/enro/core/NavigationAnimations.kt index 79b91785..ba6df977 100644 --- a/enro-core/src/main/java/nav/enro/core/NavigationAnimations.kt +++ b/enro-core/src/main/java/nav/enro/core/NavigationAnimations.kt @@ -5,13 +5,6 @@ import android.os.Parcelable import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentActivity import kotlinx.android.parcel.Parcelize -import nav.enro.core.context.NavigationContext -import nav.enro.core.context.activity -import nav.enro.core.context.navigationContext -import nav.enro.core.internal.getAttributeResourceId -import nav.enro.core.internal.navigationHandle -import nav.enro.core.navigator.NavigatorAnimations -import nav.enro.core.navigator.toResource sealed class NavigationAnimations : Parcelable { @Parcelize @@ -123,7 +116,7 @@ private fun animationsForClose( val navigatorAnimations = navigator?.animations?.toResource(theme) ?: NavigatorAnimations.default.toResource(theme) - val animations = context.navigationHandle().instruction.animations?.toResource(theme) + val animations = context.getNavigationHandleViewModel().instruction.animations?.toResource(theme) return when { animations != null -> AnimationPair( diff --git a/enro-core/src/main/java/nav/enro/core/controller/NavigationApplication.kt b/enro-core/src/main/java/nav/enro/core/NavigationApplication.kt similarity index 74% rename from enro-core/src/main/java/nav/enro/core/controller/NavigationApplication.kt rename to enro-core/src/main/java/nav/enro/core/NavigationApplication.kt index 962d26de..c4f4f208 100644 --- a/enro-core/src/main/java/nav/enro/core/controller/NavigationApplication.kt +++ b/enro-core/src/main/java/nav/enro/core/NavigationApplication.kt @@ -1,6 +1,7 @@ -package nav.enro.core.controller +package nav.enro.core import android.app.Application +import nav.enro.core.controller.NavigationController interface NavigationApplication { val navigationController: NavigationController diff --git a/enro-core/src/main/java/nav/enro/core/context/Extensions.kt b/enro-core/src/main/java/nav/enro/core/NavigationContext.kt similarity index 51% rename from enro-core/src/main/java/nav/enro/core/context/Extensions.kt rename to enro-core/src/main/java/nav/enro/core/NavigationContext.kt index ce845537..7e850dd3 100644 --- a/enro-core/src/main/java/nav/enro/core/context/Extensions.kt +++ b/enro-core/src/main/java/nav/enro/core/NavigationContext.kt @@ -1,18 +1,49 @@ -package nav.enro.core.context +package nav.enro.core -import android.view.View import androidx.activity.viewModels -import androidx.core.view.isVisible import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentActivity +import androidx.fragment.app.FragmentManager import androidx.fragment.app.viewModels +import androidx.lifecycle.Lifecycle import androidx.lifecycle.ViewModelProvider -import nav.enro.core.NavigationKey +import nav.enro.core.activity.ActivityNavigator +import nav.enro.core.controller.NavigationController +import nav.enro.core.fragment.FragmentNavigator import nav.enro.core.internal.handle.NavigationHandleViewModel -import nav.enro.core.internal.navigationHandle -import nav.enro.core.navigator.FragmentHost + +sealed class NavigationContext( + val contextReference: ContextType +) { + abstract val controller: NavigationController + abstract val lifecycle: Lifecycle + abstract val childFragmentManager: FragmentManager + + internal open val navigator: Navigator<*, ContextType>? by lazy { + controller.navigatorForContextType(contextReference::class) as? Navigator<*, ContextType> + } +} + +internal class ActivityContext( + contextReference: ContextType, +) : NavigationContext(contextReference) { + override val controller get() = contextReference.application.navigationController + override val lifecycle get() = contextReference.lifecycle + override val navigator get() = super.navigator as? ActivityNavigator<*, ContextType> + override val childFragmentManager get() = contextReference.supportFragmentManager +} + +internal class FragmentContext( + contextReference: ContextType, +) : NavigationContext(contextReference) { + override val controller get() = contextReference.requireActivity().application.navigationController + override val lifecycle get() = contextReference.lifecycle + override val navigator get() = super.navigator as? FragmentNavigator<*, ContextType> + override val childFragmentManager get() = contextReference.childFragmentManager +} val NavigationContext.fragment get() = contextReference + val NavigationContext<*>.activity: FragmentActivity get() = when (contextReference) { is FragmentActivity -> contextReference @@ -20,33 +51,13 @@ val NavigationContext<*>.activity: FragmentActivity else -> throw IllegalStateException() } -internal fun NavigationContext<*>.fragmentHostFor(key: NavigationKey): FragmentHost? { - val primaryFragment = childFragmentManager.primaryNavigationFragment - val activeContainerId = primaryFragment?.getContainerId() - - val visibleContainers = navigationHandle().childContainers.filter { - when (contextReference) { - is FragmentActivity -> contextReference.findViewById(it.containerId).isVisible - is Fragment -> contextReference.requireView().findViewById(it.containerId).isVisible - else -> false - } - } - - val primaryDefinition = visibleContainers.firstOrNull { - it.containerId == activeContainerId && it.accept(key) - } - val definition = primaryDefinition - ?: visibleContainers.firstOrNull { it.accept(key) } - - return definition?.let { - FragmentHost( - containerId = it.containerId, - fragmentManager = childFragmentManager - ) - } ?: parentContext()?.fragmentHostFor(key) -} +@Suppress("UNCHECKED_CAST") // Higher level logic dictates this cast will pass +internal val T.navigationContext: ActivityContext + get() = viewModels { ViewModelProvider.NewInstanceFactory() } .value.navigationContext as ActivityContext -internal fun Fragment.getContainerId() = (requireView().parent as View).id +@Suppress("UNCHECKED_CAST") // Higher level logic dictates this cast will pass +internal val T.navigationContext: FragmentContext + get() = viewModels { ViewModelProvider.NewInstanceFactory() } .value.navigationContext as FragmentContext fun NavigationContext<*>.rootContext(): NavigationContext<*> { var parent = this @@ -74,10 +85,10 @@ fun NavigationContext<*>.leafContext(): NavigationContext<*> { return childContext.leafContext() } -@Suppress("UNCHECKED_CAST") // Higher level logic dictates this cast will pass -internal val T.navigationContext: ActivityContext - get() = viewModels { ViewModelProvider.NewInstanceFactory() } .value.navigationContext as ActivityContext - -@Suppress("UNCHECKED_CAST") // Higher level logic dictates this cast will pass -internal val T.navigationContext: FragmentContext - get() = viewModels { ViewModelProvider.NewInstanceFactory() } .value.navigationContext as FragmentContext \ No newline at end of file +// TODO - Move to sit with NavigationHandleViewModel? +internal fun NavigationContext<*>.getNavigationHandleViewModel(): NavigationHandleViewModel { + return when (this) { + is FragmentContext -> fragment.getNavigationHandle() + is ActivityContext -> activity.getNavigationHandle() + } as NavigationHandleViewModel +} diff --git a/enro-core/src/main/java/nav/enro/core/executors/NavigationExecutor.kt b/enro-core/src/main/java/nav/enro/core/NavigationExecutor.kt similarity index 73% rename from enro-core/src/main/java/nav/enro/core/executors/NavigationExecutor.kt rename to enro-core/src/main/java/nav/enro/core/NavigationExecutor.kt index 3d08583b..8d2eea98 100644 --- a/enro-core/src/main/java/nav/enro/core/executors/NavigationExecutor.kt +++ b/enro-core/src/main/java/nav/enro/core/NavigationExecutor.kt @@ -1,20 +1,16 @@ -package nav.enro.core.executors +package nav.enro.core -import nav.enro.core.NavigationInstruction -import nav.enro.core.NavigationKey -import nav.enro.core.context.NavigationContext -import nav.enro.core.navigator.Navigator import kotlin.reflect.KClass // This class is used primarily to simplify the lambda signature of NavigationExecutor.open class ExecutorArgs( val fromContext: NavigationContext, - val navigator: Navigator, + val navigator: Navigator, val key: KeyType, val instruction: NavigationInstruction.Open ) - +// TODO add pre/post open for more configuration abstract class NavigationExecutor( val fromType: KClass, val opensType: KClass, diff --git a/enro-core/src/main/java/nav/enro/core/NavigationHandleConfiguration.kt b/enro-core/src/main/java/nav/enro/core/NavigationHandleConfiguration.kt new file mode 100644 index 00000000..b6070d19 --- /dev/null +++ b/enro-core/src/main/java/nav/enro/core/NavigationHandleConfiguration.kt @@ -0,0 +1,42 @@ +package nav.enro.core + +import androidx.annotation.IdRes +import nav.enro.core.internal.handle.NavigationHandleViewModel +import kotlin.reflect.KClass + +internal class ChildContainer( + @IdRes val containerId: Int, + val accept: (NavigationKey) -> Boolean +) + +// TODO Move this to being a "Builder" and add data class for configuration? +class NavigationHandleConfiguration @PublishedApi internal constructor( + private val keyType: KClass +) { + internal var childContainers: List = listOf() + private set + + internal var defaultKey: T? = null + private set + + internal var onCloseRequested: TypedNavigationHandle.() -> Unit = { close() } + private set + + fun container(@IdRes containerId: Int, accept: (NavigationKey) -> Boolean = { true }) { + childContainers = childContainers + ChildContainer(containerId, accept) + } + + fun defaultKey(navigationKey: T) { + defaultKey = navigationKey + } + + fun onCloseRequested(block: TypedNavigationHandle.() -> Unit) { + onCloseRequested = block + } + + // TODO Store these properties ON the navigation handle? Rather than set individual fields? + internal fun applyTo(navigationHandleViewModel: NavigationHandleViewModel) { + navigationHandleViewModel.childContainers = childContainers + navigationHandleViewModel.internalOnCloseRequested = { onCloseRequested(navigationHandleViewModel.asTyped(keyType)) } + } +} \ No newline at end of file diff --git a/enro-core/src/main/java/nav/enro/core/NavigationHandleProperty.kt b/enro-core/src/main/java/nav/enro/core/NavigationHandleProperty.kt index 9f60fc98..e1874611 100644 --- a/enro-core/src/main/java/nav/enro/core/NavigationHandleProperty.kt +++ b/enro-core/src/main/java/nav/enro/core/NavigationHandleProperty.kt @@ -1,12 +1,9 @@ package nav.enro.core -import androidx.annotation.IdRes import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentActivity import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.ViewModelStoreOwner -import nav.enro.core.context.ChildContainer -import nav.enro.core.internal.handle.NavigationHandleViewModel import nav.enro.core.internal.handle.getNavigationHandleViewModel import java.lang.ref.WeakReference import kotlin.collections.set @@ -56,35 +53,6 @@ class NavigationHandleProperty @PublishedApi internal const } } -class NavigationHandleConfiguration @PublishedApi internal constructor( - private val keyType: KClass -) { - internal var childContainers: List = listOf() - private set - - internal var defaultKey: T? = null - private set - - internal var onCloseRequested: TypedNavigationHandle.() -> Unit = { close() } - private set - - fun container(@IdRes containerId: Int, accept: (NavigationKey) -> Boolean = { true }) { - childContainers = childContainers + ChildContainer(containerId, accept) - } - - fun defaultKey(navigationKey: T) { - defaultKey = navigationKey - } - - fun onCloseRequested(block: TypedNavigationHandle.() -> Unit) { - onCloseRequested = block - } - - internal fun applyTo(navigationHandleViewModel: NavigationHandleViewModel) { - navigationHandleViewModel.childContainers = childContainers - navigationHandleViewModel.internalOnCloseRequested = { onCloseRequested(navigationHandleViewModel.asTyped(keyType)) } - } -} inline fun FragmentActivity.navigationHandle( diff --git a/enro-core/src/main/java/nav/enro/core/NavigationInstruction.kt b/enro-core/src/main/java/nav/enro/core/NavigationInstruction.kt index f180d322..72c3bba1 100644 --- a/enro-core/src/main/java/nav/enro/core/NavigationInstruction.kt +++ b/enro-core/src/main/java/nav/enro/core/NavigationInstruction.kt @@ -14,8 +14,11 @@ enum class NavigationDirection { } internal const val OPEN_ARG = "nav.enro.core.OPEN_ARG" + +// TODO Put this somewhere closer to where it's being used? internal const val CONTEXT_ID_ARG = "nav.enro.core.CONTEXT_ID" +// TODO Hide some of these properties? sealed class NavigationInstruction { @Parcelize data class Open( diff --git a/enro-core/src/main/java/nav/enro/core/NavigationKey.kt b/enro-core/src/main/java/nav/enro/core/NavigationKey.kt index 15973994..4e6c329e 100644 --- a/enro-core/src/main/java/nav/enro/core/NavigationKey.kt +++ b/enro-core/src/main/java/nav/enro/core/NavigationKey.kt @@ -1,13 +1,6 @@ package nav.enro.core -import android.os.Bundle import android.os.Parcelable -import kotlinx.android.parcel.Parcelize interface NavigationKey : Parcelable -@Parcelize -internal class NoNavigationKeyBound( - val contextType: Class<*>, - val arguments: Bundle? -) : NavigationKey \ No newline at end of file diff --git a/enro-core/src/main/java/nav/enro/core/executors/ExecutorOverride.kt b/enro-core/src/main/java/nav/enro/core/NavigationOverride.kt similarity index 79% rename from enro-core/src/main/java/nav/enro/core/executors/ExecutorOverride.kt rename to enro-core/src/main/java/nav/enro/core/NavigationOverride.kt index 2acf464f..1ff45a4b 100644 --- a/enro-core/src/main/java/nav/enro/core/executors/ExecutorOverride.kt +++ b/enro-core/src/main/java/nav/enro/core/NavigationOverride.kt @@ -1,15 +1,15 @@ -package nav.enro.core.executors +package nav.enro.core import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentActivity -import nav.enro.core.NavigationKey -import nav.enro.core.context.NavigationContext +import nav.enro.core.activity.DefaultActivityExecutor +import nav.enro.core.fragment.DefaultFragmentExecutor import kotlin.reflect.KClass fun createOverride( fromClass: KClass, opensClass: KClass, - launch: ((ExecutorArgs) -> Unit), + open: ((ExecutorArgs) -> Unit), close: ((context: NavigationContext) -> Unit) ): NavigationExecutor = object : NavigationExecutor( @@ -18,7 +18,7 @@ fun createOverride( keyType = NavigationKey::class ) { override fun open(args: ExecutorArgs) { - launch(args) + open(args) } override fun close(context: NavigationContext) { @@ -27,13 +27,13 @@ fun createOverride( } inline fun createOverride( - noinline launch: ((ExecutorArgs) -> Unit) = defaultLaunch(), + noinline open: ((ExecutorArgs) -> Unit) = defaultOpen(), noinline close: (NavigationContext) -> Unit = defaultClose() ): NavigationExecutor = - createOverride(From::class, Opens::class, launch, close) + createOverride(From::class, Opens::class, open, close) @Suppress("UNCHECKED_CAST") -inline fun defaultLaunch(): ((ExecutorArgs) -> Unit) { +inline fun defaultOpen(): ((ExecutorArgs) -> Unit) { return when { FragmentActivity::class.java.isAssignableFrom(Opens::class.java) -> DefaultActivityExecutor::open as ((ExecutorArgs) -> Unit) diff --git a/enro-core/src/main/java/nav/enro/core/Navigator.kt b/enro-core/src/main/java/nav/enro/core/Navigator.kt new file mode 100644 index 00000000..908f51af --- /dev/null +++ b/enro-core/src/main/java/nav/enro/core/Navigator.kt @@ -0,0 +1,9 @@ +package nav.enro.core + +import kotlin.reflect.KClass + +interface Navigator { + val keyType: KClass + val contextType: KClass + val animations: NavigatorAnimations +} \ No newline at end of file diff --git a/enro-core/src/main/java/nav/enro/core/navigator/NavigatorAnimations.kt b/enro-core/src/main/java/nav/enro/core/NavigatorAnimations.kt similarity index 72% rename from enro-core/src/main/java/nav/enro/core/navigator/NavigatorAnimations.kt rename to enro-core/src/main/java/nav/enro/core/NavigatorAnimations.kt index af279912..dd598349 100644 --- a/enro-core/src/main/java/nav/enro/core/navigator/NavigatorAnimations.kt +++ b/enro-core/src/main/java/nav/enro/core/NavigatorAnimations.kt @@ -1,8 +1,9 @@ -package nav.enro.core.navigator +package nav.enro.core +import android.R import android.content.res.Resources -import nav.enro.core.internal.getAttributeResourceId +// Allow setting on NavigationDestination annotation? sealed class NavigatorAnimations { data class Resource( val forwardEnter: Int, @@ -33,17 +34,17 @@ sealed class NavigatorAnimations { ): NavigatorAnimations() companion object { val default = Attr( - forwardEnter = android.R.attr.activityOpenEnterAnimation, - forwardExit = android.R.attr.activityOpenExitAnimation, + forwardEnter = R.attr.activityOpenEnterAnimation, + forwardExit = R.attr.activityOpenExitAnimation, - replaceEnter = android.R.attr.activityOpenEnterAnimation, - replaceExit = android.R.attr.activityOpenExitAnimation, + replaceEnter = R.attr.activityOpenEnterAnimation, + replaceExit = R.attr.activityOpenExitAnimation, - replaceRootEnter = android.R.attr.taskOpenEnterAnimation, - replaceRootExit = android.R.attr.taskOpenExitAnimation, + replaceRootEnter = R.attr.taskOpenEnterAnimation, + replaceRootExit = R.attr.taskOpenExitAnimation, - closeEnter = android.R.attr.activityCloseEnterAnimation, - closeExit = android.R.attr.activityCloseExitAnimation + closeEnter = R.attr.activityCloseEnterAnimation, + closeExit = R.attr.activityCloseExitAnimation ) } } diff --git a/enro-core/src/main/java/nav/enro/core/activity/ActivityNavigator.kt b/enro-core/src/main/java/nav/enro/core/activity/ActivityNavigator.kt new file mode 100644 index 00000000..2cd1d2a0 --- /dev/null +++ b/enro-core/src/main/java/nav/enro/core/activity/ActivityNavigator.kt @@ -0,0 +1,27 @@ +package nav.enro.core.activity + +import androidx.fragment.app.FragmentActivity +import nav.enro.core.NavigationKey +import nav.enro.core.Navigator +import nav.enro.core.NavigatorAnimations +import kotlin.reflect.KClass + +class ActivityNavigator @PublishedApi internal constructor( + override val keyType: KClass, + override val contextType: KClass, + override val animations: NavigatorAnimations = NavigatorAnimations.default +) : Navigator + +fun createActivityNavigator( + keyType: Class, + activityType: Class +): Navigator = ActivityNavigator( + keyType = keyType.kotlin, + contextType = activityType.kotlin, +) + +inline fun createActivityNavigator(): Navigator = + createActivityNavigator( + keyType = KeyType::class.java, + activityType = ActivityType::class.java, + ) \ No newline at end of file diff --git a/enro-core/src/main/java/nav/enro/core/executors/DefaultActivityExecutor.kt b/enro-core/src/main/java/nav/enro/core/activity/DefaultActivityExecutor.kt similarity index 92% rename from enro-core/src/main/java/nav/enro/core/executors/DefaultActivityExecutor.kt rename to enro-core/src/main/java/nav/enro/core/activity/DefaultActivityExecutor.kt index 42acfc6b..64534661 100644 --- a/enro-core/src/main/java/nav/enro/core/executors/DefaultActivityExecutor.kt +++ b/enro-core/src/main/java/nav/enro/core/activity/DefaultActivityExecutor.kt @@ -1,11 +1,10 @@ -package nav.enro.core.executors +package nav.enro.core.activity import android.content.Intent import androidx.fragment.app.FragmentActivity import nav.enro.core.* -import nav.enro.core.context.NavigationContext -import nav.enro.core.context.activity -import nav.enro.core.navigator.ActivityNavigator +import nav.enro.core.NavigationContext +import nav.enro.core.activity object DefaultActivityExecutor : NavigationExecutor( fromType = Any::class, diff --git a/enro-core/src/main/java/nav/enro/core/internal/handle/NavigationHandleActivityBinder.kt b/enro-core/src/main/java/nav/enro/core/activity/NavigationHandleActivityBinder.kt similarity index 79% rename from enro-core/src/main/java/nav/enro/core/internal/handle/NavigationHandleActivityBinder.kt rename to enro-core/src/main/java/nav/enro/core/activity/NavigationHandleActivityBinder.kt index 2dd56205..c56c65cf 100644 --- a/enro-core/src/main/java/nav/enro/core/internal/handle/NavigationHandleActivityBinder.kt +++ b/enro-core/src/main/java/nav/enro/core/activity/NavigationHandleActivityBinder.kt @@ -1,4 +1,4 @@ -package nav.enro.core.internal.handle +package nav.enro.core.activity import android.app.Activity import android.app.Application @@ -6,11 +6,11 @@ import android.os.Bundle import android.view.ViewGroup import androidx.fragment.app.FragmentActivity import nav.enro.core.* -import nav.enro.core.context.ActivityContext -import nav.enro.core.context.leafContext -import nav.enro.core.context.navigationContext -import nav.enro.core.controller.navigationController -import nav.enro.core.internal.navigationHandle +import nav.enro.core.ActivityContext +import nav.enro.core.leafContext +import nav.enro.core.navigationContext +import nav.enro.core.internal.NoNavigationKey +import nav.enro.core.internal.handle.createNavigationHandleViewModel import java.util.* internal object NavigationHandleActivityBinder : Application.ActivityLifecycleCallbacks { @@ -18,9 +18,6 @@ internal object NavigationHandleActivityBinder : Application.ActivityLifecycleCa if (activity !is FragmentActivity) return activity.theme.applyStyle(android.R.style.Animation_Activity, false) - activity.supportFragmentManager.registerFragmentLifecycleCallbacks( - NavigationHandleFragmentBinder, true - ) val instruction = activity.intent.extras?.readOpenInstruction() val contextId = instruction?.instructionId @@ -31,7 +28,7 @@ internal object NavigationHandleActivityBinder : Application.ActivityLifecycleCa val defaultInstruction = NavigationInstruction.Open( instructionId = contextId, navigationDirection = NavigationDirection.FORWARD, - navigationKey = config?.defaultKey ?: NoNavigationKeyBound(activity::class.java, activity.intent.extras) + navigationKey = config?.defaultKey ?: NoNavigationKey(activity::class.java, activity.intent.extras) ) val handle = activity.createNavigationHandleViewModel( @@ -43,7 +40,7 @@ internal object NavigationHandleActivityBinder : Application.ActivityLifecycleCa handle.navigationContext = ActivityContext(activity) if(savedInstanceState == null) handle.executeDeeplink() activity.findViewById(android.R.id.content).viewTreeObserver.addOnGlobalLayoutListener { - activity.application.navigationController.active = activity.navigationContext.leafContext().navigationHandle() + activity.application.navigationController.active = activity.navigationContext.leafContext().getNavigationHandleViewModel() } } @@ -58,6 +55,6 @@ internal object NavigationHandleActivityBinder : Application.ActivityLifecycleCa override fun onActivityStopped(activity: Activity) {} override fun onActivityResumed(activity: Activity) { if(activity !is FragmentActivity) return - activity.application.navigationController.active = activity.navigationContext.leafContext().navigationHandle() + activity.application.navigationController.active = activity.navigationContext.leafContext().getNavigationHandleViewModel() } } \ No newline at end of file diff --git a/enro-core/src/main/java/nav/enro/core/context/NavigationContext.kt b/enro-core/src/main/java/nav/enro/core/context/NavigationContext.kt deleted file mode 100644 index 0019196b..00000000 --- a/enro-core/src/main/java/nav/enro/core/context/NavigationContext.kt +++ /dev/null @@ -1,50 +0,0 @@ -package nav.enro.core.context - -import androidx.annotation.IdRes -import androidx.fragment.app.Fragment -import androidx.fragment.app.FragmentActivity -import androidx.fragment.app.FragmentManager -import androidx.lifecycle.Lifecycle -import nav.enro.core.NavigationKey -import nav.enro.core.controller.NavigationController -import nav.enro.core.controller.navigationController -import nav.enro.core.navigator.ActivityNavigator -import nav.enro.core.navigator.FragmentNavigator -import nav.enro.core.navigator.Navigator - -data class ChildContainer( - @IdRes val containerId: Int, - val accept: (NavigationKey) -> Boolean -) - -sealed class NavigationContext( - val contextReference: ContextType -) { - abstract val controller: NavigationController - abstract val lifecycle: Lifecycle - abstract val childFragmentManager: FragmentManager - - internal open val navigator: Navigator? by lazy { - controller.navigatorForContextType(contextReference::class) as? Navigator - } -} - -internal class ActivityContext( - contextReference: ContextType, -) : NavigationContext(contextReference) { - override val controller get() = contextReference.application.navigationController - override val lifecycle get() = contextReference.lifecycle - override val navigator get() = super.navigator as? ActivityNavigator - override val childFragmentManager get() = contextReference.supportFragmentManager -} - -internal class FragmentContext( - contextReference: ContextType, -) : NavigationContext(contextReference) { - override val controller get() = contextReference.requireActivity().application.navigationController - override val lifecycle get() = contextReference.lifecycle - override val navigator get() = super.navigator as? FragmentNavigator - override val childFragmentManager get() = contextReference.childFragmentManager -} - - diff --git a/enro-core/src/main/java/nav/enro/core/controller/NavigationComponentBuilder.kt b/enro-core/src/main/java/nav/enro/core/controller/NavigationComponentBuilder.kt index c87490f2..290b364a 100644 --- a/enro-core/src/main/java/nav/enro/core/controller/NavigationComponentBuilder.kt +++ b/enro-core/src/main/java/nav/enro/core/controller/NavigationComponentBuilder.kt @@ -1,13 +1,11 @@ -package nav.enro.core.controller + package nav.enro.core.controller -import androidx.fragment.app.Fragment -import androidx.fragment.app.FragmentActivity -import nav.enro.core.NavigationKey -import nav.enro.core.context.NavigationContext -import nav.enro.core.executors.* -import nav.enro.core.navigator.* +import nav.enro.core.NavigationApplication +import nav.enro.core.NavigationExecutor +import nav.enro.core.Navigator import nav.enro.core.plugins.EnroPlugin +// TODO get rid of this, or give it a better name interface NavigationComponentBuilderCommand { fun execute(builder: NavigationComponentBuilder) } @@ -20,46 +18,21 @@ class NavigationComponentBuilder { @PublishedApi internal val plugins: MutableList = mutableListOf() - inline fun activityNavigator( - noinline block: ActivityNavigatorBuilder.() -> Unit = {} - ) { - navigators.add(createActivityNavigator(block)) - } - - inline fun fragmentNavigator( - noinline block: FragmentNavigatorBuilder.() -> Unit = {} - ) { - navigators.add(createFragmentNavigator(block)) - } - - inline fun syntheticNavigator( - destination: SyntheticDestination - ) { - navigators.add(createSyntheticNavigator(destination)) - } - - inline fun override( - noinline launch: ((ExecutorArgs) -> Unit) = defaultLaunch(), - noinline close: (NavigationContext) -> Unit = defaultClose() - ) { - overrides.add(createOverride(launch, close)) - } - - fun add(navigator: Navigator<*, *>) { + fun navigator(navigator: Navigator<*, *>) { navigators.add(navigator) } - fun add(override: NavigationExecutor<*, *, *>) { + fun override(override: NavigationExecutor<*, *, *>) { overrides.add(override) } - fun withComponent(builder: NavigationComponentBuilder) { + fun component(builder: NavigationComponentBuilder) { navigators.addAll(builder.navigators) overrides.addAll(builder.overrides) plugins.addAll(builder.plugins) } - fun withPlugin(enroPlugin: EnroPlugin) { + fun plugin(enroPlugin: EnroPlugin) { plugins.add(enroPlugin) } diff --git a/enro-core/src/main/java/nav/enro/core/controller/NavigationController.kt b/enro-core/src/main/java/nav/enro/core/controller/NavigationController.kt index 5ce2bb7b..e91455e5 100644 --- a/enro-core/src/main/java/nav/enro/core/controller/NavigationController.kt +++ b/enro-core/src/main/java/nav/enro/core/controller/NavigationController.kt @@ -4,26 +4,27 @@ import android.app.Application import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentActivity import nav.enro.core.* -import nav.enro.core.context.ActivityContext -import nav.enro.core.context.FragmentContext -import nav.enro.core.context.NavigationContext -import nav.enro.core.context.parentContext -import nav.enro.core.executors.DefaultActivityExecutor -import nav.enro.core.executors.DefaultFragmentExecutor -import nav.enro.core.executors.ExecutorArgs -import nav.enro.core.executors.NavigationExecutor -import nav.enro.core.internal.HiltSingleFragmentActivity -import nav.enro.core.internal.SingleFragmentActivity -import nav.enro.core.internal.SingleFragmentKey -import nav.enro.core.internal.handle.NavigationHandleActivityBinder +import nav.enro.core.activity.ActivityNavigator +import nav.enro.core.activity.DefaultActivityExecutor +import nav.enro.core.activity.NavigationHandleActivityBinder +import nav.enro.core.activity.createActivityNavigator +import nav.enro.core.fragment.DefaultFragmentExecutor +import nav.enro.core.fragment.FragmentNavigator +import nav.enro.core.fragment.NavigationHandleFragmentBinder +import nav.enro.core.fragment.internal.HiltSingleFragmentActivity +import nav.enro.core.fragment.internal.SingleFragmentActivity +import nav.enro.core.fragment.internal.SingleFragmentKey +import nav.enro.core.internal.NoKeyNavigator +import nav.enro.core.internal.NoNavigationKey import nav.enro.core.internal.handle.NavigationHandleViewModel -import nav.enro.core.internal.navigationHandle -import nav.enro.core.navigator.* import nav.enro.core.plugins.EnroHilt import nav.enro.core.plugins.EnroPlugin +import nav.enro.core.synthetic.SyntheticDestination +import nav.enro.core.synthetic.SyntheticNavigator import kotlin.reflect.KClass - +// TODO split functionality out into more focused classes (e.g. OverrideController or similar) +// TODO has too many sideways dependencies class NavigationController( navigators: List>, overrides: List> = listOf(), @@ -72,7 +73,7 @@ class NavigationController( .toMap() private val overrides = overrides.map { (it.fromType to it.opensType) to it }.toMap() - private val temporaryOverrides = mutableMapOf, KClass>, NavigationExecutor<*,*,*>>() + private val temporaryOverrides = mutableMapOf, KClass>, NavigationExecutor<*, *, *>>() internal val handles = mutableMapOf() @@ -149,7 +150,7 @@ class NavigationController( private fun openOverrideFor( fromContext: NavigationContext, - navigator: Navigator, + navigator: Navigator, instruction: NavigationInstruction.Open ): Boolean { @@ -192,7 +193,7 @@ class NavigationController( } private fun closeOverrideFor(navigationContext: NavigationContext): Boolean { - val parentInstruction = navigationContext.navigationHandle().instruction.parentInstruction + val parentInstruction = navigationContext.getNavigationHandleViewModel().instruction.parentInstruction val parentNavigator = parentInstruction ?.let { if(it.navigationKey is SingleFragmentKey) { @@ -205,7 +206,7 @@ class NavigationController( ?: return false val parentType = when(parentInstruction.navigationKey) { - is NoNavigationKeyBound -> parentInstruction.navigationKey.contextType.kotlin + is NoNavigationKey -> parentInstruction.navigationKey.contextType.kotlin else -> parentNavigator.contextType } @@ -221,7 +222,7 @@ class NavigationController( private fun NavigationInstruction.Open.setParentInstruction( parentContext: NavigationContext<*>, - navigator: Navigator + navigator: Navigator ): NavigationInstruction.Open { if (parentInstruction != null) return this @@ -239,19 +240,19 @@ class NavigationController( } val parentInstruction = when (navigationDirection) { - NavigationDirection.FORWARD -> findCorrectParentInstructionFor(parentContext.navigationHandle().instruction) - NavigationDirection.REPLACE -> findCorrectParentInstructionFor(parentContext.navigationHandle().instruction)?.parentInstruction + NavigationDirection.FORWARD -> findCorrectParentInstructionFor(parentContext.getNavigationHandleViewModel().instruction) + NavigationDirection.REPLACE -> findCorrectParentInstructionFor(parentContext.getNavigationHandleViewModel().instruction)?.parentInstruction NavigationDirection.REPLACE_ROOT -> null } return copy(parentInstruction = parentInstruction) } - fun addOverride(navigationExecutor: NavigationExecutor<*,*,*>) { + fun addOverride(navigationExecutor: NavigationExecutor<*, *, *>) { temporaryOverrides[navigationExecutor.fromType to navigationExecutor.opensType] = navigationExecutor } - fun removeOverride(navigationExecutor: NavigationExecutor<*,*,*>) { + fun removeOverride(navigationExecutor: NavigationExecutor<*, *, *>) { temporaryOverrides.remove(navigationExecutor.fromType to navigationExecutor.opensType) } @@ -263,6 +264,10 @@ class NavigationController( navigationApplication.registerActivityLifecycleCallbacks( NavigationHandleActivityBinder ) + + navigationApplication.registerActivityLifecycleCallbacks( + NavigationHandleFragmentBinder + ) } } } \ No newline at end of file diff --git a/enro-core/src/main/java/nav/enro/core/executors/DefaultFragmentExecutor.kt b/enro-core/src/main/java/nav/enro/core/fragment/DefaultFragmentExecutor.kt similarity index 93% rename from enro-core/src/main/java/nav/enro/core/executors/DefaultFragmentExecutor.kt rename to enro-core/src/main/java/nav/enro/core/fragment/DefaultFragmentExecutor.kt index d079273f..16cb9b0b 100644 --- a/enro-core/src/main/java/nav/enro/core/executors/DefaultFragmentExecutor.kt +++ b/enro-core/src/main/java/nav/enro/core/fragment/DefaultFragmentExecutor.kt @@ -1,18 +1,15 @@ -package nav.enro.core.executors +package nav.enro.core.fragment import android.os.Bundle import android.os.Handler import android.os.Looper +import android.view.View import androidx.core.view.ViewCompat import androidx.fragment.app.* import nav.enro.core.* -import nav.enro.core.context.* -import nav.enro.core.internal.AbstractSingleFragmentActivity -import nav.enro.core.internal.SingleFragmentKey -import nav.enro.core.internal.navigationHandle -import nav.enro.core.navigator.ActivityNavigator -import nav.enro.core.navigator.FragmentNavigator -import nav.enro.core.navigator.Navigator +import nav.enro.core.fragment.internal.AbstractSingleFragmentActivity +import nav.enro.core.fragment.internal.SingleFragmentKey +import nav.enro.core.fragment.internal.fragmentHostFor object DefaultFragmentExecutor : NavigationExecutor( fromType = Any::class, @@ -84,7 +81,7 @@ object DefaultFragmentExecutor : NavigationExecutor.getParentFragment(): Fragment? { val containerView = contextReference.getContainerId() - val parentInstruction = navigationHandle().instruction.parentInstruction + val parentInstruction = getNavigationHandleViewModel().instruction.parentInstruction parentInstruction ?: return null val previousNavigator = controller.navigatorForKeyType(parentInstruction.navigationKey::class) - if(previousNavigator is ActivityNavigator) return null - previousNavigator as FragmentNavigator<*, *> + if(previousNavigator !is FragmentNavigator) return null val previousHost = fragmentHostFor(parentInstruction.navigationKey) val previousFragment = previousHost?.fragmentManager?.findFragmentByTag(parentInstruction.instructionId) @@ -216,3 +212,5 @@ fun NavigationContext.getParentFragment(): Fragment? { else -> previousHost?.fragmentManager?.findFragmentById(previousHost.containerId) } } + +private fun Fragment.getContainerId() = (requireView().parent as View).id \ No newline at end of file diff --git a/enro-core/src/main/java/nav/enro/core/fragment/FragmentNavigator.kt b/enro-core/src/main/java/nav/enro/core/fragment/FragmentNavigator.kt new file mode 100644 index 00000000..6ed9177c --- /dev/null +++ b/enro-core/src/main/java/nav/enro/core/fragment/FragmentNavigator.kt @@ -0,0 +1,27 @@ +package nav.enro.core.fragment + +import androidx.fragment.app.Fragment +import nav.enro.core.NavigationKey +import nav.enro.core.Navigator +import nav.enro.core.NavigatorAnimations +import kotlin.reflect.KClass + +class FragmentNavigator @PublishedApi internal constructor( + override val keyType: KClass, + override val contextType: KClass, + override val animations: NavigatorAnimations = NavigatorAnimations.default +) : Navigator + +fun createFragmentNavigator( + keyType: Class, + fragmentType: Class +): Navigator = FragmentNavigator( + keyType = keyType.kotlin, + contextType = fragmentType.kotlin, +) + +inline fun createFragmentNavigator(): Navigator = + createFragmentNavigator( + keyType = KeyType::class.java, + fragmentType = FragmentType::class.java, + ) \ No newline at end of file diff --git a/enro-core/src/main/java/nav/enro/core/fragment/NavigationHandleFragmentBinder.kt b/enro-core/src/main/java/nav/enro/core/fragment/NavigationHandleFragmentBinder.kt new file mode 100644 index 00000000..187d6b66 --- /dev/null +++ b/enro-core/src/main/java/nav/enro/core/fragment/NavigationHandleFragmentBinder.kt @@ -0,0 +1,66 @@ +package nav.enro.core.fragment + +import android.app.Activity +import android.app.Application +import android.os.Bundle +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentActivity +import androidx.fragment.app.FragmentManager +import nav.enro.core.* +import nav.enro.core.internal.NoNavigationKey +import nav.enro.core.internal.handle.createNavigationHandleViewModel +import java.util.* + +internal object NavigationHandleFragmentBinder: Application.ActivityLifecycleCallbacks { + override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) { + if(activity !is FragmentActivity) return + activity.supportFragmentManager.registerFragmentLifecycleCallbacks( + FragmentCallbacks, true + ) + } + + override fun onActivityStarted(activity: Activity) {} + + override fun onActivityResumed(activity: Activity) {} + + override fun onActivityPaused(activity: Activity) {} + + override fun onActivityStopped(activity: Activity) {} + + override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {} + + override fun onActivityDestroyed(activity: Activity) {} + + private object FragmentCallbacks: FragmentManager.FragmentLifecycleCallbacks() { + override fun onFragmentCreated(fm: FragmentManager, fragment: Fragment, savedInstanceState: Bundle?) { + val instruction = fragment.arguments?.readOpenInstruction() + val contextId = instruction?.instructionId + ?: savedInstanceState?.getString(CONTEXT_ID_ARG) + ?: UUID.randomUUID().toString() + + val config = NavigationHandleProperty.getPendingConfig(fragment) + val defaultInstruction = NavigationInstruction.Open( + instructionId = contextId, + navigationDirection = NavigationDirection.FORWARD, + navigationKey = config?.defaultKey ?: NoNavigationKey(fragment::class.java, fragment.arguments) + ) + + val handle = fragment.createNavigationHandleViewModel( + fragment.requireActivity().application.navigationController, + instruction ?: defaultInstruction + ) + config?.applyTo(handle) + + handle.navigationContext = FragmentContext(fragment) + if(savedInstanceState == null) handle.executeDeeplink() + } + + override fun onFragmentSaveInstanceState(fm: FragmentManager, fragment: Fragment, outState: Bundle) { + outState.putString(CONTEXT_ID_ARG, fragment.getNavigationHandle().id) + } + + override fun onFragmentResumed(fm: FragmentManager, f: Fragment) { + f.requireActivity().application.navigationController.active = f.requireActivity().navigationContext.leafContext().getNavigationHandleViewModel() + } + } +} diff --git a/enro-core/src/main/java/nav/enro/core/fragment/internal/FragmentHost.kt b/enro-core/src/main/java/nav/enro/core/fragment/internal/FragmentHost.kt new file mode 100644 index 00000000..7cd01d49 --- /dev/null +++ b/enro-core/src/main/java/nav/enro/core/fragment/internal/FragmentHost.kt @@ -0,0 +1,42 @@ +package nav.enro.core.fragment.internal + +import android.view.View +import androidx.core.view.isVisible +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentActivity +import androidx.fragment.app.FragmentManager +import nav.enro.core.NavigationContext +import nav.enro.core.NavigationKey +import nav.enro.core.getNavigationHandleViewModel +import nav.enro.core.parentContext + +internal class FragmentHost( + internal val containerId: Int, + internal val fragmentManager: FragmentManager +) + +internal fun NavigationContext<*>.fragmentHostFor(key: NavigationKey): FragmentHost? { + val primaryFragment = childFragmentManager.primaryNavigationFragment + val activeContainerId = (primaryFragment?.view?.parent as View).id + + val visibleContainers = getNavigationHandleViewModel().childContainers.filter { + when (contextReference) { + is FragmentActivity -> contextReference.findViewById(it.containerId).isVisible + is Fragment -> contextReference.requireView().findViewById(it.containerId).isVisible + else -> false + } + } + + val primaryDefinition = visibleContainers.firstOrNull { + it.containerId == activeContainerId && it.accept(key) + } + val definition = primaryDefinition + ?: visibleContainers.firstOrNull { it.accept(key) } + + return definition?.let { + FragmentHost( + containerId = it.containerId, + fragmentManager = childFragmentManager + ) + } ?: parentContext()?.fragmentHostFor(key) +} \ No newline at end of file diff --git a/enro-core/src/main/java/nav/enro/core/internal/SingleFragmentActivity.kt b/enro-core/src/main/java/nav/enro/core/fragment/internal/SingleFragmentActivity.kt similarity index 92% rename from enro-core/src/main/java/nav/enro/core/internal/SingleFragmentActivity.kt rename to enro-core/src/main/java/nav/enro/core/fragment/internal/SingleFragmentActivity.kt index 627f576d..644458bf 100644 --- a/enro-core/src/main/java/nav/enro/core/internal/SingleFragmentActivity.kt +++ b/enro-core/src/main/java/nav/enro/core/fragment/internal/SingleFragmentActivity.kt @@ -1,6 +1,5 @@ -package nav.enro.core.internal +package nav.enro.core.fragment.internal -import android.R import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import dagger.hilt.android.AndroidEntryPoint @@ -16,7 +15,7 @@ internal data class SingleFragmentKey( internal abstract class AbstractSingleFragmentActivity : AppCompatActivity() { private val handle by navigationHandle { - container(R.id.content) + container(android.R.id.content) } override fun onCreate(savedInstanceState: Bundle?) { diff --git a/enro-core/src/main/java/nav/enro/core/internal/Extensions.kt b/enro-core/src/main/java/nav/enro/core/internal/Extensions.kt deleted file mode 100644 index 53e3fe6c..00000000 --- a/enro-core/src/main/java/nav/enro/core/internal/Extensions.kt +++ /dev/null @@ -1,43 +0,0 @@ -package nav.enro.core.internal - -import android.content.res.Resources -import android.util.TypedValue -import androidx.activity.OnBackPressedCallback -import androidx.fragment.app.Fragment -import androidx.fragment.app.FragmentActivity -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.LifecycleEventObserver -import androidx.lifecycle.LifecycleOwner -import nav.enro.core.context.* -import nav.enro.core.getNavigationHandle -import nav.enro.core.internal.handle.NavigationHandleViewModel - -internal fun Lifecycle.onEvent(on: Lifecycle.Event, block: () -> Unit) { - addObserver(object : LifecycleEventObserver { - override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) { - if(on == event) { - block() - } - } - }) -} - -internal fun FragmentActivity.addOnBackPressedListener(block: () -> Unit) { - onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) { - override fun handleOnBackPressed() { - block() - } - }) -} - -internal fun NavigationContext<*>.navigationHandle(): NavigationHandleViewModel { - return when (this) { - is FragmentContext -> fragment.getNavigationHandle() - is ActivityContext -> activity.getNavigationHandle() - } as NavigationHandleViewModel -} - -internal fun Resources.Theme.getAttributeResourceId(attr: Int) = TypedValue().let { - resolveAttribute(attr, it, true) - it.resourceId -} \ No newline at end of file diff --git a/enro-core/src/main/java/nav/enro/core/internal/NoNavigationKey.kt b/enro-core/src/main/java/nav/enro/core/internal/NoNavigationKey.kt new file mode 100644 index 00000000..0cefc2bc --- /dev/null +++ b/enro-core/src/main/java/nav/enro/core/internal/NoNavigationKey.kt @@ -0,0 +1,20 @@ +package nav.enro.core.internal + +import android.os.Bundle +import kotlinx.android.parcel.Parcelize +import nav.enro.core.NavigationKey +import nav.enro.core.Navigator +import nav.enro.core.NavigatorAnimations +import kotlin.reflect.KClass + +@Parcelize +internal class NoNavigationKey( + val contextType: Class<*>, + val arguments: Bundle? +) : NavigationKey + +internal class NoKeyNavigator: Navigator { + override val keyType: KClass = NoNavigationKey::class + override val contextType: KClass = Nothing::class + override val animations: NavigatorAnimations = NavigatorAnimations.default +} \ No newline at end of file diff --git a/enro-core/src/main/java/nav/enro/core/internal/handle/NavigationHandleFragmentBinder.kt b/enro-core/src/main/java/nav/enro/core/internal/handle/NavigationHandleFragmentBinder.kt deleted file mode 100644 index 864ade33..00000000 --- a/enro-core/src/main/java/nav/enro/core/internal/handle/NavigationHandleFragmentBinder.kt +++ /dev/null @@ -1,47 +0,0 @@ -package nav.enro.core.internal.handle - -import android.os.Bundle -import androidx.fragment.app.Fragment -import androidx.fragment.app.FragmentManager -import nav.enro.core.* -import nav.enro.core.context.FragmentContext -import nav.enro.core.context.leafContext -import nav.enro.core.context.navigationContext -import nav.enro.core.controller.navigationController -import nav.enro.core.internal.navigationHandle -import java.util.* - -internal object NavigationHandleFragmentBinder: FragmentManager.FragmentLifecycleCallbacks() { - override fun onFragmentCreated(fm: FragmentManager, fragment: Fragment, savedInstanceState: Bundle?) { - val instruction = fragment.arguments?.readOpenInstruction() - val contextId = instruction?.instructionId - ?: savedInstanceState?.getString(CONTEXT_ID_ARG) - ?: UUID.randomUUID().toString() - - val config = NavigationHandleProperty.getPendingConfig(fragment) - val defaultInstruction = NavigationInstruction.Open( - instructionId = contextId, - navigationDirection = NavigationDirection.FORWARD, - navigationKey = config?.defaultKey ?: NoNavigationKeyBound(fragment::class.java, fragment.arguments) - ) - - val handle = fragment.createNavigationHandleViewModel( - fragment.requireActivity().application.navigationController, - instruction ?: defaultInstruction - ) - config?.applyTo(handle) - - handle.navigationContext = FragmentContext(fragment) - if(savedInstanceState == null) handle.executeDeeplink() - } - - override fun onFragmentSaveInstanceState(fm: FragmentManager, fragment: Fragment, outState: Bundle) { - outState.putString(CONTEXT_ID_ARG, fragment.getNavigationHandle().id) - } - - override fun onFragmentResumed(fm: FragmentManager, f: Fragment) { - f.requireActivity().application.navigationController.active = f.requireActivity().navigationContext.leafContext().navigationHandle() - } - - -} \ No newline at end of file diff --git a/enro-core/src/main/java/nav/enro/core/internal/handle/NavigationHandleViewModel.kt b/enro-core/src/main/java/nav/enro/core/internal/handle/NavigationHandleViewModel.kt index b225abf0..5d8f9e80 100644 --- a/enro-core/src/main/java/nav/enro/core/internal/handle/NavigationHandleViewModel.kt +++ b/enro-core/src/main/java/nav/enro/core/internal/handle/NavigationHandleViewModel.kt @@ -3,52 +3,12 @@ package nav.enro.core.internal.handle import android.os.Bundle import android.os.Handler import android.os.Looper -import androidx.activity.viewModels -import androidx.fragment.app.Fragment +import androidx.activity.OnBackPressedCallback import androidx.fragment.app.FragmentActivity -import androidx.fragment.app.viewModels import androidx.lifecycle.* import nav.enro.core.* -import nav.enro.core.context.* import nav.enro.core.controller.NavigationController -import nav.enro.core.internal.addOnBackPressedListener -import nav.enro.core.internal.navigationHandle -import nav.enro.core.internal.onEvent - -internal class NavigationHandleViewModelFactory( - private val navigationController: NavigationController, - private val instruction: NavigationInstruction.Open -) : ViewModelProvider.Factory { - override fun create(modelClass: Class): T { - return NavigationHandleViewModel( - navigationController, - instruction - ) as T - } -} - -internal fun ViewModelStoreOwner.createNavigationHandleViewModel( - navigationController: NavigationController, - instruction: NavigationInstruction.Open -): NavigationHandleViewModel { - return when(this) { - is FragmentActivity -> viewModels { - NavigationHandleViewModelFactory(navigationController, instruction) - }.value - is Fragment -> viewModels { - NavigationHandleViewModelFactory(navigationController, instruction) - }.value - else -> throw IllegalArgumentException("ViewModelStoreOwner must be a Fragment or Activity") - } -} - -internal fun ViewModelStoreOwner.getNavigationHandleViewModel(): NavigationHandleViewModel { - return when(this) { - is FragmentActivity -> viewModels { ViewModelProvider.NewInstanceFactory() }.value - is Fragment -> viewModels { ViewModelProvider.NewInstanceFactory() }.value - else -> throw IllegalArgumentException("ViewModelStoreOwner must be a Fragment or Activity") - } -} +import nav.enro.core.internal.NoNavigationKey internal class NavigationHandleViewModel( override val controller: NavigationController, @@ -57,10 +17,10 @@ internal class NavigationHandleViewModel( private var pendingInstruction: NavigationInstruction? = null - internal val hasKey get() = instruction.navigationKey !is NoNavigationKeyBound + internal val hasKey get() = instruction.navigationKey !is NoNavigationKey override val key: NavigationKey get() { - if(instruction.navigationKey is NoNavigationKeyBound) throw IllegalStateException("This NavigationHandle has no NavigationKey") + if(instruction.navigationKey is NoNavigationKey) throw IllegalStateException("This NavigationHandle has no NavigationKey") return instruction.navigationKey } override val id: String get() = instruction.instructionId @@ -114,7 +74,7 @@ internal class NavigationHandleViewModel( private fun registerOnBackPressedListener(context: NavigationContext) { if (context is ActivityContext) { context.activity.addOnBackPressedListener { - context.leafContext().navigationHandle().internalOnCloseRequested() + context.leafContext().getNavigationHandleViewModel().internalOnCloseRequested() } } } @@ -156,4 +116,23 @@ internal class NavigationHandleViewModel( controller.handles.remove(id) lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY) } +} + + +private fun Lifecycle.onEvent(on: Lifecycle.Event, block: () -> Unit) { + addObserver(object : LifecycleEventObserver { + override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) { + if(on == event) { + block() + } + } + }) +} + +private fun FragmentActivity.addOnBackPressedListener(block: () -> Unit) { + onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) { + override fun handleOnBackPressed() { + block() + } + }) } \ No newline at end of file diff --git a/enro-core/src/main/java/nav/enro/core/internal/handle/NavigationHandleViewModelFactory.kt b/enro-core/src/main/java/nav/enro/core/internal/handle/NavigationHandleViewModelFactory.kt new file mode 100644 index 00000000..31a6ee54 --- /dev/null +++ b/enro-core/src/main/java/nav/enro/core/internal/handle/NavigationHandleViewModelFactory.kt @@ -0,0 +1,46 @@ +package nav.enro.core.internal.handle + +import androidx.activity.viewModels +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentActivity +import androidx.fragment.app.viewModels +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.ViewModelStoreOwner +import nav.enro.core.NavigationInstruction +import nav.enro.core.controller.NavigationController + +internal class NavigationHandleViewModelFactory( + private val navigationController: NavigationController, + private val instruction: NavigationInstruction.Open +) : ViewModelProvider.Factory { + override fun create(modelClass: Class): T { + return NavigationHandleViewModel( + navigationController, + instruction + ) as T + } +} + +internal fun ViewModelStoreOwner.createNavigationHandleViewModel( + navigationController: NavigationController, + instruction: NavigationInstruction.Open +): NavigationHandleViewModel { + return when(this) { + is FragmentActivity -> viewModels { + NavigationHandleViewModelFactory(navigationController, instruction) + }.value + is Fragment -> viewModels { + NavigationHandleViewModelFactory(navigationController, instruction) + }.value + else -> throw IllegalArgumentException("ViewModelStoreOwner must be a Fragment or Activity") + } +} + +internal fun ViewModelStoreOwner.getNavigationHandleViewModel(): NavigationHandleViewModel { + return when(this) { + is FragmentActivity -> viewModels { ViewModelProvider.NewInstanceFactory() }.value + is Fragment -> viewModels { ViewModelProvider.NewInstanceFactory() }.value + else -> throw IllegalArgumentException("ViewModelStoreOwner must be a Fragment or Activity") + } +} \ No newline at end of file diff --git a/enro-core/src/main/java/nav/enro/core/navigator/FragmentHost.kt b/enro-core/src/main/java/nav/enro/core/navigator/FragmentHost.kt deleted file mode 100644 index 084dd0a6..00000000 --- a/enro-core/src/main/java/nav/enro/core/navigator/FragmentHost.kt +++ /dev/null @@ -1,11 +0,0 @@ -package nav.enro.core.navigator - -import androidx.fragment.app.Fragment -import androidx.fragment.app.FragmentManager -import nav.enro.core.NavigationKey -import kotlin.reflect.KClass - -internal class FragmentHost( - internal val containerId: Int, - internal val fragmentManager: FragmentManager -) diff --git a/enro-core/src/main/java/nav/enro/core/navigator/Navigator.kt b/enro-core/src/main/java/nav/enro/core/navigator/Navigator.kt deleted file mode 100644 index 83dc191f..00000000 --- a/enro-core/src/main/java/nav/enro/core/navigator/Navigator.kt +++ /dev/null @@ -1,39 +0,0 @@ -package nav.enro.core.navigator - -import androidx.fragment.app.Fragment -import androidx.fragment.app.FragmentActivity -import nav.enro.core.NavigationKey -import nav.enro.core.NoNavigationKeyBound -import kotlin.reflect.KClass - -interface Navigator { - val keyType: KClass - val contextType: KClass - val animations: NavigatorAnimations -} - -class ActivityNavigator @PublishedApi internal constructor( - override val keyType: KClass, - override val contextType: KClass, - override val animations: NavigatorAnimations = NavigatorAnimations.default -) : Navigator - -class FragmentNavigator @PublishedApi internal constructor( - override val keyType: KClass, - override val contextType: KClass, - override val animations: NavigatorAnimations = NavigatorAnimations.default -) : Navigator - -class SyntheticNavigator @PublishedApi internal constructor( - override val keyType: KClass, - val destination: SyntheticDestination -) : Navigator { - override val contextType: KClass = Any::class - override val animations: NavigatorAnimations = NavigatorAnimations.default -} - -internal class NoKeyNavigator: Navigator { - override val keyType: KClass = NoNavigationKeyBound::class - override val contextType: KClass = Any::class - override val animations: NavigatorAnimations = NavigatorAnimations.default -} \ No newline at end of file diff --git a/enro-core/src/main/java/nav/enro/core/navigator/NavigatorDefinition.kt b/enro-core/src/main/java/nav/enro/core/navigator/NavigatorDefinition.kt deleted file mode 100644 index 96d65496..00000000 --- a/enro-core/src/main/java/nav/enro/core/navigator/NavigatorDefinition.kt +++ /dev/null @@ -1,76 +0,0 @@ -package nav.enro.core.navigator - -import androidx.fragment.app.Fragment -import androidx.fragment.app.FragmentActivity -import nav.enro.core.NavigationKey -import kotlin.reflect.KClass - -fun createActivityNavigator( - navigationKeyType: KClass, - activityType: KClass, - block: ActivityNavigatorBuilder.() -> Unit = {} -): Navigator = - ActivityNavigatorBuilder( - keyType = navigationKeyType, - contextType = activityType - ).apply(block).build() - -inline fun createActivityNavigator( - noinline block: ActivityNavigatorBuilder.() -> Unit = {} -): Navigator = createActivityNavigator(T::class, A::class, block) - -class ActivityNavigatorBuilder( - private val keyType: KClass, - @PublishedApi internal val contextType: KClass -) { - fun build() = ActivityNavigator( - keyType = keyType, - contextType = contextType, - ) -} - -fun createFragmentNavigator( - navigationKeyType: KClass, - fragmentType: KClass, - block: FragmentNavigatorBuilder.() -> Unit = {} -): Navigator = - FragmentNavigatorBuilder( - keyType = navigationKeyType, - contextType = fragmentType, - ).apply(block).build() - -inline fun createFragmentNavigator( - noinline block: FragmentNavigatorBuilder.() -> Unit = {} -): Navigator = createFragmentNavigator(T::class, A::class, block) - -class FragmentNavigatorBuilder( - private val keyType: KClass, - private val contextType: KClass -) { - fun build() = FragmentNavigator( - keyType = keyType, - contextType = contextType, - ) -} - -fun createSyntheticNavigator( - navigationKeyType: KClass, - destination: SyntheticDestination -): Navigator = - SyntheticNavigatorBuilder( - keyType = navigationKeyType, - destination = destination - ).build() - -inline fun createSyntheticNavigator(destination: SyntheticDestination): Navigator = - createSyntheticNavigator(T::class, destination) - -class SyntheticNavigatorBuilder( - private val keyType: KClass, - private val destination: SyntheticDestination -) { - fun build() = SyntheticNavigator( - keyType = keyType, - destination = destination - ) -} \ No newline at end of file diff --git a/enro-core/src/main/java/nav/enro/core/navigator/Synthetic.kt b/enro-core/src/main/java/nav/enro/core/synthetic/SyntheticDestination.kt similarity index 77% rename from enro-core/src/main/java/nav/enro/core/navigator/Synthetic.kt rename to enro-core/src/main/java/nav/enro/core/synthetic/SyntheticDestination.kt index b63d102b..f6b52cd4 100644 --- a/enro-core/src/main/java/nav/enro/core/navigator/Synthetic.kt +++ b/enro-core/src/main/java/nav/enro/core/synthetic/SyntheticDestination.kt @@ -1,8 +1,8 @@ -package nav.enro.core.navigator +package nav.enro.core.synthetic import nav.enro.core.NavigationInstruction import nav.enro.core.NavigationKey -import nav.enro.core.context.NavigationContext +import nav.enro.core.NavigationContext interface SyntheticDestination { fun process( diff --git a/enro-core/src/main/java/nav/enro/core/synthetic/SyntheticNavigator.kt b/enro-core/src/main/java/nav/enro/core/synthetic/SyntheticNavigator.kt new file mode 100644 index 00000000..41a97e6f --- /dev/null +++ b/enro-core/src/main/java/nav/enro/core/synthetic/SyntheticNavigator.kt @@ -0,0 +1,32 @@ +package nav.enro.core.synthetic + +import nav.enro.core.NavigationKey +import nav.enro.core.Navigator +import nav.enro.core.NavigatorAnimations +import kotlin.reflect.KClass + + +class SyntheticNavigator @PublishedApi internal constructor( + override val keyType: KClass, + val destination: SyntheticDestination +) : Navigator> { + override val contextType: KClass> = SyntheticDestination::class + override val animations: NavigatorAnimations = NavigatorAnimations.default +} + +fun createSyntheticNavigator( + navigationKeyType: KClass, + destination: SyntheticDestination +): Navigator> = + SyntheticNavigator( + keyType = navigationKeyType, + destination = destination + ) + +inline fun createSyntheticNavigator( + destination: SyntheticDestination +): Navigator> = + SyntheticNavigator( + keyType = KeyType::class, + destination = destination + ) \ No newline at end of file diff --git a/enro-masterdetail/src/main/java/nav/enro/masterdetail/MasterDetailComponent.kt b/enro-masterdetail/src/main/java/nav/enro/masterdetail/MasterDetailComponent.kt index 648429e8..d45bfe5f 100644 --- a/enro-masterdetail/src/main/java/nav/enro/masterdetail/MasterDetailComponent.kt +++ b/enro-masterdetail/src/main/java/nav/enro/masterdetail/MasterDetailComponent.kt @@ -9,13 +9,13 @@ import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.LifecycleOwner import nav.enro.core.NavigationKey import nav.enro.core.addOpenInstruction -import nav.enro.core.context.activity -import nav.enro.core.context.fragment +import nav.enro.core.activity +import nav.enro.core.fragment import nav.enro.core.controller.NavigationController -import nav.enro.core.controller.navigationController -import nav.enro.core.executors.DefaultActivityExecutor -import nav.enro.core.executors.ExecutorArgs -import nav.enro.core.executors.createOverride +import nav.enro.core.navigationController +import nav.enro.core.activity.DefaultActivityExecutor +import nav.enro.core.ExecutorArgs +import nav.enro.core.createOverride import nav.enro.core.forward import nav.enro.core.getNavigationHandle import kotlin.properties.ReadOnlyProperty @@ -42,7 +42,7 @@ class MasterDetailProperty( createOverride( owningType, masterType, - launch = { + open = { val fragment = it.fromContext.childFragmentManager.fragmentFactory.instantiate( masterType.java.classLoader!!, masterType.java.name @@ -64,7 +64,7 @@ class MasterDetailProperty( createOverride( owningType, detailType, - launch = { + open = { if(!Fragment::class.java.isAssignableFrom(it.navigator.contextType.java)) { Log.e("Enro", "Attempted to open ${detailKey::class.java} as a Detail in ${it.fromContext.contextReference}, " + "but ${detailKey::class.java}'s NavigationDestination is not a Fragment! Defaulting to standard navigation") diff --git a/enro-multistack/src/main/java/nav/enro/multistack/MultistackControllerFragment.kt b/enro-multistack/src/main/java/nav/enro/multistack/MultistackControllerFragment.kt index 2c39a066..45f5f105 100644 --- a/enro-multistack/src/main/java/nav/enro/multistack/MultistackControllerFragment.kt +++ b/enro-multistack/src/main/java/nav/enro/multistack/MultistackControllerFragment.kt @@ -12,16 +12,10 @@ import androidx.annotation.AnimRes import androidx.core.view.isVisible import androidx.fragment.app.Fragment import androidx.lifecycle.MutableLiveData -import nav.enro.core.NavigationDirection -import nav.enro.core.NavigationInstruction -import nav.enro.core.close -import nav.enro.core.controller.navigationController -import nav.enro.core.executors.DefaultFragmentExecutor -import nav.enro.core.getNavigationHandle -import nav.enro.core.navigator.ActivityNavigator -import nav.enro.core.navigator.FragmentNavigator -import nav.enro.core.navigator.NavigatorAnimations -import nav.enro.core.navigator.toResource +import nav.enro.core.* +import nav.enro.core.activity.ActivityNavigator +import nav.enro.core.fragment.DefaultFragmentExecutor +import nav.enro.core.fragment.FragmentNavigator @PublishedApi @@ -92,7 +86,7 @@ internal class MultistackControllerFragment : Fragment(), ViewTreeObserver.OnGlo val controller = requireActivity().application.navigationController val navigator = controller.navigatorForKeyType(container.rootKey::class) - if(navigator is ActivityNavigator<*,*>) { + if(navigator is ActivityNavigator<*, *>) { listenForEvents = true return } diff --git a/enro/src/androidTest/java/nav/enro/TestApplication.kt b/enro/src/androidTest/java/nav/enro/TestApplication.kt index 7d8dd2a3..b4f0dedc 100644 --- a/enro/src/androidTest/java/nav/enro/TestApplication.kt +++ b/enro/src/androidTest/java/nav/enro/TestApplication.kt @@ -1,10 +1,10 @@ package nav.enro import android.app.Application -import nav.enro.core.controller.NavigationApplication +import nav.enro.core.NavigationApplication +import nav.enro.core.activity.createActivityNavigator import nav.enro.core.controller.NavigationController -import nav.enro.core.navigator.createActivityNavigator -import nav.enro.core.navigator.createFragmentNavigator +import nav.enro.core.fragment.createFragmentNavigator class TestApplication : Application(), NavigationApplication { diff --git a/enro/src/androidTest/java/nav/enro/core/Extensions.kt b/enro/src/androidTest/java/nav/enro/core/TestExtensions.kt similarity index 100% rename from enro/src/androidTest/java/nav/enro/core/Extensions.kt rename to enro/src/androidTest/java/nav/enro/core/TestExtensions.kt diff --git a/enro/src/androidTest/java/nav/enro/core/overrides/ActivityToActivityOverrideTests.kt b/enro/src/androidTest/java/nav/enro/core/overrides/ActivityToActivityOverrideTests.kt index aba3382d..aade965e 100644 --- a/enro/src/androidTest/java/nav/enro/core/overrides/ActivityToActivityOverrideTests.kt +++ b/enro/src/androidTest/java/nav/enro/core/overrides/ActivityToActivityOverrideTests.kt @@ -5,10 +5,10 @@ import androidx.test.core.app.ActivityScenario import junit.framework.Assert.assertTrue import nav.enro.* import nav.enro.core.* -import nav.enro.core.controller.navigationController -import nav.enro.core.executors.createOverride -import nav.enro.core.executors.defaultClose -import nav.enro.core.executors.defaultLaunch +import nav.enro.core.navigationController +import nav.enro.core.createOverride +import nav.enro.core.defaultClose +import nav.enro.core.defaultOpen import org.junit.Test class ActivityToActivityOverrideTests() { @@ -18,9 +18,9 @@ class ActivityToActivityOverrideTests() { var launchOverrideCalled = false application.navigationController.addOverride( createOverride( - launch = { + open = { launchOverrideCalled = true - defaultLaunch().invoke(it) + defaultOpen().invoke(it) } ) ) @@ -63,9 +63,9 @@ class ActivityToActivityOverrideTests() { var launchOverrideCalled = false application.navigationController.addOverride( createOverride( - launch = { + open = { launchOverrideCalled = true - defaultLaunch().invoke(it) + defaultOpen().invoke(it) } ) ) @@ -125,9 +125,9 @@ class ActivityToActivityOverrideTests() { var launchOverrideCalled = false application.navigationController.addOverride( createOverride( - launch = { + open = { launchOverrideCalled = true - defaultLaunch().invoke(it) + defaultOpen().invoke(it) } ) ) diff --git a/enro/src/androidTest/java/nav/enro/core/overrides/ActivityToFragmentOverrideTests.kt b/enro/src/androidTest/java/nav/enro/core/overrides/ActivityToFragmentOverrideTests.kt index 330699e7..a4fe7a21 100644 --- a/enro/src/androidTest/java/nav/enro/core/overrides/ActivityToFragmentOverrideTests.kt +++ b/enro/src/androidTest/java/nav/enro/core/overrides/ActivityToFragmentOverrideTests.kt @@ -5,10 +5,10 @@ import androidx.test.core.app.ActivityScenario import junit.framework.Assert.assertTrue import nav.enro.* import nav.enro.core.* -import nav.enro.core.controller.navigationController -import nav.enro.core.executors.createOverride -import nav.enro.core.executors.defaultClose -import nav.enro.core.executors.defaultLaunch +import nav.enro.core.navigationController +import nav.enro.core.createOverride +import nav.enro.core.defaultClose +import nav.enro.core.defaultOpen import org.junit.Test class ActivityToFragmentOverrideTests() { @@ -18,9 +18,9 @@ class ActivityToFragmentOverrideTests() { var launchOverrideCalled = false application.navigationController.addOverride( createOverride( - launch = { + open = { launchOverrideCalled = true - defaultLaunch().invoke(it) + defaultOpen().invoke(it) } ) ) @@ -62,9 +62,9 @@ class ActivityToFragmentOverrideTests() { var launchOverrideCalled = false application.navigationController.addOverride( createOverride( - launch = { + open = { launchOverrideCalled = true - defaultLaunch().invoke(it) + defaultOpen().invoke(it) } ) ) @@ -123,9 +123,9 @@ class ActivityToFragmentOverrideTests() { var launchOverrideCalled = false application.navigationController.addOverride( createOverride( - launch = { + open = { launchOverrideCalled = true - defaultLaunch().invoke(it) + defaultOpen().invoke(it) } ) ) diff --git a/enro/src/androidTest/java/nav/enro/core/overrides/FragmentToActivityOverrideTests.kt b/enro/src/androidTest/java/nav/enro/core/overrides/FragmentToActivityOverrideTests.kt index 64fb076d..5734c4ae 100644 --- a/enro/src/androidTest/java/nav/enro/core/overrides/FragmentToActivityOverrideTests.kt +++ b/enro/src/androidTest/java/nav/enro/core/overrides/FragmentToActivityOverrideTests.kt @@ -5,9 +5,9 @@ import androidx.test.core.app.ActivityScenario import junit.framework.Assert.assertTrue import nav.enro.* import nav.enro.core.* -import nav.enro.core.controller.navigationController -import nav.enro.core.executors.createOverride -import nav.enro.core.executors.defaultLaunch +import nav.enro.core.navigationController +import nav.enro.core.createOverride +import nav.enro.core.defaultOpen import org.junit.Before import org.junit.Test @@ -33,9 +33,9 @@ class FragmentToActivityOverrideTests() { var launchOverrideCalled = false application.navigationController.addOverride( createOverride( - launch = { + open = { launchOverrideCalled = true - defaultLaunch().invoke(it) + defaultOpen().invoke(it) } ) ) @@ -57,9 +57,9 @@ class FragmentToActivityOverrideTests() { var closeOverrideCalled = false application.navigationController.addOverride( createOverride( - launch = { + open = { closeOverrideCalled = true - defaultLaunch().invoke(it) + defaultOpen().invoke(it) } ) ) @@ -86,9 +86,9 @@ class FragmentToActivityOverrideTests() { var launchOverrideCalled = false application.navigationController.addOverride( createOverride( - launch = { + open = { launchOverrideCalled = true - defaultLaunch().invoke(it) + defaultOpen().invoke(it) } ) ) @@ -110,9 +110,9 @@ class FragmentToActivityOverrideTests() { var closeOverrideCalled = false application.navigationController.addOverride( createOverride( - launch = { + open = { closeOverrideCalled = true - defaultLaunch().invoke(it) + defaultOpen().invoke(it) } ) ) diff --git a/enro/src/androidTest/java/nav/enro/core/overrides/FragmentToFragmentOverrideTests.kt b/enro/src/androidTest/java/nav/enro/core/overrides/FragmentToFragmentOverrideTests.kt index 7eb2d160..02be6b48 100644 --- a/enro/src/androidTest/java/nav/enro/core/overrides/FragmentToFragmentOverrideTests.kt +++ b/enro/src/androidTest/java/nav/enro/core/overrides/FragmentToFragmentOverrideTests.kt @@ -5,9 +5,9 @@ import androidx.test.core.app.ActivityScenario import junit.framework.Assert.assertTrue import nav.enro.* import nav.enro.core.* -import nav.enro.core.controller.navigationController -import nav.enro.core.executors.createOverride -import nav.enro.core.executors.defaultLaunch +import nav.enro.core.navigationController +import nav.enro.core.createOverride +import nav.enro.core.defaultOpen import org.junit.Before import org.junit.Test @@ -33,9 +33,9 @@ class FragmentToFragmentOverrideTests() { var launchOverrideCalled = false application.navigationController.addOverride( createOverride( - launch = { + open = { launchOverrideCalled = true - defaultLaunch().invoke(it) + defaultOpen().invoke(it) } ) ) @@ -57,9 +57,9 @@ class FragmentToFragmentOverrideTests() { var closeOverrideCalled = false application.navigationController.addOverride( createOverride( - launch = { + open = { closeOverrideCalled = true - defaultLaunch().invoke(it) + defaultOpen().invoke(it) } ) ) @@ -86,9 +86,9 @@ class FragmentToFragmentOverrideTests() { var launchOverrideCalled = false application.navigationController.addOverride( createOverride( - launch = { + open = { launchOverrideCalled = true - defaultLaunch().invoke(it) + defaultOpen().invoke(it) } ) ) @@ -110,9 +110,9 @@ class FragmentToFragmentOverrideTests() { var closeOverrideCalled = false application.navigationController.addOverride( createOverride( - launch = { + open = { closeOverrideCalled = true - defaultLaunch().invoke(it) + defaultOpen().invoke(it) } ) ) @@ -139,9 +139,9 @@ class FragmentToFragmentOverrideTests() { var launchOverrideCalled = false application.navigationController.addOverride( createOverride( - launch = { + open = { launchOverrideCalled = true - defaultLaunch().invoke(it) + defaultOpen().invoke(it) } ) ) @@ -163,9 +163,9 @@ class FragmentToFragmentOverrideTests() { var closeOverrideCalled = false application.navigationController.addOverride( createOverride( - launch = { + open = { closeOverrideCalled = true - defaultLaunch().invoke(it) + defaultOpen().invoke(it) } ) ) diff --git a/example/src/main/java/nav/enro/example/ExampleApplication.kt b/example/src/main/java/nav/enro/example/ExampleApplication.kt index 72e79df8..0fe0b080 100644 --- a/example/src/main/java/nav/enro/example/ExampleApplication.kt +++ b/example/src/main/java/nav/enro/example/ExampleApplication.kt @@ -2,9 +2,8 @@ package nav.enro.example import android.app.Application import nav.enro.annotations.NavigationComponent -import nav.enro.core.controller.NavigationApplication -import nav.enro.core.controller.NavigationController -import nav.enro.core.controller.navigationController +import nav.enro.core.NavigationApplication +import nav.enro.core.navigationController import nav.enro.core.plugins.EnroLogger import nav.enro.result.EnroResult diff --git a/example/src/main/java/nav/enro/example/SimpleMessage.kt b/example/src/main/java/nav/enro/example/SimpleMessage.kt index 3cf7f2dd..417a4724 100644 --- a/example/src/main/java/nav/enro/example/SimpleMessage.kt +++ b/example/src/main/java/nav/enro/example/SimpleMessage.kt @@ -5,10 +5,10 @@ import kotlinx.android.parcel.Parcelize import nav.enro.annotations.NavigationDestination import nav.enro.core.NavigationInstruction import nav.enro.core.NavigationKey -import nav.enro.core.context.NavigationContext -import nav.enro.core.context.activity +import nav.enro.core.NavigationContext +import nav.enro.core.activity import nav.enro.core.getNavigationHandle -import nav.enro.core.navigator.SyntheticDestination +import nav.enro.core.synthetic.SyntheticDestination @Parcelize data class SimpleMessage( diff --git a/modularised-example/app/src/main/java/nav/enro/example/ExampleApplication.kt b/modularised-example/app/src/main/java/nav/enro/example/ExampleApplication.kt index b08fc5ff..6651a29b 100644 --- a/modularised-example/app/src/main/java/nav/enro/example/ExampleApplication.kt +++ b/modularised-example/app/src/main/java/nav/enro/example/ExampleApplication.kt @@ -3,20 +3,14 @@ package nav.enro.example import android.app.Application import dagger.hilt.android.HiltAndroidApp import nav.enro.annotations.NavigationComponent -import nav.enro.core.context.activity -import nav.enro.core.controller.NavigationApplication -import nav.enro.core.controller.navigationController -import nav.enro.core.executors.DefaultActivityExecutor +import nav.enro.core.NavigationApplication +import nav.enro.core.navigationController +import nav.enro.core.activity.DefaultActivityExecutor import nav.enro.core.plugins.EnroHilt import nav.enro.core.plugins.EnroLogger import nav.enro.example.core.data.UserRepository -import nav.enro.example.core.navigation.MultiStackKey -import nav.enro.example.core.navigation.UserKey import nav.enro.example.dashboard.DashboardActivity import nav.enro.example.login.LoginActivity -import nav.enro.example.login.LoginErrorDestination -import nav.enro.example.multistack.MultiStackActivity -import nav.enro.example.user.UserFragment import nav.enro.result.EnroResult @NavigationComponent diff --git a/modularised-example/app/src/main/java/nav/enro/example/MainActivity.kt b/modularised-example/app/src/main/java/nav/enro/example/MainActivity.kt index a2913254..39bb53e4 100644 --- a/modularised-example/app/src/main/java/nav/enro/example/MainActivity.kt +++ b/modularised-example/app/src/main/java/nav/enro/example/MainActivity.kt @@ -11,9 +11,9 @@ import dagger.hilt.android.AndroidEntryPoint import kotlinx.android.parcel.Parcelize import nav.enro.annotations.NavigationDestination import nav.enro.core.* -import nav.enro.core.context.NavigationContext -import nav.enro.core.context.activity -import nav.enro.core.navigator.SyntheticDestination +import nav.enro.core.NavigationContext +import nav.enro.core.activity +import nav.enro.core.synthetic.SyntheticDestination import nav.enro.example.core.data.UserRepository import nav.enro.example.core.navigation.DashboardKey import nav.enro.example.core.navigation.LaunchKey diff --git a/modularised-example/feature/login/src/main/java/nav/enro/example/login/LoginError.kt b/modularised-example/feature/login/src/main/java/nav/enro/example/login/LoginError.kt index 03bad0a1..07689102 100644 --- a/modularised-example/feature/login/src/main/java/nav/enro/example/login/LoginError.kt +++ b/modularised-example/feature/login/src/main/java/nav/enro/example/login/LoginError.kt @@ -6,10 +6,10 @@ import android.os.Bundle import androidx.fragment.app.DialogFragment import nav.enro.annotations.NavigationDestination import nav.enro.core.NavigationInstruction -import nav.enro.core.context.NavigationContext -import nav.enro.core.context.activity +import nav.enro.core.NavigationContext +import nav.enro.core.activity import nav.enro.core.navigationHandle -import nav.enro.core.navigator.SyntheticDestination +import nav.enro.core.synthetic.SyntheticDestination import nav.enro.example.core.navigation.LoginErrorKey class LoginErrorFragment : DialogFragment() { From 153e21e26add5ef5e6c664443df49ff152dfbf81 Mon Sep 17 00:00:00 2001 From: Isaac Date: Sat, 26 Dec 2020 16:04:46 +1300 Subject: [PATCH 08/24] Update NavigationDestinationProcessor to allow for the example project to compile and launch with new packages --- .../enro/core/synthetic/SyntheticNavigator.kt | 4 +- .../java/nav/enro/processor/Extensions.kt | 8 +++- .../NavigationDestinationProcessor.kt | 40 +++++++------------ .../nav/enro/example/ExampleApplication.kt | 5 ++- .../java/nav/enro/example/SimpleMessage.kt | 1 + 5 files changed, 26 insertions(+), 32 deletions(-) diff --git a/enro-core/src/main/java/nav/enro/core/synthetic/SyntheticNavigator.kt b/enro-core/src/main/java/nav/enro/core/synthetic/SyntheticNavigator.kt index 41a97e6f..af6042de 100644 --- a/enro-core/src/main/java/nav/enro/core/synthetic/SyntheticNavigator.kt +++ b/enro-core/src/main/java/nav/enro/core/synthetic/SyntheticNavigator.kt @@ -15,11 +15,11 @@ class SyntheticNavigator @PublishedApi internal constru } fun createSyntheticNavigator( - navigationKeyType: KClass, + navigationKeyType: Class, destination: SyntheticDestination ): Navigator> = SyntheticNavigator( - keyType = navigationKeyType, + keyType = navigationKeyType.kotlin, destination = destination ) diff --git a/enro-processor/src/main/java/nav/enro/processor/Extensions.kt b/enro-processor/src/main/java/nav/enro/processor/Extensions.kt index ddd54957..a1c9e950 100644 --- a/enro-processor/src/main/java/nav/enro/processor/Extensions.kt +++ b/enro-processor/src/main/java/nav/enro/processor/Extensions.kt @@ -16,13 +16,17 @@ internal object ClassNames { val navigationComponentBuilderCommand = ClassName.get("nav.enro.core.controller", "NavigationComponentBuilderCommand") val navigationComponentBuilder = ClassName.get("nav.enro.core.controller", "NavigationComponentBuilder") - val navigatorDefinitionKt = ClassName.get("nav.enro.core.navigator", "NavigatorDefinitionKt") val jvmClassMappings = ClassName.get("kotlin.jvm", "JvmClassMappingKt") val unit = ClassName.get("kotlin", "Unit") val fragmentActivity = ClassName.get( "androidx.fragment.app", "FragmentActivity") + val activityNavigatorKt = ClassName.get("nav.enro.core.activity","ActivityNavigatorKt") + val fragment = ClassName.get("androidx.fragment.app","Fragment") - val syntheticNavigator = ClassName.get("nav.enro.core.navigator","SyntheticDestination") + val fragmentNavigatorKt = ClassName.get("nav.enro.core.fragment","FragmentNavigatorKt") + + val syntheticDestination = ClassName.get("nav.enro.core.synthetic","SyntheticDestination") + val syntheticNavigatorKt = ClassName.get("nav.enro.core.synthetic","SyntheticNavigatorKt") } internal fun getNameFromKClass(block: () -> KClass<*>) : String { diff --git a/enro-processor/src/main/java/nav/enro/processor/NavigationDestinationProcessor.kt b/enro-processor/src/main/java/nav/enro/processor/NavigationDestinationProcessor.kt index d9da6671..b49fcdfb 100644 --- a/enro-processor/src/main/java/nav/enro/processor/NavigationDestinationProcessor.kt +++ b/enro-processor/src/main/java/nav/enro/processor/NavigationDestinationProcessor.kt @@ -7,7 +7,6 @@ import nav.enro.annotations.GeneratedNavigationBinding import net.ltgt.gradle.incap.IncrementalAnnotationProcessor import net.ltgt.gradle.incap.IncrementalAnnotationProcessorType import java.lang.IllegalStateException -import javax.annotation.Generated import javax.annotation.processing.Processor import javax.annotation.processing.RoundEnvironment import javax.lang.model.SourceVersion @@ -86,12 +85,9 @@ class NavigationDestinationProcessor : BaseProcessor() { JavaFile .builder(EnroProcessor.GENERATED_PACKAGE, classBuilder) - .addStaticImport( - ClassNames.navigatorDefinitionKt, - "createActivityNavigator", - "createFragmentNavigator", - "createSyntheticNavigator" - ) + .addStaticImport(ClassNames.activityNavigatorKt, "createActivityNavigator") + .addStaticImport(ClassNames.fragmentNavigatorKt, "createFragmentNavigator") + .addStaticImport(ClassNames.syntheticNavigatorKt, "createSyntheticNavigator") .addStaticImport(ClassNames.jvmClassMappings, "getKotlinClass") .build() .writeTo(processingEnv.filer) @@ -105,7 +101,7 @@ class NavigationDestinationProcessor : BaseProcessor() { val destinationIsActivity = destination.extends(ClassNames.fragmentActivity) val destinationIsFragment = destination.extends(ClassNames.fragment) - val destinationIsSynthetic = destination.implements(ClassNames.syntheticNavigator) + val destinationIsSynthetic = destination.implements(ClassNames.syntheticDestination) val annotation = destination.getAnnotation(NavigationDestination::class.java) @@ -113,43 +109,35 @@ class NavigationDestinationProcessor : BaseProcessor() { when { destinationIsActivity -> CodeBlock.of( """ - builder.add( + builder.navigator( createActivityNavigator( - getKotlinClass($1T.class), - getKotlinClass($2T.class), - it -> { - return $3T.INSTANCE; - } + $1T.class, + $2T.class ) ) """.trimIndent(), key, - destination, - ClassNames.unit + destination ) destinationIsFragment -> CodeBlock.of( """ - builder.add( + builder.navigator( createFragmentNavigator( - getKotlinClass($1T.class), - getKotlinClass($2T.class), - it -> { - return $3T.INSTANCE; - } + $1T.class, + $2T.class ) ) """.trimIndent(), key, - destination, - ClassNames.unit + destination ) destinationIsSynthetic -> CodeBlock.of( """ - builder.add( + builder.navigator( createSyntheticNavigator( - getKotlinClass($1T.class), + $1T.class, new $2T() ) ) diff --git a/example/src/main/java/nav/enro/example/ExampleApplication.kt b/example/src/main/java/nav/enro/example/ExampleApplication.kt index 0fe0b080..a513058c 100644 --- a/example/src/main/java/nav/enro/example/ExampleApplication.kt +++ b/example/src/main/java/nav/enro/example/ExampleApplication.kt @@ -3,6 +3,7 @@ package nav.enro.example import android.app.Application import nav.enro.annotations.NavigationComponent import nav.enro.core.NavigationApplication +import nav.enro.core.controller.navigationController import nav.enro.core.navigationController import nav.enro.core.plugins.EnroLogger import nav.enro.result.EnroResult @@ -10,7 +11,7 @@ import nav.enro.result.EnroResult @NavigationComponent class ExampleApplication : Application(), NavigationApplication { override val navigationController = navigationController { - withPlugin(EnroResult()) - withPlugin(EnroLogger()) + plugin(EnroResult()) + plugin(EnroLogger()) } } \ No newline at end of file diff --git a/example/src/main/java/nav/enro/example/SimpleMessage.kt b/example/src/main/java/nav/enro/example/SimpleMessage.kt index 417a4724..646630c0 100644 --- a/example/src/main/java/nav/enro/example/SimpleMessage.kt +++ b/example/src/main/java/nav/enro/example/SimpleMessage.kt @@ -21,6 +21,7 @@ data class SimpleMessage( class SimpleMessageDestination : SyntheticDestination { override fun process( navigationContext: NavigationContext, + key: SimpleMessage, instruction: NavigationInstruction.Open ) { val key = instruction.navigationKey as SimpleMessage From eba16b01bc73e0790548204060e2afdd08e834a0 Mon Sep 17 00:00:00 2001 From: Isaac Date: Sat, 26 Dec 2020 16:13:57 +1300 Subject: [PATCH 09/24] Fix a few bugs with incorrect fragment/handle states --- .../java/nav/enro/core/fragment/internal/FragmentHost.kt | 2 +- .../enro/core/internal/handle/NavigationHandleViewModel.kt | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/enro-core/src/main/java/nav/enro/core/fragment/internal/FragmentHost.kt b/enro-core/src/main/java/nav/enro/core/fragment/internal/FragmentHost.kt index 7cd01d49..d6c64ea3 100644 --- a/enro-core/src/main/java/nav/enro/core/fragment/internal/FragmentHost.kt +++ b/enro-core/src/main/java/nav/enro/core/fragment/internal/FragmentHost.kt @@ -17,7 +17,7 @@ internal class FragmentHost( internal fun NavigationContext<*>.fragmentHostFor(key: NavigationKey): FragmentHost? { val primaryFragment = childFragmentManager.primaryNavigationFragment - val activeContainerId = (primaryFragment?.view?.parent as View).id + val activeContainerId = (primaryFragment?.view?.parent as? View)?.id val visibleContainers = getNavigationHandleViewModel().childContainers.filter { when (contextReference) { diff --git a/enro-core/src/main/java/nav/enro/core/internal/handle/NavigationHandleViewModel.kt b/enro-core/src/main/java/nav/enro/core/internal/handle/NavigationHandleViewModel.kt index 5d8f9e80..17ab62cc 100644 --- a/enro-core/src/main/java/nav/enro/core/internal/handle/NavigationHandleViewModel.kt +++ b/enro-core/src/main/java/nav/enro/core/internal/handle/NavigationHandleViewModel.kt @@ -20,7 +20,9 @@ internal class NavigationHandleViewModel( internal val hasKey get() = instruction.navigationKey !is NoNavigationKey override val key: NavigationKey get() { - if(instruction.navigationKey is NoNavigationKey) throw IllegalStateException("This NavigationHandle has no NavigationKey") + if(instruction.navigationKey is NoNavigationKey) throw IllegalStateException( + "The navigation handle for the context ${navigationContext?.contextReference} has no NavigationKey" + ) return instruction.navigationKey } override val id: String get() = instruction.instructionId @@ -32,6 +34,7 @@ internal class NavigationHandleViewModel( private val lifecycle = LifecycleRegistry(this).apply { addObserver(object : LifecycleEventObserver { override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) { + if(!hasKey) return if (event == Lifecycle.Event.ON_CREATE) controller.onOpened(this@NavigationHandleViewModel) if (event == Lifecycle.Event.ON_DESTROY) controller.onClosed(this@NavigationHandleViewModel) } From 01d945afdbd258cccc272d643c5f6e439b7ec20e Mon Sep 17 00:00:00 2001 From: Isaac Date: Sat, 26 Dec 2020 16:18:31 +1300 Subject: [PATCH 10/24] Update test to ignore illegal state exception's message --- enro/src/androidTest/java/nav/enro/core/UnboundActivitiesTest.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/enro/src/androidTest/java/nav/enro/core/UnboundActivitiesTest.kt b/enro/src/androidTest/java/nav/enro/core/UnboundActivitiesTest.kt index 3952e290..4e10b8a7 100644 --- a/enro/src/androidTest/java/nav/enro/core/UnboundActivitiesTest.kt +++ b/enro/src/androidTest/java/nav/enro/core/UnboundActivitiesTest.kt @@ -25,7 +25,6 @@ class UnboundActivitiesTest { caught = t } assertTrue(caught is IllegalStateException) - assertEquals("This NavigationHandle has no NavigationKey", caught.message) } @Test From 0ea218922b68857cf1c9a1dc15298ca87b96dbfb Mon Sep 17 00:00:00 2001 From: Isaac Date: Sat, 26 Dec 2020 16:52:21 +1300 Subject: [PATCH 11/24] Move the override functionality to be based on a builder class rather than a single function, added (as yet unused) pre and post open/close override functions --- .../src/main/java/nav/enro/core/Extensions.kt | 1 - .../java/nav/enro/core/NavigationExecutor.kt | 121 +++++++++++++++++- .../java/nav/enro/core/NavigationOverride.kt | 24 +--- .../masterdetail/MasterDetailComponent.kt | 45 ++++--- .../ActivityToActivityOverrideTests.kt | 38 +++--- .../ActivityToFragmentOverrideTests.kt | 40 +++--- .../FragmentToActivityOverrideTests.kt | 24 ++-- .../FragmentToFragmentOverrideTests.kt | 36 +++--- 8 files changed, 218 insertions(+), 111 deletions(-) diff --git a/enro-core/src/main/java/nav/enro/core/Extensions.kt b/enro-core/src/main/java/nav/enro/core/Extensions.kt index 6f93bd2b..bb01a6f6 100644 --- a/enro-core/src/main/java/nav/enro/core/Extensions.kt +++ b/enro-core/src/main/java/nav/enro/core/Extensions.kt @@ -3,7 +3,6 @@ package nav.enro.core import android.content.res.Resources import android.util.TypedValue - internal fun Resources.Theme.getAttributeResourceId(attr: Int) = TypedValue().let { resolveAttribute(attr, it, true) it.resourceId diff --git a/enro-core/src/main/java/nav/enro/core/NavigationExecutor.kt b/enro-core/src/main/java/nav/enro/core/NavigationExecutor.kt index 8d2eea98..fff9628e 100644 --- a/enro-core/src/main/java/nav/enro/core/NavigationExecutor.kt +++ b/enro-core/src/main/java/nav/enro/core/NavigationExecutor.kt @@ -1,5 +1,9 @@ package nav.enro.core +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentActivity +import nav.enro.core.activity.DefaultActivityExecutor +import nav.enro.core.fragment.DefaultFragmentExecutor import kotlin.reflect.KClass // This class is used primarily to simplify the lambda signature of NavigationExecutor.open @@ -10,18 +14,133 @@ class ExecutorArgs( val instruction: NavigationInstruction.Open ) -// TODO add pre/post open for more configuration abstract class NavigationExecutor( val fromType: KClass, val opensType: KClass, val keyType: KClass ) { + open fun preOpened( + context: NavigationContext + ) {} + abstract fun open( args: ExecutorArgs ) + open fun postOpened( + context: NavigationContext + ) {} + + open fun preClosed( + context: NavigationContext + ) {} + abstract fun close( context: NavigationContext ) + + open fun postClosed( + context: NavigationContext + ) {} } +class NavigationExecutorBuilder @PublishedApi internal constructor( + private val fromType: KClass, + private val opensType: KClass, + private val keyType: KClass +) { + + private var preOpenedFunc: (( context: NavigationContext) -> Unit)? = null + private var openedFunc: ((args: ExecutorArgs) -> Unit)? = null + private var postOpenedFunc: ((context: NavigationContext) -> Unit)? = null + private var preClosedFunc: ((context: NavigationContext) -> Unit)? = null + private var closedFunc: ((context: NavigationContext) -> Unit)? = null + private var postClosedFunc: ((context: NavigationContext) -> Unit)? = null + + @Suppress("UNCHECKED_CAST") + private val defaultOpens: (args: ExecutorArgs) -> Unit by lazy { + when { + FragmentActivity::class.java.isAssignableFrom(opensType.java) -> + DefaultActivityExecutor::open as ((ExecutorArgs) -> Unit) + + Fragment::class.java.isAssignableFrom(opensType.java) -> + DefaultFragmentExecutor::open as ((ExecutorArgs) -> Unit) + + else -> throw IllegalArgumentException("No default launch executor found for ${opensType.java}") + } + } + + @Suppress("UNCHECKED_CAST") + private val defaultCloses: (context: NavigationContext) -> Unit by lazy { + when { + FragmentActivity::class.java.isAssignableFrom(opensType.java) -> + DefaultActivityExecutor::close as (NavigationContext) -> Unit + + Fragment::class.java.isAssignableFrom(opensType.java) -> + DefaultFragmentExecutor::close as (NavigationContext) -> Unit + + else -> throw IllegalArgumentException("No default close executor found for ${opensType.java}") + } + } + + fun preOpened(block: ( context: NavigationContext) -> Unit) { + if(preOpenedFunc != null) throw IllegalStateException("Value is already set!") + preOpenedFunc = block + } + + fun opened(block: (args: ExecutorArgs) -> Unit) { + if(openedFunc != null) throw IllegalStateException("Value is already set!") + openedFunc = block + } + + fun postOpened(block: (context: NavigationContext) -> Unit) { + if(postOpenedFunc != null) throw IllegalStateException("Value is already set!") + postOpenedFunc = block + } + + fun preClosed(block: (context: NavigationContext) -> Unit) { + if(preClosedFunc != null) throw IllegalStateException("Value is already set!") + preClosedFunc = block + } + + fun closed(block: (context: NavigationContext) -> Unit) { + if(closedFunc != null) throw IllegalStateException("Value is already set!") + closedFunc = block + } + + fun postClosed(block: (context: NavigationContext) -> Unit) { + if(postClosedFunc != null) throw IllegalStateException("Value is already set!") + postClosedFunc = block + } + + + internal fun build() = object : NavigationExecutor( + fromType, + opensType, + keyType + ) { + override fun preOpened(context: NavigationContext) { + preOpenedFunc?.invoke(context) + } + + override fun open(args: ExecutorArgs) { + (openedFunc ?: defaultOpens).invoke(args) + } + + override fun postOpened(context: NavigationContext) { + postOpenedFunc?.invoke(context) + } + + override fun preClosed(context: NavigationContext) { + preClosedFunc?.invoke(context) + } + + override fun close(context: NavigationContext) { + (closedFunc ?: defaultCloses).invoke(context) + } + + override fun postClosed(context: NavigationContext) { + postClosedFunc?.invoke(context) + } + } +} \ No newline at end of file diff --git a/enro-core/src/main/java/nav/enro/core/NavigationOverride.kt b/enro-core/src/main/java/nav/enro/core/NavigationOverride.kt index 1ff45a4b..75dc42c4 100644 --- a/enro-core/src/main/java/nav/enro/core/NavigationOverride.kt +++ b/enro-core/src/main/java/nav/enro/core/NavigationOverride.kt @@ -9,28 +9,16 @@ import kotlin.reflect.KClass fun createOverride( fromClass: KClass, opensClass: KClass, - open: ((ExecutorArgs) -> Unit), - close: ((context: NavigationContext) -> Unit) + block: NavigationExecutorBuilder.() -> Unit ): NavigationExecutor = - object : NavigationExecutor( - fromType = fromClass, - opensType = opensClass, - keyType = NavigationKey::class - ) { - override fun open(args: ExecutorArgs) { - open(args) - } - - override fun close(context: NavigationContext) { - close(context) - } - } + NavigationExecutorBuilder(fromClass, opensClass, NavigationKey::class) + .apply(block) + .build() inline fun createOverride( - noinline open: ((ExecutorArgs) -> Unit) = defaultOpen(), - noinline close: (NavigationContext) -> Unit = defaultClose() + noinline block: NavigationExecutorBuilder.() -> Unit ): NavigationExecutor = - createOverride(From::class, Opens::class, open, close) + createOverride(From::class, Opens::class, block) @Suppress("UNCHECKED_CAST") inline fun defaultOpen(): ((ExecutorArgs) -> Unit) { diff --git a/enro-masterdetail/src/main/java/nav/enro/masterdetail/MasterDetailComponent.kt b/enro-masterdetail/src/main/java/nav/enro/masterdetail/MasterDetailComponent.kt index d45bfe5f..8caec902 100644 --- a/enro-masterdetail/src/main/java/nav/enro/masterdetail/MasterDetailComponent.kt +++ b/enro-masterdetail/src/main/java/nav/enro/masterdetail/MasterDetailComponent.kt @@ -39,10 +39,8 @@ class MasterDetailProperty( private val masterOverride by lazy { val masterType = navigationController.navigatorForKeyType(masterKey)!!.contextType as KClass - createOverride( - owningType, - masterType, - open = { + createOverride(owningType, masterType) { + opened { val fragment = it.fromContext.childFragmentManager.fragmentFactory.instantiate( masterType.java.classLoader!!, masterType.java.name @@ -52,27 +50,29 @@ class MasterDetailProperty( .replace(masterContainer, fragment) .setPrimaryNavigationFragment(fragment) .commitNow() - }, - close = { + } + + closed { it.activity.finish() } - ) + } } private val detailOverride by lazy { val detailType = navigationController.navigatorForKeyType(detailKey)!!.contextType as KClass - createOverride( - owningType, - detailType, - open = { - if(!Fragment::class.java.isAssignableFrom(it.navigator.contextType.java)) { - Log.e("Enro", "Attempted to open ${detailKey::class.java} as a Detail in ${it.fromContext.contextReference}, " + - "but ${detailKey::class.java}'s NavigationDestination is not a Fragment! Defaulting to standard navigation") + createOverride(owningType, detailType) { + opened { + if (!Fragment::class.java.isAssignableFrom(it.navigator.contextType.java)) { + Log.e( + "Enro", + "Attempted to open ${detailKey::class.java} as a Detail in ${it.fromContext.contextReference}, " + + "but ${detailKey::class.java}'s NavigationDestination is not a Fragment! Defaulting to standard navigation" + ) DefaultActivityExecutor.open(it as ExecutorArgs) - return@createOverride + return@opened } - val fragment = it.fromContext.childFragmentManager.fragmentFactory.instantiate( + val fragment = it.fromContext.childFragmentManager.fragmentFactory.instantiate( detailType.java.classLoader!!, detailType.java.name ).addOpenInstruction(it.instruction) @@ -81,14 +81,19 @@ class MasterDetailProperty( .replace(detailContainer, fragment) .setPrimaryNavigationFragment(fragment) .commitNow() - }, - close = { context -> + } + + closed { context -> context.fragment.parentFragmentManager.beginTransaction() .remove(context.fragment) - .setPrimaryNavigationFragment(context.activity.supportFragmentManager.findFragmentById(masterContainer)) + .setPrimaryNavigationFragment( + context.activity.supportFragmentManager.findFragmentById( + masterContainer + ) + ) .commitNow() } - ) + } } init { diff --git a/enro/src/androidTest/java/nav/enro/core/overrides/ActivityToActivityOverrideTests.kt b/enro/src/androidTest/java/nav/enro/core/overrides/ActivityToActivityOverrideTests.kt index aade965e..9095533e 100644 --- a/enro/src/androidTest/java/nav/enro/core/overrides/ActivityToActivityOverrideTests.kt +++ b/enro/src/androidTest/java/nav/enro/core/overrides/ActivityToActivityOverrideTests.kt @@ -17,12 +17,12 @@ class ActivityToActivityOverrideTests() { fun givenActivityToActivityOverride_whenInitialActivityOpenedWithDefaultKey_whenActivityIsLaunched_thenOverrideIsCalled() { var launchOverrideCalled = false application.navigationController.addOverride( - createOverride( - open = { + createOverride { + opened { launchOverrideCalled = true defaultOpen().invoke(it) } - ) + } ) ActivityScenario.launch(DefaultActivity::class.java) .getNavigationHandle() @@ -36,13 +36,13 @@ class ActivityToActivityOverrideTests() { @Test fun givenActivityToActivityOverride_whenInitialActivityOpenedWithDefaultKey_whenActivityIsClosed_thenOverrideIsCalled() { var closeOverrideCalled = false - application.navigationController.addOverride( - createOverride( - close = { + application.navigationController.addOverride ( + createOverride { + closed { closeOverrideCalled = true defaultClose().invoke(it) } - ) + } ) ActivityScenario.launch(DefaultActivity::class.java) @@ -62,12 +62,12 @@ class ActivityToActivityOverrideTests() { fun givenActivityToActivityOverride_whenActivityIsLaunched_thenOverrideIsCalled() { var launchOverrideCalled = false application.navigationController.addOverride( - createOverride( - open = { + createOverride{ + opened { launchOverrideCalled = true defaultOpen().invoke(it) } - ) + } ) val intent = Intent(application, GenericActivity::class.java) .addOpenInstruction( @@ -90,12 +90,12 @@ class ActivityToActivityOverrideTests() { fun givenActivityToActivityOverride_whenActivityIsClosed_thenOverrideIsCalled() { var closeOverrideCalled = false application.navigationController.addOverride( - createOverride( - close = { + createOverride { + closed { closeOverrideCalled = true defaultClose().invoke(it) } - ) + } ) val intent = Intent(application, GenericActivity::class.java) @@ -124,12 +124,12 @@ class ActivityToActivityOverrideTests() { fun givenUnboundActivityToActivityOverride_whenActivityIsLaunched_thenOverrideIsCalled() { var launchOverrideCalled = false application.navigationController.addOverride( - createOverride( - open = { + createOverride{ + opened { launchOverrideCalled = true defaultOpen().invoke(it) } - ) + } ) ActivityScenario.launch(UnboundActivity::class.java) @@ -145,12 +145,12 @@ class ActivityToActivityOverrideTests() { fun givenUnboundActivityToActivityOverride_whenActivityIsClosed_thenOverrideIsCalled() { var closeOverrideCalled = false application.navigationController.addOverride( - createOverride( - close = { + createOverride { + closed { closeOverrideCalled = true defaultClose().invoke(it) } - ) + } ) ActivityScenario.launch(UnboundActivity::class.java) diff --git a/enro/src/androidTest/java/nav/enro/core/overrides/ActivityToFragmentOverrideTests.kt b/enro/src/androidTest/java/nav/enro/core/overrides/ActivityToFragmentOverrideTests.kt index a4fe7a21..dad7245d 100644 --- a/enro/src/androidTest/java/nav/enro/core/overrides/ActivityToFragmentOverrideTests.kt +++ b/enro/src/androidTest/java/nav/enro/core/overrides/ActivityToFragmentOverrideTests.kt @@ -5,10 +5,6 @@ import androidx.test.core.app.ActivityScenario import junit.framework.Assert.assertTrue import nav.enro.* import nav.enro.core.* -import nav.enro.core.navigationController -import nav.enro.core.createOverride -import nav.enro.core.defaultClose -import nav.enro.core.defaultOpen import org.junit.Test class ActivityToFragmentOverrideTests() { @@ -17,12 +13,12 @@ class ActivityToFragmentOverrideTests() { fun givenActivityToFragmentOverride_andActivityDoesNotSupportFragment_whenInitialActivityOpenedWithDefaultKey_whenFragmentIsLaunched_whenActivityDoes_thenOverrideIsCalled() { var launchOverrideCalled = false application.navigationController.addOverride( - createOverride( - open = { + createOverride { + opened { launchOverrideCalled = true defaultOpen().invoke(it) } - ) + } ) ActivityScenario.launch(DefaultActivity::class.java) .getNavigationHandle() @@ -37,12 +33,12 @@ class ActivityToFragmentOverrideTests() { fun givenActivityToFragmentOverride_andActivityDoesNotSupportFragment_whenInitialActivityOpenedWithDefaultKey_whenFragmentIsClosed_thenOverrideIsCalled() { var closeOverrideCalled = false application.navigationController.addOverride( - createOverride( - close = { + createOverride { + closed { closeOverrideCalled = true defaultClose().invoke(it) } - ) + } ) ActivityScenario.launch(DefaultActivity::class.java) @@ -61,12 +57,12 @@ class ActivityToFragmentOverrideTests() { fun givenActivityToFragmentOverride_andActivityDoesNotSupportFragment_whenFragmentIsLaunched_thenOverrideIsCalled() { var launchOverrideCalled = false application.navigationController.addOverride( - createOverride( - open = { + createOverride { + opened { launchOverrideCalled = true defaultOpen().invoke(it) } - ) + } ) val intent = Intent(application, GenericActivity::class.java) .addOpenInstruction( @@ -89,12 +85,12 @@ class ActivityToFragmentOverrideTests() { fun givenActivityToFragmentOverride_andActivityDoesNotSupportFragment_whenFragmentIsClosed_thenOverrideIsCalled() { var closeOverrideCalled = false application.navigationController.addOverride( - createOverride( - close = { + createOverride { + closed { closeOverrideCalled = true defaultClose().invoke(it) } - ) + } ) val intent = Intent(application, GenericActivity::class.java) @@ -122,12 +118,12 @@ class ActivityToFragmentOverrideTests() { fun givenActivityToFragmentOverride_whenFragmentIsLaunched_thenOverrideIsCalled() { var launchOverrideCalled = false application.navigationController.addOverride( - createOverride( - open = { + createOverride { + opened { launchOverrideCalled = true defaultOpen().invoke(it) } - ) + } ) val intent = Intent(application, ActivityWithFragments::class.java) .addOpenInstruction( @@ -150,12 +146,12 @@ class ActivityToFragmentOverrideTests() { fun givenActivityToFragmentOverride_whenFragmentIsClosed_thenOverrideIsCalled() { var closeOverrideCalled = false application.navigationController.addOverride( - createOverride( - close = { + createOverride { + closed { closeOverrideCalled = true defaultClose().invoke(it) } - ) + } ) val intent = Intent(application, ActivityWithFragments::class.java) .addOpenInstruction( diff --git a/enro/src/androidTest/java/nav/enro/core/overrides/FragmentToActivityOverrideTests.kt b/enro/src/androidTest/java/nav/enro/core/overrides/FragmentToActivityOverrideTests.kt index 5734c4ae..82221d8b 100644 --- a/enro/src/androidTest/java/nav/enro/core/overrides/FragmentToActivityOverrideTests.kt +++ b/enro/src/androidTest/java/nav/enro/core/overrides/FragmentToActivityOverrideTests.kt @@ -32,12 +32,12 @@ class FragmentToActivityOverrideTests() { fun givenFragmentToActivityOverride_whenFragmentIsStandalone_whenActivityIsLaunchedFrom_thenOverrideIsCalled() { var launchOverrideCalled = false application.navigationController.addOverride( - createOverride( - open = { + createOverride{ + opened { launchOverrideCalled = true defaultOpen().invoke(it) } - ) + } ) initialScenario.getNavigationHandle() @@ -56,12 +56,12 @@ class FragmentToActivityOverrideTests() { fun givenFragmentToActivityOverride_whenFragmentIsStandalone_whenActivityIsClosed_thenOverrideIsCalled() { var closeOverrideCalled = false application.navigationController.addOverride( - createOverride( - open = { + createOverride { + opened { closeOverrideCalled = true defaultOpen().invoke(it) } - ) + } ) initialScenario.getNavigationHandle() @@ -85,12 +85,12 @@ class FragmentToActivityOverrideTests() { fun givenFragmentToActivityOverride_whenFragmentIsNested_whenActivityIsLaunchedFrom_thenOverrideIsCalled() { var launchOverrideCalled = false application.navigationController.addOverride( - createOverride( - open = { + createOverride { + opened { launchOverrideCalled = true defaultOpen().invoke(it) } - ) + } ) initialScenario.getNavigationHandle() @@ -109,12 +109,12 @@ class FragmentToActivityOverrideTests() { fun givenFragmentToActivityOverride_whenFragmentIsNested_whenActivityIsClosed_thenOverrideIsCalled() { var closeOverrideCalled = false application.navigationController.addOverride( - createOverride( - open = { + createOverride { + opened { closeOverrideCalled = true defaultOpen().invoke(it) } - ) + } ) initialScenario.getNavigationHandle() diff --git a/enro/src/androidTest/java/nav/enro/core/overrides/FragmentToFragmentOverrideTests.kt b/enro/src/androidTest/java/nav/enro/core/overrides/FragmentToFragmentOverrideTests.kt index 02be6b48..df1a12e2 100644 --- a/enro/src/androidTest/java/nav/enro/core/overrides/FragmentToFragmentOverrideTests.kt +++ b/enro/src/androidTest/java/nav/enro/core/overrides/FragmentToFragmentOverrideTests.kt @@ -32,12 +32,12 @@ class FragmentToFragmentOverrideTests() { fun givenFragmentToFragmentOverride_whenFragmentIsStandalone_whenFragmentIsLaunched_thenOverrideIsCalled() { var launchOverrideCalled = false application.navigationController.addOverride( - createOverride( - open = { + createOverride { + opened { launchOverrideCalled = true defaultOpen().invoke(it) } - ) + } ) initialScenario.getNavigationHandle() @@ -56,12 +56,12 @@ class FragmentToFragmentOverrideTests() { fun givenFragmentToFragmentOverride_whenFragmentIsStandalone_whenFragmentIsClosed_thenOverrideIsCalled() { var closeOverrideCalled = false application.navigationController.addOverride( - createOverride( - open = { + createOverride { + opened { closeOverrideCalled = true defaultOpen().invoke(it) } - ) + } ) initialScenario.getNavigationHandle() @@ -85,12 +85,12 @@ class FragmentToFragmentOverrideTests() { fun givenFragmentToFragmentOverride_whenFragmentIsNested_andTargetIsStandalone_whenFragmentIsLaunched_thenOverrideIsCalled() { var launchOverrideCalled = false application.navigationController.addOverride( - createOverride( - open = { + createOverride { + opened { launchOverrideCalled = true defaultOpen().invoke(it) } - ) + } ) initialScenario.getNavigationHandle() @@ -109,12 +109,12 @@ class FragmentToFragmentOverrideTests() { fun givenFragmentToFragmentOverride_whenFragmentIsNested_andTargetIsStandalone_whenFragmentIsClosed_thenOverrideIsCalled() { var closeOverrideCalled = false application.navigationController.addOverride( - createOverride( - open = { + createOverride { + opened { closeOverrideCalled = true defaultOpen().invoke(it) } - ) + } ) initialScenario.getNavigationHandle() @@ -138,12 +138,12 @@ class FragmentToFragmentOverrideTests() { fun givenFragmentToFragmentOverride_whenFragmentIsNested_andTargetIsNested_whenFragmentIsLaunched_thenOverrideIsCalled() { var launchOverrideCalled = false application.navigationController.addOverride( - createOverride( - open = { + createOverride { + opened { launchOverrideCalled = true defaultOpen().invoke(it) } - ) + } ) initialScenario.getNavigationHandle() @@ -162,12 +162,12 @@ class FragmentToFragmentOverrideTests() { fun givenFragmentToFragmentOverride_whenFragmentIsNested_andTargetIsNested_whenFragmentIsClosed_thenOverrideIsCalled() { var closeOverrideCalled = false application.navigationController.addOverride( - createOverride( - open = { + createOverride { + opened { closeOverrideCalled = true defaultOpen().invoke(it) } - ) + } ) initialScenario.getNavigationHandle() From da7f6d45c7dbb599cefd5746338fc8ce781d4c5c Mon Sep 17 00:00:00 2001 From: Isaac Date: Sat, 26 Dec 2020 20:03:19 +1300 Subject: [PATCH 12/24] Finish adding additional callbacks for overrides --- .../java/nav/enro/core/NavigationExecutor.kt | 23 +-- .../nav/enro/core/NavigationInstruction.kt | 1 + .../NavigationHandleActivityBinder.kt | 5 +- .../core/controller/NavigationController.kt | 183 +++++++++--------- .../NavigationHandleFragmentBinder.kt | 8 +- .../synthetic/DefaultSyntheticExecutor.kt | 28 +++ .../java/nav/enro/core/TestExtensions.kt | 6 +- .../ActivityToActivityOverrideTests.kt | 52 ++++- .../ActivityToFragmentOverrideTests.kt | 51 ++++- .../FragmentToActivityOverrideTests.kt | 42 +++- .../FragmentToFragmentOverrideTests.kt | 60 ++++-- 11 files changed, 302 insertions(+), 157 deletions(-) create mode 100644 enro-core/src/main/java/nav/enro/core/synthetic/DefaultSyntheticExecutor.kt diff --git a/enro-core/src/main/java/nav/enro/core/NavigationExecutor.kt b/enro-core/src/main/java/nav/enro/core/NavigationExecutor.kt index fff9628e..0b2f9b84 100644 --- a/enro-core/src/main/java/nav/enro/core/NavigationExecutor.kt +++ b/enro-core/src/main/java/nav/enro/core/NavigationExecutor.kt @@ -20,7 +20,7 @@ abstract class NavigationExecutor ) { open fun preOpened( - context: NavigationContext + context: NavigationContext ) {} abstract fun open( @@ -38,10 +38,6 @@ abstract class NavigationExecutor ) - - open fun postClosed( - context: NavigationContext - ) {} } class NavigationExecutorBuilder @PublishedApi internal constructor( @@ -50,12 +46,11 @@ class NavigationExecutorBuilder ) { - private var preOpenedFunc: (( context: NavigationContext) -> Unit)? = null + private var preOpenedFunc: (( context: NavigationContext) -> Unit)? = null private var openedFunc: ((args: ExecutorArgs) -> Unit)? = null private var postOpenedFunc: ((context: NavigationContext) -> Unit)? = null private var preClosedFunc: ((context: NavigationContext) -> Unit)? = null private var closedFunc: ((context: NavigationContext) -> Unit)? = null - private var postClosedFunc: ((context: NavigationContext) -> Unit)? = null @Suppress("UNCHECKED_CAST") private val defaultOpens: (args: ExecutorArgs) -> Unit by lazy { @@ -83,7 +78,7 @@ class NavigationExecutorBuilder) -> Unit) { + fun preOpened(block: ( context: NavigationContext) -> Unit) { if(preOpenedFunc != null) throw IllegalStateException("Value is already set!") preOpenedFunc = block } @@ -108,18 +103,12 @@ class NavigationExecutorBuilder) -> Unit) { - if(postClosedFunc != null) throw IllegalStateException("Value is already set!") - postClosedFunc = block - } - - internal fun build() = object : NavigationExecutor( fromType, opensType, keyType ) { - override fun preOpened(context: NavigationContext) { + override fun preOpened(context: NavigationContext) { preOpenedFunc?.invoke(context) } @@ -138,9 +127,5 @@ class NavigationExecutorBuilder) { (closedFunc ?: defaultCloses).invoke(context) } - - override fun postClosed(context: NavigationContext) { - postClosedFunc?.invoke(context) - } } } \ No newline at end of file diff --git a/enro-core/src/main/java/nav/enro/core/NavigationInstruction.kt b/enro-core/src/main/java/nav/enro/core/NavigationInstruction.kt index 72c3bba1..793e0693 100644 --- a/enro-core/src/main/java/nav/enro/core/NavigationInstruction.kt +++ b/enro-core/src/main/java/nav/enro/core/NavigationInstruction.kt @@ -26,6 +26,7 @@ sealed class NavigationInstruction { val navigationKey: NavigationKey, val children: List = emptyList(), val parentInstruction: Open? = null, + val parentContext: Class? = null, val animations: NavigationAnimations? = null, val additionalData: Bundle = Bundle(), val instructionId: String = UUID.randomUUID().toString() diff --git a/enro-core/src/main/java/nav/enro/core/activity/NavigationHandleActivityBinder.kt b/enro-core/src/main/java/nav/enro/core/activity/NavigationHandleActivityBinder.kt index c56c65cf..9f0b81bf 100644 --- a/enro-core/src/main/java/nav/enro/core/activity/NavigationHandleActivityBinder.kt +++ b/enro-core/src/main/java/nav/enro/core/activity/NavigationHandleActivityBinder.kt @@ -31,13 +31,16 @@ internal object NavigationHandleActivityBinder : Application.ActivityLifecycleCa navigationKey = config?.defaultKey ?: NoNavigationKey(activity::class.java, activity.intent.extras) ) + val controller = activity.application.navigationController val handle = activity.createNavigationHandleViewModel( - activity.application.navigationController, + controller, instruction ?: defaultInstruction ) config?.applyTo(handle) + val context = ActivityContext(activity) handle.navigationContext = ActivityContext(activity) + controller.onContextCreated(context, savedInstanceState) if(savedInstanceState == null) handle.executeDeeplink() activity.findViewById(android.R.id.content).viewTreeObserver.addOnGlobalLayoutListener { activity.application.navigationController.active = activity.navigationContext.leafContext().getNavigationHandleViewModel() diff --git a/enro-core/src/main/java/nav/enro/core/controller/NavigationController.kt b/enro-core/src/main/java/nav/enro/core/controller/NavigationController.kt index e91455e5..4abe80d3 100644 --- a/enro-core/src/main/java/nav/enro/core/controller/NavigationController.kt +++ b/enro-core/src/main/java/nav/enro/core/controller/NavigationController.kt @@ -1,6 +1,8 @@ package nav.enro.core.controller import android.app.Application +import android.os.Bundle +import android.util.Log import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentActivity import nav.enro.core.* @@ -20,7 +22,7 @@ import nav.enro.core.internal.handle.NavigationHandleViewModel import nav.enro.core.plugins.EnroHilt import nav.enro.core.plugins.EnroPlugin import nav.enro.core.synthetic.SyntheticDestination -import nav.enro.core.synthetic.SyntheticNavigator +import nav.enro.core.synthetic.DefaultSyntheticExecutor import kotlin.reflect.KClass // TODO split functionality out into more focused classes (e.g. OverrideController or similar) @@ -88,40 +90,30 @@ class NavigationController( val navigator = navigatorForKeyType(instruction.navigationKey::class) ?: throw IllegalStateException("Attempted to execute $instruction but could not find a valid navigator for the key type on this instruction") - if (openOverrideFor(navigationContext, navigator, instruction)) return - when (navigator) { - is ActivityNavigator -> DefaultActivityExecutor.open( - ExecutorArgs( - navigationContext, - navigator, - instruction.navigationKey, - instruction.setParentInstruction(navigationContext, navigator) - ) - ) - is FragmentNavigator -> DefaultFragmentExecutor.open( - ExecutorArgs( - navigationContext, - navigator, - instruction.navigationKey, - instruction.setParentInstruction(navigationContext, navigator) - ) - ) - is SyntheticNavigator -> (navigator.destination as SyntheticDestination) - .process(navigationContext, instruction.navigationKey, instruction) + Log.e("FINDING OVERRIDE FOR", "Exec open for ${instruction::class.java.simpleName} from ${navigationContext.contextReference}") - is NoKeyNavigator -> { throw IllegalArgumentException() } - } + val executor = executorForOpen(navigationContext, navigator.contextType) + + val args = ExecutorArgs( + executor.context, + navigator, + instruction.navigationKey, + instruction + .setParentInstruction(executor.context, navigator) + .setParentContext(executor.context) + ) + + Log.e("FINDING OVERRIDE FOR", "Exec open for ${instruction::class.java.simpleName} with real context ${executor.context.contextReference}") + executor.executor.preOpened(executor.context) + executor.executor.open(args) } internal fun close( navigationContext: NavigationContext ) { - if (!closeOverrideFor(navigationContext)) { - when (navigationContext) { - is ActivityContext -> DefaultActivityExecutor.close(navigationContext) - is FragmentContext -> DefaultFragmentExecutor.close(navigationContext) - } - } + val executor = executorForClose(navigationContext) + executor.preClosed(navigationContext) + executor.close(navigationContext) } internal fun onOpened(navigationHandle: NavigationHandle) { @@ -132,6 +124,14 @@ class NavigationController( plugins.forEach { it.onClosed(navigationHandle) } } + internal fun onContextCreated(navigationContext: NavigationContext, savedInstanceState: Bundle?) { + + Log.e("FINDING OVERRIDE FOR", "CONTEXT CREATED ${navigationContext.contextReference}\n\n-") + if(savedInstanceState == null) { + executorForClose(navigationContext).postOpened(navigationContext) + } + } + fun navigatorForContextType( contextType: KClass<*> ): Navigator<*, *>? { @@ -148,76 +148,63 @@ class NavigationController( return temporaryOverrides[types] ?: overrides[types] } - private fun openOverrideFor( - fromContext: NavigationContext, - navigator: Navigator, - instruction: NavigationInstruction.Open - ): Boolean { + private fun executorForOpen(fromContext: NavigationContext, opensContext: KClass): OpenExecutorPair { + val opensContextIsActivity by lazy { + FragmentActivity::class.java.isAssignableFrom(opensContext.java) + } - val override = overrideFor(fromContext.contextReference::class to navigator.contextType) - ?: when(fromContext.contextReference) { - is FragmentActivity -> overrideFor(FragmentActivity::class to navigator.contextType) - is Fragment -> overrideFor(Fragment::class to navigator.contextType) - else -> null - } - ?: overrideFor(Any::class to navigator.contextType) - ?: when(navigator) { - is ActivityNavigator<*, *> -> overrideFor(fromContext.contextReference::class to FragmentActivity::class) - is FragmentNavigator<*, *> -> overrideFor(fromContext.contextReference::class to Fragment::class) - else -> null - } - ?: overrideFor(fromContext.contextReference::class to Any::class) - - if (override != null) { - @Suppress("UNCHECKED_CAST") // higher level logic dictates that this cast should succeed - override as NavigationExecutor - override.open( - ExecutorArgs( - fromContext, - navigator, - instruction.navigationKey, - instruction.setParentInstruction(fromContext, navigator) - ) - ) - return true + val opensContextIsFragment by lazy { + Fragment::class.java.isAssignableFrom(opensContext.java) } - return when (fromContext.contextReference) { - is Fragment -> openOverrideFor( - fromContext.parentContext() ?: return false, - navigator, - instruction - ) - else -> false + val opensContextIsSynthetic by lazy { + SyntheticDestination::class.java.isAssignableFrom(opensContext.java) } - } - private fun closeOverrideFor(navigationContext: NavigationContext): Boolean { - val parentInstruction = navigationContext.getNavigationHandleViewModel().instruction.parentInstruction - val parentNavigator = parentInstruction - ?.let { - if(it.navigationKey is SingleFragmentKey) { - it.parentInstruction - } else it + fun getOverrideExecutor(overrideContext: NavigationContext): OpenExecutorPair? { + val override = overrideFor(overrideContext.contextReference::class to opensContext) + ?: when (overrideContext.contextReference) { + is FragmentActivity -> overrideFor(FragmentActivity::class to opensContext) + is Fragment -> overrideFor(Fragment::class to opensContext) + else -> null + } + ?: overrideFor(Any::class to opensContext) + ?: when { + opensContextIsActivity -> overrideFor(overrideContext.contextReference::class to FragmentActivity::class) + opensContextIsFragment -> overrideFor(overrideContext.contextReference::class to Fragment::class) + else -> null + } + ?: overrideFor(overrideContext.contextReference::class to Any::class) + + val parentContext = overrideContext.parentContext() + return when { + override != null -> OpenExecutorPair(overrideContext, override) + parentContext != null -> getOverrideExecutor(parentContext) + else -> null } - ?.let { - return@let navigatorForKeyType(it.navigationKey::class) + } + + val override = getOverrideExecutor(fromContext) + return override ?: when { + opensContextIsActivity -> OpenExecutorPair(fromContext, DefaultActivityExecutor) + opensContextIsFragment -> OpenExecutorPair(fromContext, DefaultFragmentExecutor) + opensContextIsSynthetic -> OpenExecutorPair(fromContext, DefaultSyntheticExecutor) + else -> throw IllegalStateException() } - ?: return false + } - val parentType = when(parentInstruction.navigationKey) { - is NoNavigationKey -> parentInstruction.navigationKey.contextType.kotlin - else -> parentNavigator.contextType + @Suppress("UNCHECKED_CAST") + private fun executorForClose(navigationContext: NavigationContext): NavigationExecutor { + val parentContextType = navigationContext.getNavigationHandleViewModel().instruction.parentContext?.kotlin + val override = parentContextType?.let { + overrideFor(it to navigationContext.contextReference::class) + as? NavigationExecutor } - @Suppress("UNCHECKED_CAST") - // higher level logic dictates that this cast should succeed - val override = overrideFor(parentType to navigationContext.contextReference::class) - as? NavigationExecutor - ?: return false - - override.close(navigationContext) - return true + return override ?: when (navigationContext) { + is ActivityContext -> DefaultActivityExecutor as NavigationExecutor + is FragmentContext -> DefaultFragmentExecutor as NavigationExecutor + } } private fun NavigationInstruction.Open.setParentInstruction( @@ -248,6 +235,15 @@ class NavigationController( return copy(parentInstruction = parentInstruction) } + private fun NavigationInstruction.Open.setParentContext( + parentContext: NavigationContext<*> + ): NavigationInstruction.Open { + if(parentContext.contextReference is SingleFragmentActivity) { + return copy(parentContext = parentContext.getNavigationHandleViewModel().instruction.parentContext) + } + return copy(parentContext = parentContext.contextReference::class.java) + } + fun addOverride(navigationExecutor: NavigationExecutor<*, *, *>) { temporaryOverrides[navigationExecutor.fromType to navigationExecutor.opensType] = navigationExecutor } @@ -270,4 +266,13 @@ class NavigationController( ) } } +} + +@Suppress("UNCHECKED_CAST") +class OpenExecutorPair( + context: NavigationContext, + executor: NavigationExecutor +) { + val context = context as NavigationContext + val executor = executor as NavigationExecutor } \ No newline at end of file diff --git a/enro-core/src/main/java/nav/enro/core/fragment/NavigationHandleFragmentBinder.kt b/enro-core/src/main/java/nav/enro/core/fragment/NavigationHandleFragmentBinder.kt index 187d6b66..c0a1bc05 100644 --- a/enro-core/src/main/java/nav/enro/core/fragment/NavigationHandleFragmentBinder.kt +++ b/enro-core/src/main/java/nav/enro/core/fragment/NavigationHandleFragmentBinder.kt @@ -44,14 +44,16 @@ internal object NavigationHandleFragmentBinder: Application.ActivityLifecycleCal navigationDirection = NavigationDirection.FORWARD, navigationKey = config?.defaultKey ?: NoNavigationKey(fragment::class.java, fragment.arguments) ) - + val controller = fragment.requireActivity().application.navigationController val handle = fragment.createNavigationHandleViewModel( - fragment.requireActivity().application.navigationController, + controller, instruction ?: defaultInstruction ) config?.applyTo(handle) - handle.navigationContext = FragmentContext(fragment) + val context = FragmentContext(fragment) + handle.navigationContext = context + controller.onContextCreated(context, savedInstanceState) if(savedInstanceState == null) handle.executeDeeplink() } diff --git a/enro-core/src/main/java/nav/enro/core/synthetic/DefaultSyntheticExecutor.kt b/enro-core/src/main/java/nav/enro/core/synthetic/DefaultSyntheticExecutor.kt new file mode 100644 index 00000000..d50952d1 --- /dev/null +++ b/enro-core/src/main/java/nav/enro/core/synthetic/DefaultSyntheticExecutor.kt @@ -0,0 +1,28 @@ +package nav.enro.core.synthetic + +import androidx.fragment.app.Fragment +import nav.enro.core.ExecutorArgs +import nav.enro.core.NavigationContext +import nav.enro.core.NavigationExecutor +import nav.enro.core.NavigationKey +import java.lang.IllegalStateException + +object DefaultSyntheticExecutor : NavigationExecutor, NavigationKey>( + fromType = Any::class, + opensType = SyntheticDestination::class, + keyType = NavigationKey::class +) { + override fun open(args: ExecutorArgs, out NavigationKey>) { + args.navigator as SyntheticNavigator + + args.navigator.destination.process( + args.fromContext, + args.key, + args.instruction + ) + } + + override fun close(context: NavigationContext>) { + throw IllegalStateException("Synthetic Destinations should not ever execute a 'close' instruction") + } +} \ No newline at end of file diff --git a/enro/src/androidTest/java/nav/enro/core/TestExtensions.kt b/enro/src/androidTest/java/nav/enro/core/TestExtensions.kt index dba01ec8..edac8e59 100644 --- a/enro/src/androidTest/java/nav/enro/core/TestExtensions.kt +++ b/enro/src/androidTest/java/nav/enro/core/TestExtensions.kt @@ -1,6 +1,7 @@ package nav.enro.core import android.app.Application +import android.os.Looper import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentActivity import androidx.test.core.app.ActivityScenario @@ -66,9 +67,10 @@ fun expectNoActivity() { fun waitFor(block: () -> Boolean) { val maximumTime = 20_000 val startTime = System.currentTimeMillis() + while(true) { if(block()) return - Thread.sleep(100) + Thread.sleep(250) if(System.currentTimeMillis() - startTime > maximumTime) throw IllegalStateException("Took too long waiting") } } @@ -80,7 +82,7 @@ fun waitOnMain(block: () -> T?): T { while(true) { if (System.currentTimeMillis() - startTime > maximumTime) throw IllegalStateException("Took too long waiting") - Thread.sleep(100) + Thread.sleep(250) InstrumentationRegistry.getInstrumentation().runOnMainSync { currentResponse = block() } diff --git a/enro/src/androidTest/java/nav/enro/core/overrides/ActivityToActivityOverrideTests.kt b/enro/src/androidTest/java/nav/enro/core/overrides/ActivityToActivityOverrideTests.kt index 9095533e..ba97560c 100644 --- a/enro/src/androidTest/java/nav/enro/core/overrides/ActivityToActivityOverrideTests.kt +++ b/enro/src/androidTest/java/nav/enro/core/overrides/ActivityToActivityOverrideTests.kt @@ -15,11 +15,16 @@ class ActivityToActivityOverrideTests() { @Test fun givenActivityToActivityOverride_whenInitialActivityOpenedWithDefaultKey_whenActivityIsLaunched_thenOverrideIsCalled() { - var launchOverrideCalled = false + var preOpenCalled = false + var openCalled = false + var postOpenCalled = false + application.navigationController.addOverride( createOverride { + preOpened { preOpenCalled = true } + postOpened { postOpenCalled = true } opened { - launchOverrideCalled = true + openCalled = true defaultOpen().invoke(it) } } @@ -30,11 +35,14 @@ class ActivityToActivityOverrideTests() { expectActivity() - assertTrue(launchOverrideCalled) + assertTrue(preOpenCalled) + assertTrue(openCalled) + assertTrue(postOpenCalled) } @Test fun givenActivityToActivityOverride_whenInitialActivityOpenedWithDefaultKey_whenActivityIsClosed_thenOverrideIsCalled() { + var preCloseCalled = false var closeOverrideCalled = false application.navigationController.addOverride ( createOverride { @@ -42,6 +50,9 @@ class ActivityToActivityOverrideTests() { closeOverrideCalled = true defaultClose().invoke(it) } + preClosed { + preCloseCalled = true + } } ) @@ -56,15 +67,21 @@ class ActivityToActivityOverrideTests() { expectActivity() assertTrue(closeOverrideCalled) + assertTrue(preCloseCalled) } @Test fun givenActivityToActivityOverride_whenActivityIsLaunched_thenOverrideIsCalled() { - var launchOverrideCalled = false + var preOpenCalled = false + var openCalled = false + var postOpenCalled = false + application.navigationController.addOverride( createOverride{ + preOpened { preOpenCalled = true } + postOpened { postOpenCalled = true } opened { - launchOverrideCalled = true + openCalled = true defaultOpen().invoke(it) } } @@ -83,18 +100,23 @@ class ActivityToActivityOverrideTests() { expectActivity() - assertTrue(launchOverrideCalled) + assertTrue(preOpenCalled) + assertTrue(openCalled) + assertTrue(postOpenCalled) } @Test fun givenActivityToActivityOverride_whenActivityIsClosed_thenOverrideIsCalled() { var closeOverrideCalled = false + var preCloseCalled = false + application.navigationController.addOverride( createOverride { closed { closeOverrideCalled = true defaultClose().invoke(it) } + preClosed { preCloseCalled = true } } ) @@ -117,16 +139,22 @@ class ActivityToActivityOverrideTests() { expectActivity() assertTrue(closeOverrideCalled) + assertTrue(preCloseCalled) } @Test fun givenUnboundActivityToActivityOverride_whenActivityIsLaunched_thenOverrideIsCalled() { - var launchOverrideCalled = false + var preOpenCalled = false + var openCalled = false + var postOpenCalled = false + application.navigationController.addOverride( createOverride{ + preOpened { preOpenCalled = true } + postOpened { postOpenCalled = true } opened { - launchOverrideCalled = true + openCalled = true defaultOpen().invoke(it) } } @@ -138,18 +166,23 @@ class ActivityToActivityOverrideTests() { expectActivity() - assertTrue(launchOverrideCalled) + assertTrue(preOpenCalled) + assertTrue(openCalled) + assertTrue(postOpenCalled) } @Test fun givenUnboundActivityToActivityOverride_whenActivityIsClosed_thenOverrideIsCalled() { var closeOverrideCalled = false + var preCloseCalled = false + application.navigationController.addOverride( createOverride { closed { closeOverrideCalled = true defaultClose().invoke(it) } + preClosed { preCloseCalled = true } } ) @@ -164,5 +197,6 @@ class ActivityToActivityOverrideTests() { expectActivity() assertTrue(closeOverrideCalled) + assertTrue(preCloseCalled) } } \ No newline at end of file diff --git a/enro/src/androidTest/java/nav/enro/core/overrides/ActivityToFragmentOverrideTests.kt b/enro/src/androidTest/java/nav/enro/core/overrides/ActivityToFragmentOverrideTests.kt index dad7245d..a2af1b64 100644 --- a/enro/src/androidTest/java/nav/enro/core/overrides/ActivityToFragmentOverrideTests.kt +++ b/enro/src/androidTest/java/nav/enro/core/overrides/ActivityToFragmentOverrideTests.kt @@ -11,11 +11,16 @@ class ActivityToFragmentOverrideTests() { @Test fun givenActivityToFragmentOverride_andActivityDoesNotSupportFragment_whenInitialActivityOpenedWithDefaultKey_whenFragmentIsLaunched_whenActivityDoes_thenOverrideIsCalled() { - var launchOverrideCalled = false + var preOpenCalled = false + var openCalled = false + var postOpenCalled = false + application.navigationController.addOverride( createOverride { + preOpened { preOpenCalled = true } + postOpened { postOpenCalled = true } opened { - launchOverrideCalled = true + openCalled = true defaultOpen().invoke(it) } } @@ -26,14 +31,19 @@ class ActivityToFragmentOverrideTests() { expectFragment() - assertTrue(launchOverrideCalled) + assertTrue(preOpenCalled) + assertTrue(openCalled) + assertTrue(postOpenCalled) } @Test fun givenActivityToFragmentOverride_andActivityDoesNotSupportFragment_whenInitialActivityOpenedWithDefaultKey_whenFragmentIsClosed_thenOverrideIsCalled() { var closeOverrideCalled = false + var preCloseCalled = false + application.navigationController.addOverride( createOverride { + preClosed { preCloseCalled = true } closed { closeOverrideCalled = true defaultClose().invoke(it) @@ -51,15 +61,21 @@ class ActivityToFragmentOverrideTests() { expectActivity() assertTrue(closeOverrideCalled) + assertTrue(preCloseCalled) } @Test fun givenActivityToFragmentOverride_andActivityDoesNotSupportFragment_whenFragmentIsLaunched_thenOverrideIsCalled() { - var launchOverrideCalled = false + var preOpenCalled = false + var openCalled = false + var postOpenCalled = false + application.navigationController.addOverride( createOverride { + preOpened { preOpenCalled = true } + postOpened { postOpenCalled = true } opened { - launchOverrideCalled = true + openCalled = true defaultOpen().invoke(it) } } @@ -78,14 +94,19 @@ class ActivityToFragmentOverrideTests() { expectFragment() - assertTrue(launchOverrideCalled) + assertTrue(preOpenCalled) + assertTrue(openCalled) + assertTrue(postOpenCalled) } @Test fun givenActivityToFragmentOverride_andActivityDoesNotSupportFragment_whenFragmentIsClosed_thenOverrideIsCalled() { var closeOverrideCalled = false + var preCloseCalled = false + application.navigationController.addOverride( createOverride { + preClosed { preCloseCalled = true } closed { closeOverrideCalled = true defaultClose().invoke(it) @@ -112,15 +133,21 @@ class ActivityToFragmentOverrideTests() { expectActivity() assertTrue(closeOverrideCalled) + assertTrue(preCloseCalled) } @Test fun givenActivityToFragmentOverride_whenFragmentIsLaunched_thenOverrideIsCalled() { - var launchOverrideCalled = false + var preOpenCalled = false + var openCalled = false + var postOpenCalled = false + application.navigationController.addOverride( createOverride { + preOpened { preOpenCalled = true } + postOpened { postOpenCalled = true } opened { - launchOverrideCalled = true + openCalled = true defaultOpen().invoke(it) } } @@ -139,14 +166,19 @@ class ActivityToFragmentOverrideTests() { expectFragment() - assertTrue(launchOverrideCalled) + assertTrue(preOpenCalled) + assertTrue(openCalled) + assertTrue(postOpenCalled) } @Test fun givenActivityToFragmentOverride_whenFragmentIsClosed_thenOverrideIsCalled() { var closeOverrideCalled = false + var preCloseCalled = false + application.navigationController.addOverride( createOverride { + preClosed { preCloseCalled = true } closed { closeOverrideCalled = true defaultClose().invoke(it) @@ -172,5 +204,6 @@ class ActivityToFragmentOverrideTests() { expectActivity() assertTrue(closeOverrideCalled) + assertTrue(preCloseCalled) } } \ No newline at end of file diff --git a/enro/src/androidTest/java/nav/enro/core/overrides/FragmentToActivityOverrideTests.kt b/enro/src/androidTest/java/nav/enro/core/overrides/FragmentToActivityOverrideTests.kt index 82221d8b..fbeef0f1 100644 --- a/enro/src/androidTest/java/nav/enro/core/overrides/FragmentToActivityOverrideTests.kt +++ b/enro/src/androidTest/java/nav/enro/core/overrides/FragmentToActivityOverrideTests.kt @@ -30,11 +30,16 @@ class FragmentToActivityOverrideTests() { @Test fun givenFragmentToActivityOverride_whenFragmentIsStandalone_whenActivityIsLaunchedFrom_thenOverrideIsCalled() { - var launchOverrideCalled = false + var preOpenCalled = false + var openCalled = false + var postOpenCalled = false + application.navigationController.addOverride( createOverride{ + preOpened { preOpenCalled = true } + postOpened { postOpenCalled = true } opened { - launchOverrideCalled = true + openCalled = true defaultOpen().invoke(it) } } @@ -49,17 +54,22 @@ class FragmentToActivityOverrideTests() { expectActivity() - assertTrue(launchOverrideCalled) + assertTrue(preOpenCalled) + assertTrue(openCalled) + assertTrue(postOpenCalled) } @Test fun givenFragmentToActivityOverride_whenFragmentIsStandalone_whenActivityIsClosed_thenOverrideIsCalled() { var closeOverrideCalled = false + var preCloseCalled = false + application.navigationController.addOverride( createOverride { - opened { + preClosed { preCloseCalled = true} + closed { closeOverrideCalled = true - defaultOpen().invoke(it) + defaultClose().invoke(it) } } ) @@ -78,16 +88,22 @@ class FragmentToActivityOverrideTests() { expectFragment() assertTrue(closeOverrideCalled) + assertTrue(preCloseCalled) } @Test fun givenFragmentToActivityOverride_whenFragmentIsNested_whenActivityIsLaunchedFrom_thenOverrideIsCalled() { - var launchOverrideCalled = false + var preOpenCalled = false + var openCalled = false + var postOpenCalled = false + application.navigationController.addOverride( createOverride { + preOpened { preOpenCalled = true } + postOpened { postOpenCalled = true } opened { - launchOverrideCalled = true + openCalled = true defaultOpen().invoke(it) } } @@ -102,17 +118,22 @@ class FragmentToActivityOverrideTests() { expectActivity() - assertTrue(launchOverrideCalled) + assertTrue(preOpenCalled) + assertTrue(openCalled) + assertTrue(postOpenCalled) } @Test fun givenFragmentToActivityOverride_whenFragmentIsNested_whenActivityIsClosed_thenOverrideIsCalled() { var closeOverrideCalled = false + var preCloseCalled = false + application.navigationController.addOverride( createOverride { - opened { + preClosed { preCloseCalled = true } + closed { closeOverrideCalled = true - defaultOpen().invoke(it) + defaultClose().invoke(it) } } ) @@ -131,6 +152,7 @@ class FragmentToActivityOverrideTests() { expectFragment() assertTrue(closeOverrideCalled) + assertTrue(preCloseCalled) } } \ No newline at end of file diff --git a/enro/src/androidTest/java/nav/enro/core/overrides/FragmentToFragmentOverrideTests.kt b/enro/src/androidTest/java/nav/enro/core/overrides/FragmentToFragmentOverrideTests.kt index df1a12e2..4fde7ccd 100644 --- a/enro/src/androidTest/java/nav/enro/core/overrides/FragmentToFragmentOverrideTests.kt +++ b/enro/src/androidTest/java/nav/enro/core/overrides/FragmentToFragmentOverrideTests.kt @@ -30,11 +30,16 @@ class FragmentToFragmentOverrideTests() { @Test fun givenFragmentToFragmentOverride_whenFragmentIsStandalone_whenFragmentIsLaunched_thenOverrideIsCalled() { - var launchOverrideCalled = false + var preOpenCalled = false + var openCalled = false + var postOpenCalled = false + application.navigationController.addOverride( createOverride { + preOpened { preOpenCalled = true } + postOpened { postOpenCalled = true } opened { - launchOverrideCalled = true + openCalled = true defaultOpen().invoke(it) } } @@ -49,17 +54,21 @@ class FragmentToFragmentOverrideTests() { expectFragment() - assertTrue(launchOverrideCalled) + assertTrue(preOpenCalled) + assertTrue(openCalled) + assertTrue(postOpenCalled) } @Test fun givenFragmentToFragmentOverride_whenFragmentIsStandalone_whenFragmentIsClosed_thenOverrideIsCalled() { var closeOverrideCalled = false + var preCloseCalled = false application.navigationController.addOverride( createOverride { - opened { + preClosed { preCloseCalled = true } + closed { closeOverrideCalled = true - defaultOpen().invoke(it) + defaultClose().invoke(it) } } ) @@ -78,16 +87,22 @@ class FragmentToFragmentOverrideTests() { expectFragment() assertTrue(closeOverrideCalled) + assertTrue(preCloseCalled) } @Test fun givenFragmentToFragmentOverride_whenFragmentIsNested_andTargetIsStandalone_whenFragmentIsLaunched_thenOverrideIsCalled() { - var launchOverrideCalled = false + var preOpenCalled = false + var openCalled = false + var postOpenCalled = false + application.navigationController.addOverride( createOverride { + preOpened { preOpenCalled = true } + postOpened { postOpenCalled = true } opened { - launchOverrideCalled = true + openCalled = true defaultOpen().invoke(it) } } @@ -102,17 +117,21 @@ class FragmentToFragmentOverrideTests() { expectFragment() - assertTrue(launchOverrideCalled) + assertTrue(preOpenCalled) + assertTrue(openCalled) + assertTrue(postOpenCalled) } @Test fun givenFragmentToFragmentOverride_whenFragmentIsNested_andTargetIsStandalone_whenFragmentIsClosed_thenOverrideIsCalled() { var closeOverrideCalled = false + var preCloseCalled = false application.navigationController.addOverride( createOverride { - opened { + preClosed { preCloseCalled = true } + closed { closeOverrideCalled = true - defaultOpen().invoke(it) + defaultClose().invoke(it) } } ) @@ -131,16 +150,22 @@ class FragmentToFragmentOverrideTests() { expectFragment() assertTrue(closeOverrideCalled) + assertTrue(preCloseCalled) } @Test fun givenFragmentToFragmentOverride_whenFragmentIsNested_andTargetIsNested_whenFragmentIsLaunched_thenOverrideIsCalled() { - var launchOverrideCalled = false + var preOpenCalled = false + var openCalled = false + var postOpenCalled = false + application.navigationController.addOverride( createOverride { + preOpened { preOpenCalled = true } + postOpened { postOpenCalled = true } opened { - launchOverrideCalled = true + openCalled = true defaultOpen().invoke(it) } } @@ -155,17 +180,21 @@ class FragmentToFragmentOverrideTests() { expectFragment() - assertTrue(launchOverrideCalled) + assertTrue(preOpenCalled) + assertTrue(openCalled) + assertTrue(postOpenCalled) } @Test fun givenFragmentToFragmentOverride_whenFragmentIsNested_andTargetIsNested_whenFragmentIsClosed_thenOverrideIsCalled() { var closeOverrideCalled = false + var preCloseCalled = false application.navigationController.addOverride( createOverride { - opened { + preClosed { preCloseCalled = true } + closed { closeOverrideCalled = true - defaultOpen().invoke(it) + defaultClose().invoke(it) } } ) @@ -184,6 +213,7 @@ class FragmentToFragmentOverrideTests() { expectFragment() assertTrue(closeOverrideCalled) + assertTrue(preCloseCalled) } } \ No newline at end of file From 65e938f845f25930ad4914382eae9ccee3b0edeb Mon Sep 17 00:00:00 2001 From: Isaac Date: Sat, 26 Dec 2020 20:04:56 +1300 Subject: [PATCH 13/24] Remove logging statements --- .../java/nav/enro/core/controller/NavigationController.kt | 5 ----- 1 file changed, 5 deletions(-) diff --git a/enro-core/src/main/java/nav/enro/core/controller/NavigationController.kt b/enro-core/src/main/java/nav/enro/core/controller/NavigationController.kt index 4abe80d3..142a1f61 100644 --- a/enro-core/src/main/java/nav/enro/core/controller/NavigationController.kt +++ b/enro-core/src/main/java/nav/enro/core/controller/NavigationController.kt @@ -90,8 +90,6 @@ class NavigationController( val navigator = navigatorForKeyType(instruction.navigationKey::class) ?: throw IllegalStateException("Attempted to execute $instruction but could not find a valid navigator for the key type on this instruction") - Log.e("FINDING OVERRIDE FOR", "Exec open for ${instruction::class.java.simpleName} from ${navigationContext.contextReference}") - val executor = executorForOpen(navigationContext, navigator.contextType) val args = ExecutorArgs( @@ -103,7 +101,6 @@ class NavigationController( .setParentContext(executor.context) ) - Log.e("FINDING OVERRIDE FOR", "Exec open for ${instruction::class.java.simpleName} with real context ${executor.context.contextReference}") executor.executor.preOpened(executor.context) executor.executor.open(args) } @@ -125,8 +122,6 @@ class NavigationController( } internal fun onContextCreated(navigationContext: NavigationContext, savedInstanceState: Bundle?) { - - Log.e("FINDING OVERRIDE FOR", "CONTEXT CREATED ${navigationContext.contextReference}\n\n-") if(savedInstanceState == null) { executorForClose(navigationContext).postOpened(navigationContext) } From c0a90d5ecea1a4d6d7b27aea4ca87b31e8fa0f96 Mon Sep 17 00:00:00 2001 From: Isaac Date: Sat, 26 Dec 2020 21:40:53 +1300 Subject: [PATCH 14/24] Update animation handling, allow configuration via the override functionality --- .../nav/enro/core/NavigationAnimations.kt | 148 +++++++----------- .../java/nav/enro/core/NavigationExecutor.kt | 48 +++++- .../nav/enro/core/NavigationInstruction.kt | 1 - .../src/main/java/nav/enro/core/Navigator.kt | 1 - .../java/nav/enro/core/NavigatorAnimations.kt | 65 -------- .../enro/core/activity/ActivityNavigator.kt | 2 - .../controller/NavigationComponentBuilder.kt | 10 +- .../core/controller/NavigationController.kt | 38 ++++- .../enro/core/fragment/FragmentNavigator.kt | 2 - .../enro/core/{ => internal}/Extensions.kt | 2 +- .../nav/enro/core/internal/NoNavigationKey.kt | 2 - .../enro/core/synthetic/SyntheticNavigator.kt | 2 - .../MultistackControllerFragment.kt | 2 +- .../nav/enro/example/ExampleApplication.kt | 8 +- .../java/nav/enro/example/SimpleMessage.kt | 1 - .../java/nav/enro/example/SplashScreen.kt | 3 +- 16 files changed, 143 insertions(+), 192 deletions(-) delete mode 100644 enro-core/src/main/java/nav/enro/core/NavigatorAnimations.kt rename enro-core/src/main/java/nav/enro/core/{ => internal}/Extensions.kt (87%) diff --git a/enro-core/src/main/java/nav/enro/core/NavigationAnimations.kt b/enro-core/src/main/java/nav/enro/core/NavigationAnimations.kt index ba6df977..cc124f78 100644 --- a/enro-core/src/main/java/nav/enro/core/NavigationAnimations.kt +++ b/enro-core/src/main/java/nav/enro/core/NavigationAnimations.kt @@ -1,128 +1,92 @@ package nav.enro.core import android.content.res.Resources +import android.os.Parcel import android.os.Parcelable import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentActivity import kotlinx.android.parcel.Parcelize +import nav.enro.core.internal.getAttributeResourceId -sealed class NavigationAnimations : Parcelable { - @Parcelize - data class Resource( - val openEnter: Int, - val openExit: Int, - val closeEnter: Int, - val closeExit: Int - ) : NavigationAnimations() +sealed class AnimationPair : Parcelable{ + abstract val enter: Int + abstract val exit: Int @Parcelize - data class Attr( - val openEnter: Int, - val openExit: Int, - val closeEnter: Int, - val closeExit: Int - ) : NavigationAnimations() + class Resource( + override val enter: Int, + override val exit: Int + ): AnimationPair() - companion object { - val default = Attr( - openEnter = android.R.attr.activityOpenEnterAnimation, - openExit = android.R.attr.activityOpenExitAnimation, - closeEnter = android.R.attr.activityCloseEnterAnimation, - closeExit = android.R.attr.activityCloseExitAnimation + @Parcelize + class Attr( + override val enter: Int, + override val exit: Int + ): AnimationPair() + + fun asResource(theme: Resources.Theme) = when(this) { + is Resource -> this + is Attr -> Resource( + theme.getAttributeResourceId(enter), + theme.getAttributeResourceId(exit) ) - - val none = Resource(0, R.anim.enro_no_op_animation, 0, 0) } } -fun NavigationAnimations.toResource(theme: Resources.Theme): NavigationAnimations.Resource = - when (this) { - is NavigationAnimations.Resource -> this - is NavigationAnimations.Attr -> NavigationAnimations.Resource( - openEnter = theme.getAttributeResourceId(openEnter), - openExit = theme.getAttributeResourceId(openExit), - closeEnter = theme.getAttributeResourceId(closeEnter), - closeExit = theme.getAttributeResourceId(closeExit) - ) - } - - -data class AnimationPair( - val enter: Int, - val exit: Int -) - -fun animationsFor( - context: FragmentActivity, - navigationInstruction: NavigationInstruction -): AnimationPair = animationsFor(context.navigationContext, navigationInstruction) - -fun animationsFor(context: Fragment, navigationInstruction: NavigationInstruction): AnimationPair = - animationsFor(context.navigationContext, navigationInstruction) +object DefaultAnimations { + val forward = AnimationPair.Attr( + enter = android.R.attr.activityOpenEnterAnimation, + exit = android.R.attr.activityOpenExitAnimation + ) + + val replace = AnimationPair.Attr( + enter = android.R.attr.activityOpenEnterAnimation, + exit = android.R.attr.activityOpenExitAnimation + ) + + val replaceRoot = AnimationPair.Attr( + enter = android.R.attr.taskOpenEnterAnimation, + exit = android.R.attr.taskOpenExitAnimation + ) + + val close = AnimationPair.Attr( + enter = android.R.attr.activityCloseEnterAnimation, + exit = android.R.attr.activityCloseExitAnimation + ) + + val none = AnimationPair.Resource( + enter = 0, + exit = R.anim.enro_no_op_animation + ) +} fun animationsFor( context: NavigationContext<*>, navigationInstruction: NavigationInstruction -): AnimationPair { +): AnimationPair.Resource { if (navigationInstruction is NavigationInstruction.Open && navigationInstruction.children.isNotEmpty()) { - return AnimationPair(0, 0) + return AnimationPair.Resource(0, 0) } return when (navigationInstruction) { is NavigationInstruction.Open -> animationsForOpen(context, navigationInstruction) - is NavigationInstruction.Close -> animationsForClose(context, navigationInstruction) + is NavigationInstruction.Close -> animationsForClose(context) } } private fun animationsForOpen( context: NavigationContext<*>, navigationInstruction: NavigationInstruction.Open -): AnimationPair { +): AnimationPair.Resource { val theme = context.activity.theme - val navigator = context.navigator - - val navigatorAnimations = navigator?.animations?.toResource(theme) - ?: NavigatorAnimations.default.toResource(theme) - val instructionAnimations = navigationInstruction.animations?.toResource(theme) - - return when { - instructionAnimations != null -> AnimationPair( - instructionAnimations.openEnter, - instructionAnimations.openExit - ) - else -> when (navigationInstruction.navigationDirection) { - NavigationDirection.FORWARD -> AnimationPair( - navigatorAnimations.forwardEnter, - navigatorAnimations.forwardExit - ) - NavigationDirection.REPLACE -> AnimationPair( - navigatorAnimations.replaceEnter, - navigatorAnimations.replaceExit - ) - NavigationDirection.REPLACE_ROOT -> AnimationPair( - navigatorAnimations.replaceRootEnter, - navigatorAnimations.replaceRootExit - ) - } - } + val executor = context.activity.application.navigationController.executorForOpen(context, navigationInstruction) + return executor.executor.animation(navigationInstruction).asResource(theme) } private fun animationsForClose( - context: NavigationContext<*>, - navigationInstruction: NavigationInstruction.Close -): AnimationPair { + context: NavigationContext<*> +): AnimationPair.Resource { val theme = context.activity.theme - val navigator = context.navigator - - val navigatorAnimations = navigator?.animations?.toResource(theme) - ?: NavigatorAnimations.default.toResource(theme) - val animations = context.getNavigationHandleViewModel().instruction.animations?.toResource(theme) - - return when { - animations != null -> AnimationPair( - animations.closeEnter, - animations.closeExit - ) - else -> AnimationPair(navigatorAnimations.closeEnter, navigatorAnimations.closeExit) - } + val executor = context.activity.application.navigationController.executorForClose(context) + return executor.closeAnimation(context).asResource(theme) } \ No newline at end of file diff --git a/enro-core/src/main/java/nav/enro/core/NavigationExecutor.kt b/enro-core/src/main/java/nav/enro/core/NavigationExecutor.kt index 0b2f9b84..3a5fae14 100644 --- a/enro-core/src/main/java/nav/enro/core/NavigationExecutor.kt +++ b/enro-core/src/main/java/nav/enro/core/NavigationExecutor.kt @@ -19,6 +19,18 @@ abstract class NavigationExecutor, val keyType: KClass ) { + open fun animation(instruction: NavigationInstruction.Open): AnimationPair { + return when(instruction.navigationDirection) { + NavigationDirection.FORWARD -> DefaultAnimations.forward + NavigationDirection.REPLACE -> DefaultAnimations.replace + NavigationDirection.REPLACE_ROOT -> DefaultAnimations.replaceRoot + } + } + + open fun closeAnimation(context: NavigationContext): AnimationPair { + return DefaultAnimations.close + } + open fun preOpened( context: NavigationContext ) {} @@ -46,6 +58,8 @@ class NavigationExecutorBuilder ) { + private var animationFunc: ((instruction: NavigationInstruction.Open) -> AnimationPair)? = null + private var closeAnimationFunc: ((context: NavigationContext) -> AnimationPair)? = null private var preOpenedFunc: (( context: NavigationContext) -> Unit)? = null private var openedFunc: ((args: ExecutorArgs) -> Unit)? = null private var postOpenedFunc: ((context: NavigationContext) -> Unit)? = null @@ -53,29 +67,39 @@ class NavigationExecutorBuilder) -> Unit)? = null @Suppress("UNCHECKED_CAST") - private val defaultOpens: (args: ExecutorArgs) -> Unit by lazy { + private val defaultOpens: (args: ExecutorArgs) -> Unit = { when { - FragmentActivity::class.java.isAssignableFrom(opensType.java) -> + FragmentActivity::class.java.isAssignableFrom(it.navigator.contextType.java) -> DefaultActivityExecutor::open as ((ExecutorArgs) -> Unit) - Fragment::class.java.isAssignableFrom(opensType.java) -> + Fragment::class.java.isAssignableFrom(it.navigator.contextType.java) -> DefaultFragmentExecutor::open as ((ExecutorArgs) -> Unit) else -> throw IllegalArgumentException("No default launch executor found for ${opensType.java}") - } + }.invoke(it) } @Suppress("UNCHECKED_CAST") - private val defaultCloses: (context: NavigationContext) -> Unit by lazy { + private val defaultCloses: (context: NavigationContext) -> Unit = { when { - FragmentActivity::class.java.isAssignableFrom(opensType.java) -> + FragmentActivity::class.java.isAssignableFrom(it.contextReference::class.java) -> DefaultActivityExecutor::close as (NavigationContext) -> Unit - Fragment::class.java.isAssignableFrom(opensType.java) -> + Fragment::class.java.isAssignableFrom(it.contextReference::class.java) -> DefaultFragmentExecutor::close as (NavigationContext) -> Unit else -> throw IllegalArgumentException("No default close executor found for ${opensType.java}") - } + }.invoke(it) + } + + fun animation(block: (instruction: NavigationInstruction.Open) -> AnimationPair) { + if(animationFunc != null) throw IllegalStateException("Value is already set!") + animationFunc = block + } + + fun closeAnimation(block: ( context: NavigationContext) -> AnimationPair) { + if(closeAnimationFunc != null) throw IllegalStateException("Value is already set!") + closeAnimationFunc = block } fun preOpened(block: ( context: NavigationContext) -> Unit) { @@ -108,6 +132,14 @@ class NavigationExecutorBuilder): AnimationPair { + return closeAnimationFunc?.invoke(context) ?: super.closeAnimation(context) + } + override fun preOpened(context: NavigationContext) { preOpenedFunc?.invoke(context) } diff --git a/enro-core/src/main/java/nav/enro/core/NavigationInstruction.kt b/enro-core/src/main/java/nav/enro/core/NavigationInstruction.kt index 793e0693..5eaa1496 100644 --- a/enro-core/src/main/java/nav/enro/core/NavigationInstruction.kt +++ b/enro-core/src/main/java/nav/enro/core/NavigationInstruction.kt @@ -27,7 +27,6 @@ sealed class NavigationInstruction { val children: List = emptyList(), val parentInstruction: Open? = null, val parentContext: Class? = null, - val animations: NavigationAnimations? = null, val additionalData: Bundle = Bundle(), val instructionId: String = UUID.randomUUID().toString() ) : NavigationInstruction(), Parcelable diff --git a/enro-core/src/main/java/nav/enro/core/Navigator.kt b/enro-core/src/main/java/nav/enro/core/Navigator.kt index 908f51af..8a847564 100644 --- a/enro-core/src/main/java/nav/enro/core/Navigator.kt +++ b/enro-core/src/main/java/nav/enro/core/Navigator.kt @@ -5,5 +5,4 @@ import kotlin.reflect.KClass interface Navigator { val keyType: KClass val contextType: KClass - val animations: NavigatorAnimations } \ No newline at end of file diff --git a/enro-core/src/main/java/nav/enro/core/NavigatorAnimations.kt b/enro-core/src/main/java/nav/enro/core/NavigatorAnimations.kt deleted file mode 100644 index dd598349..00000000 --- a/enro-core/src/main/java/nav/enro/core/NavigatorAnimations.kt +++ /dev/null @@ -1,65 +0,0 @@ -package nav.enro.core - -import android.R -import android.content.res.Resources - -// Allow setting on NavigationDestination annotation? -sealed class NavigatorAnimations { - data class Resource( - val forwardEnter: Int, - val forwardExit: Int, - - val closeEnter: Int, - val closeExit: Int, - - val replaceEnter: Int = forwardEnter, - val replaceExit: Int = forwardExit, - - val replaceRootEnter: Int = replaceEnter, - val replaceRootExit: Int = replaceExit - ): NavigatorAnimations() - - data class Attr( - val forwardEnter: Int, - val forwardExit: Int, - - val closeEnter: Int, - val closeExit: Int, - - val replaceEnter: Int = forwardEnter, - val replaceExit: Int = forwardExit, - - val replaceRootEnter: Int = replaceEnter, - val replaceRootExit: Int = replaceExit - ): NavigatorAnimations() - companion object { - val default = Attr( - forwardEnter = R.attr.activityOpenEnterAnimation, - forwardExit = R.attr.activityOpenExitAnimation, - - replaceEnter = R.attr.activityOpenEnterAnimation, - replaceExit = R.attr.activityOpenExitAnimation, - - replaceRootEnter = R.attr.taskOpenEnterAnimation, - replaceRootExit = R.attr.taskOpenExitAnimation, - - closeEnter = R.attr.activityCloseEnterAnimation, - closeExit = R.attr.activityCloseExitAnimation - ) - } -} - -fun NavigatorAnimations.toResource(theme: Resources.Theme): NavigatorAnimations.Resource = - when(this) { - is NavigatorAnimations.Resource -> this - is NavigatorAnimations.Attr -> NavigatorAnimations.Resource( - forwardEnter = theme.getAttributeResourceId(forwardEnter), - forwardExit = theme.getAttributeResourceId(forwardExit), - closeEnter = theme.getAttributeResourceId(closeEnter), - closeExit = theme.getAttributeResourceId(closeExit), - replaceEnter = theme.getAttributeResourceId(replaceEnter), - replaceExit = theme.getAttributeResourceId(replaceExit), - replaceRootEnter = theme.getAttributeResourceId(replaceRootEnter), - replaceRootExit = theme.getAttributeResourceId(replaceRootExit) - ) - } diff --git a/enro-core/src/main/java/nav/enro/core/activity/ActivityNavigator.kt b/enro-core/src/main/java/nav/enro/core/activity/ActivityNavigator.kt index 2cd1d2a0..4753e835 100644 --- a/enro-core/src/main/java/nav/enro/core/activity/ActivityNavigator.kt +++ b/enro-core/src/main/java/nav/enro/core/activity/ActivityNavigator.kt @@ -3,13 +3,11 @@ package nav.enro.core.activity import androidx.fragment.app.FragmentActivity import nav.enro.core.NavigationKey import nav.enro.core.Navigator -import nav.enro.core.NavigatorAnimations import kotlin.reflect.KClass class ActivityNavigator @PublishedApi internal constructor( override val keyType: KClass, override val contextType: KClass, - override val animations: NavigatorAnimations = NavigatorAnimations.default ) : Navigator fun createActivityNavigator( diff --git a/enro-core/src/main/java/nav/enro/core/controller/NavigationComponentBuilder.kt b/enro-core/src/main/java/nav/enro/core/controller/NavigationComponentBuilder.kt index 290b364a..266c70f8 100644 --- a/enro-core/src/main/java/nav/enro/core/controller/NavigationComponentBuilder.kt +++ b/enro-core/src/main/java/nav/enro/core/controller/NavigationComponentBuilder.kt @@ -1,8 +1,6 @@ package nav.enro.core.controller -import nav.enro.core.NavigationApplication -import nav.enro.core.NavigationExecutor -import nav.enro.core.Navigator +import nav.enro.core.* import nav.enro.core.plugins.EnroPlugin // TODO get rid of this, or give it a better name @@ -26,6 +24,12 @@ class NavigationComponentBuilder { overrides.add(override) } + inline fun override( + noinline block: NavigationExecutorBuilder.() -> Unit + ) { + overrides.add(createOverride(From::class, Opens::class, block)) + } + fun component(builder: NavigationComponentBuilder) { navigators.addAll(builder.navigators) overrides.addAll(builder.overrides) diff --git a/enro-core/src/main/java/nav/enro/core/controller/NavigationController.kt b/enro-core/src/main/java/nav/enro/core/controller/NavigationController.kt index 142a1f61..71cb4036 100644 --- a/enro-core/src/main/java/nav/enro/core/controller/NavigationController.kt +++ b/enro-core/src/main/java/nav/enro/core/controller/NavigationController.kt @@ -90,7 +90,7 @@ class NavigationController( val navigator = navigatorForKeyType(instruction.navigationKey::class) ?: throw IllegalStateException("Attempted to execute $instruction but could not find a valid navigator for the key type on this instruction") - val executor = executorForOpen(navigationContext, navigator.contextType) + val executor = executorForOpen(navigationContext, instruction) val args = ExecutorArgs( executor.context, @@ -143,7 +143,11 @@ class NavigationController( return temporaryOverrides[types] ?: overrides[types] } - private fun executorForOpen(fromContext: NavigationContext, opensContext: KClass): OpenExecutorPair { + internal fun executorForOpen(fromContext: NavigationContext, instruction: NavigationInstruction.Open): OpenExecutorPair { + val navigator = navigatorForKeyType(instruction.navigationKey::class) + ?: throw IllegalStateException("Attempted to find executor for $instruction but could not find a valid navigator for the key type on this instruction") + + val opensContext = navigator.contextType val opensContextIsActivity by lazy { FragmentActivity::class.java.isAssignableFrom(opensContext.java) } @@ -189,12 +193,32 @@ class NavigationController( } @Suppress("UNCHECKED_CAST") - private fun executorForClose(navigationContext: NavigationContext): NavigationExecutor { + internal fun executorForClose(navigationContext: NavigationContext): NavigationExecutor { val parentContextType = navigationContext.getNavigationHandleViewModel().instruction.parentContext?.kotlin - val override = parentContextType?.let { - overrideFor(it to navigationContext.contextReference::class) - as? NavigationExecutor - } + val contextType = navigationContext.contextReference::class + + val override = parentContextType?.let { parentContext -> + val parentContextIsActivity by lazy { + FragmentActivity::class.java.isAssignableFrom(parentContext.java) + } + + val parentContextIsFragment by lazy { + Fragment::class.java.isAssignableFrom(parentContext.java) + } + + overrideFor(parentContext to contextType) + ?: when { + parentContextIsActivity -> overrideFor(FragmentActivity::class to contextType) + parentContextIsFragment -> overrideFor(Fragment::class to contextType) + else -> null + } + ?: overrideFor(Any::class to contextType) + ?: when(navigationContext) { + is ActivityContext -> overrideFor(parentContext to FragmentActivity::class) + is FragmentContext -> overrideFor(parentContext to Fragment::class) + } + ?: overrideFor(parentContext to Any::class) + } as? NavigationExecutor return override ?: when (navigationContext) { is ActivityContext -> DefaultActivityExecutor as NavigationExecutor diff --git a/enro-core/src/main/java/nav/enro/core/fragment/FragmentNavigator.kt b/enro-core/src/main/java/nav/enro/core/fragment/FragmentNavigator.kt index 6ed9177c..32d5ec88 100644 --- a/enro-core/src/main/java/nav/enro/core/fragment/FragmentNavigator.kt +++ b/enro-core/src/main/java/nav/enro/core/fragment/FragmentNavigator.kt @@ -3,13 +3,11 @@ package nav.enro.core.fragment import androidx.fragment.app.Fragment import nav.enro.core.NavigationKey import nav.enro.core.Navigator -import nav.enro.core.NavigatorAnimations import kotlin.reflect.KClass class FragmentNavigator @PublishedApi internal constructor( override val keyType: KClass, override val contextType: KClass, - override val animations: NavigatorAnimations = NavigatorAnimations.default ) : Navigator fun createFragmentNavigator( diff --git a/enro-core/src/main/java/nav/enro/core/Extensions.kt b/enro-core/src/main/java/nav/enro/core/internal/Extensions.kt similarity index 87% rename from enro-core/src/main/java/nav/enro/core/Extensions.kt rename to enro-core/src/main/java/nav/enro/core/internal/Extensions.kt index bb01a6f6..903c73fa 100644 --- a/enro-core/src/main/java/nav/enro/core/Extensions.kt +++ b/enro-core/src/main/java/nav/enro/core/internal/Extensions.kt @@ -1,4 +1,4 @@ -package nav.enro.core +package nav.enro.core.internal import android.content.res.Resources import android.util.TypedValue diff --git a/enro-core/src/main/java/nav/enro/core/internal/NoNavigationKey.kt b/enro-core/src/main/java/nav/enro/core/internal/NoNavigationKey.kt index 0cefc2bc..3960e7cc 100644 --- a/enro-core/src/main/java/nav/enro/core/internal/NoNavigationKey.kt +++ b/enro-core/src/main/java/nav/enro/core/internal/NoNavigationKey.kt @@ -4,7 +4,6 @@ import android.os.Bundle import kotlinx.android.parcel.Parcelize import nav.enro.core.NavigationKey import nav.enro.core.Navigator -import nav.enro.core.NavigatorAnimations import kotlin.reflect.KClass @Parcelize @@ -16,5 +15,4 @@ internal class NoNavigationKey( internal class NoKeyNavigator: Navigator { override val keyType: KClass = NoNavigationKey::class override val contextType: KClass = Nothing::class - override val animations: NavigatorAnimations = NavigatorAnimations.default } \ No newline at end of file diff --git a/enro-core/src/main/java/nav/enro/core/synthetic/SyntheticNavigator.kt b/enro-core/src/main/java/nav/enro/core/synthetic/SyntheticNavigator.kt index af6042de..a2198d7d 100644 --- a/enro-core/src/main/java/nav/enro/core/synthetic/SyntheticNavigator.kt +++ b/enro-core/src/main/java/nav/enro/core/synthetic/SyntheticNavigator.kt @@ -2,7 +2,6 @@ package nav.enro.core.synthetic import nav.enro.core.NavigationKey import nav.enro.core.Navigator -import nav.enro.core.NavigatorAnimations import kotlin.reflect.KClass @@ -11,7 +10,6 @@ class SyntheticNavigator @PublishedApi internal constru val destination: SyntheticDestination ) : Navigator> { override val contextType: KClass> = SyntheticDestination::class - override val animations: NavigatorAnimations = NavigatorAnimations.default } fun createSyntheticNavigator( diff --git a/enro-multistack/src/main/java/nav/enro/multistack/MultistackControllerFragment.kt b/enro-multistack/src/main/java/nav/enro/multistack/MultistackControllerFragment.kt index 45f5f105..597596c8 100644 --- a/enro-multistack/src/main/java/nav/enro/multistack/MultistackControllerFragment.kt +++ b/enro-multistack/src/main/java/nav/enro/multistack/MultistackControllerFragment.kt @@ -131,7 +131,7 @@ internal class MultistackControllerFragment : Fragment(), ViewTreeObserver.OnGlo } } - val animation = openStackAnimation ?: NavigatorAnimations.default.toResource(requireActivity().theme).replaceEnter + val animation = openStackAnimation ?: DefaultAnimations.replace.asResource(requireActivity().theme).enter val enter = AnimationUtils.loadAnimation(requireContext(), animation) activeContainer.startAnimation(enter) diff --git a/example/src/main/java/nav/enro/example/ExampleApplication.kt b/example/src/main/java/nav/enro/example/ExampleApplication.kt index a513058c..17d60797 100644 --- a/example/src/main/java/nav/enro/example/ExampleApplication.kt +++ b/example/src/main/java/nav/enro/example/ExampleApplication.kt @@ -2,9 +2,8 @@ package nav.enro.example import android.app.Application import nav.enro.annotations.NavigationComponent -import nav.enro.core.NavigationApplication +import nav.enro.core.* import nav.enro.core.controller.navigationController -import nav.enro.core.navigationController import nav.enro.core.plugins.EnroLogger import nav.enro.result.EnroResult @@ -13,5 +12,10 @@ class ExampleApplication : Application(), NavigationApplication { override val navigationController = navigationController { plugin(EnroResult()) plugin(EnroLogger()) + + override { + animation { DefaultAnimations.none } + } + } } \ No newline at end of file diff --git a/example/src/main/java/nav/enro/example/SimpleMessage.kt b/example/src/main/java/nav/enro/example/SimpleMessage.kt index 646630c0..ca810077 100644 --- a/example/src/main/java/nav/enro/example/SimpleMessage.kt +++ b/example/src/main/java/nav/enro/example/SimpleMessage.kt @@ -24,7 +24,6 @@ class SimpleMessageDestination : SyntheticDestination { key: SimpleMessage, instruction: NavigationInstruction.Open ) { - val key = instruction.navigationKey as SimpleMessage val activity = navigationContext.activity AlertDialog.Builder(activity).apply { setTitle(key.title) diff --git a/example/src/main/java/nav/enro/example/SplashScreen.kt b/example/src/main/java/nav/enro/example/SplashScreen.kt index e031fd04..6875233e 100644 --- a/example/src/main/java/nav/enro/example/SplashScreen.kt +++ b/example/src/main/java/nav/enro/example/SplashScreen.kt @@ -28,8 +28,7 @@ class SplashScreenActivity : AppCompatActivity() { super.onResume() navigation.executeInstruction(NavigationInstruction.Open( navigationDirection = NavigationDirection.REPLACE_ROOT, - navigationKey = MainKey(), - animations = NavigationAnimations.none + navigationKey = MainKey() )) } } \ No newline at end of file From ef9c80c841c66955939d76b2a58a3e8482512498 Mon Sep 17 00:00:00 2001 From: Isaac Date: Sat, 26 Dec 2020 21:54:58 +1300 Subject: [PATCH 15/24] Replaced defaultOpen and defaultClose with methods on the NavigationExecutorBuilder --- .../java/nav/enro/core/NavigationExecutor.kt | 36 +++++++++----- .../java/nav/enro/core/NavigationOverride.kt | 47 ------------------- .../ActivityToActivityOverrideTests.kt | 14 +++--- .../ActivityToFragmentOverrideTests.kt | 12 ++--- .../FragmentToActivityOverrideTests.kt | 9 ++-- .../FragmentToFragmentOverrideTests.kt | 13 +++-- .../java/nav/enro/example/SplashScreen.kt | 5 +- 7 files changed, 48 insertions(+), 88 deletions(-) delete mode 100644 enro-core/src/main/java/nav/enro/core/NavigationOverride.kt diff --git a/enro-core/src/main/java/nav/enro/core/NavigationExecutor.kt b/enro-core/src/main/java/nav/enro/core/NavigationExecutor.kt index 3a5fae14..dc81d4db 100644 --- a/enro-core/src/main/java/nav/enro/core/NavigationExecutor.kt +++ b/enro-core/src/main/java/nav/enro/core/NavigationExecutor.kt @@ -67,29 +67,29 @@ class NavigationExecutorBuilder) -> Unit)? = null @Suppress("UNCHECKED_CAST") - private val defaultOpens: (args: ExecutorArgs) -> Unit = { + fun defaultOpened(args: ExecutorArgs) { when { - FragmentActivity::class.java.isAssignableFrom(it.navigator.contextType.java) -> + FragmentActivity::class.java.isAssignableFrom(args.navigator.contextType.java) -> DefaultActivityExecutor::open as ((ExecutorArgs) -> Unit) - Fragment::class.java.isAssignableFrom(it.navigator.contextType.java) -> + Fragment::class.java.isAssignableFrom(args.navigator.contextType.java) -> DefaultFragmentExecutor::open as ((ExecutorArgs) -> Unit) else -> throw IllegalArgumentException("No default launch executor found for ${opensType.java}") - }.invoke(it) + }.invoke(args) } @Suppress("UNCHECKED_CAST") - private val defaultCloses: (context: NavigationContext) -> Unit = { + fun defaultClosed(context: NavigationContext) { when { - FragmentActivity::class.java.isAssignableFrom(it.contextReference::class.java) -> + FragmentActivity::class.java.isAssignableFrom(context.contextReference::class.java) -> DefaultActivityExecutor::close as (NavigationContext) -> Unit - Fragment::class.java.isAssignableFrom(it.contextReference::class.java) -> + Fragment::class.java.isAssignableFrom(context.contextReference::class.java) -> DefaultFragmentExecutor::close as (NavigationContext) -> Unit else -> throw IllegalArgumentException("No default close executor found for ${opensType.java}") - }.invoke(it) + }.invoke(context) } fun animation(block: (instruction: NavigationInstruction.Open) -> AnimationPair) { @@ -145,7 +145,7 @@ class NavigationExecutorBuilder) { - (openedFunc ?: defaultOpens).invoke(args) + openedFunc?.invoke(args) ?: defaultOpened(args) } override fun postOpened(context: NavigationContext) { @@ -157,7 +157,21 @@ class NavigationExecutorBuilder) { - (closedFunc ?: defaultCloses).invoke(context) + closedFunc?.invoke(context) ?: defaultClosed(context) } } -} \ No newline at end of file +} + +fun createOverride( + fromClass: KClass, + opensClass: KClass, + block: NavigationExecutorBuilder.() -> Unit +): NavigationExecutor = + NavigationExecutorBuilder(fromClass, opensClass, NavigationKey::class) + .apply(block) + .build() + +inline fun createOverride( + noinline block: NavigationExecutorBuilder.() -> Unit +): NavigationExecutor = + createOverride(From::class, Opens::class, block) diff --git a/enro-core/src/main/java/nav/enro/core/NavigationOverride.kt b/enro-core/src/main/java/nav/enro/core/NavigationOverride.kt deleted file mode 100644 index 75dc42c4..00000000 --- a/enro-core/src/main/java/nav/enro/core/NavigationOverride.kt +++ /dev/null @@ -1,47 +0,0 @@ -package nav.enro.core - -import androidx.fragment.app.Fragment -import androidx.fragment.app.FragmentActivity -import nav.enro.core.activity.DefaultActivityExecutor -import nav.enro.core.fragment.DefaultFragmentExecutor -import kotlin.reflect.KClass - -fun createOverride( - fromClass: KClass, - opensClass: KClass, - block: NavigationExecutorBuilder.() -> Unit -): NavigationExecutor = - NavigationExecutorBuilder(fromClass, opensClass, NavigationKey::class) - .apply(block) - .build() - -inline fun createOverride( - noinline block: NavigationExecutorBuilder.() -> Unit -): NavigationExecutor = - createOverride(From::class, Opens::class, block) - -@Suppress("UNCHECKED_CAST") -inline fun defaultOpen(): ((ExecutorArgs) -> Unit) { - return when { - FragmentActivity::class.java.isAssignableFrom(Opens::class.java) -> - DefaultActivityExecutor::open as ((ExecutorArgs) -> Unit) - - Fragment::class.java.isAssignableFrom(Opens::class.java) -> - DefaultFragmentExecutor::open as ((ExecutorArgs) -> Unit) - - else -> throw IllegalArgumentException("No default launch executor found for ${Opens::class}") - } -} - -@Suppress("UNCHECKED_CAST") -inline fun defaultClose(): (NavigationContext) -> Unit { - return when { - FragmentActivity::class.java.isAssignableFrom(Opens::class.java) -> - DefaultActivityExecutor::close as (NavigationContext) -> Unit - - Fragment::class.java.isAssignableFrom(Opens::class.java) -> - DefaultFragmentExecutor::close as (NavigationContext) -> Unit - - else -> throw IllegalArgumentException("No default close executor found for ${Opens::class}") - } -} \ No newline at end of file diff --git a/enro/src/androidTest/java/nav/enro/core/overrides/ActivityToActivityOverrideTests.kt b/enro/src/androidTest/java/nav/enro/core/overrides/ActivityToActivityOverrideTests.kt index ba97560c..f14c05c0 100644 --- a/enro/src/androidTest/java/nav/enro/core/overrides/ActivityToActivityOverrideTests.kt +++ b/enro/src/androidTest/java/nav/enro/core/overrides/ActivityToActivityOverrideTests.kt @@ -7,8 +7,6 @@ import nav.enro.* import nav.enro.core.* import nav.enro.core.navigationController import nav.enro.core.createOverride -import nav.enro.core.defaultClose -import nav.enro.core.defaultOpen import org.junit.Test class ActivityToActivityOverrideTests() { @@ -25,7 +23,7 @@ class ActivityToActivityOverrideTests() { postOpened { postOpenCalled = true } opened { openCalled = true - defaultOpen().invoke(it) + defaultOpened(it) } } ) @@ -48,7 +46,7 @@ class ActivityToActivityOverrideTests() { createOverride { closed { closeOverrideCalled = true - defaultClose().invoke(it) + defaultClosed(it) } preClosed { preCloseCalled = true @@ -82,7 +80,7 @@ class ActivityToActivityOverrideTests() { postOpened { postOpenCalled = true } opened { openCalled = true - defaultOpen().invoke(it) + defaultOpened(it) } } ) @@ -114,7 +112,7 @@ class ActivityToActivityOverrideTests() { createOverride { closed { closeOverrideCalled = true - defaultClose().invoke(it) + defaultClosed(it) } preClosed { preCloseCalled = true } } @@ -155,7 +153,7 @@ class ActivityToActivityOverrideTests() { postOpened { postOpenCalled = true } opened { openCalled = true - defaultOpen().invoke(it) + defaultOpened(it) } } ) @@ -180,7 +178,7 @@ class ActivityToActivityOverrideTests() { createOverride { closed { closeOverrideCalled = true - defaultClose().invoke(it) + defaultClosed(it) } preClosed { preCloseCalled = true } } diff --git a/enro/src/androidTest/java/nav/enro/core/overrides/ActivityToFragmentOverrideTests.kt b/enro/src/androidTest/java/nav/enro/core/overrides/ActivityToFragmentOverrideTests.kt index a2af1b64..5e0288b8 100644 --- a/enro/src/androidTest/java/nav/enro/core/overrides/ActivityToFragmentOverrideTests.kt +++ b/enro/src/androidTest/java/nav/enro/core/overrides/ActivityToFragmentOverrideTests.kt @@ -21,7 +21,7 @@ class ActivityToFragmentOverrideTests() { postOpened { postOpenCalled = true } opened { openCalled = true - defaultOpen().invoke(it) + defaultOpened(it) } } ) @@ -46,7 +46,7 @@ class ActivityToFragmentOverrideTests() { preClosed { preCloseCalled = true } closed { closeOverrideCalled = true - defaultClose().invoke(it) + defaultClosed(it) } } ) @@ -76,7 +76,7 @@ class ActivityToFragmentOverrideTests() { postOpened { postOpenCalled = true } opened { openCalled = true - defaultOpen().invoke(it) + defaultOpened(it) } } ) @@ -109,7 +109,7 @@ class ActivityToFragmentOverrideTests() { preClosed { preCloseCalled = true } closed { closeOverrideCalled = true - defaultClose().invoke(it) + defaultClosed(it) } } ) @@ -148,7 +148,7 @@ class ActivityToFragmentOverrideTests() { postOpened { postOpenCalled = true } opened { openCalled = true - defaultOpen().invoke(it) + defaultOpened(it) } } ) @@ -181,7 +181,7 @@ class ActivityToFragmentOverrideTests() { preClosed { preCloseCalled = true } closed { closeOverrideCalled = true - defaultClose().invoke(it) + defaultClosed(it) } } ) diff --git a/enro/src/androidTest/java/nav/enro/core/overrides/FragmentToActivityOverrideTests.kt b/enro/src/androidTest/java/nav/enro/core/overrides/FragmentToActivityOverrideTests.kt index fbeef0f1..3f1c36d5 100644 --- a/enro/src/androidTest/java/nav/enro/core/overrides/FragmentToActivityOverrideTests.kt +++ b/enro/src/androidTest/java/nav/enro/core/overrides/FragmentToActivityOverrideTests.kt @@ -7,7 +7,6 @@ import nav.enro.* import nav.enro.core.* import nav.enro.core.navigationController import nav.enro.core.createOverride -import nav.enro.core.defaultOpen import org.junit.Before import org.junit.Test @@ -40,7 +39,7 @@ class FragmentToActivityOverrideTests() { postOpened { postOpenCalled = true } opened { openCalled = true - defaultOpen().invoke(it) + defaultOpened(it) } } ) @@ -69,7 +68,7 @@ class FragmentToActivityOverrideTests() { preClosed { preCloseCalled = true} closed { closeOverrideCalled = true - defaultClose().invoke(it) + defaultClosed(it) } } ) @@ -104,7 +103,7 @@ class FragmentToActivityOverrideTests() { postOpened { postOpenCalled = true } opened { openCalled = true - defaultOpen().invoke(it) + defaultOpened(it) } } ) @@ -133,7 +132,7 @@ class FragmentToActivityOverrideTests() { preClosed { preCloseCalled = true } closed { closeOverrideCalled = true - defaultClose().invoke(it) + defaultClosed(it) } } ) diff --git a/enro/src/androidTest/java/nav/enro/core/overrides/FragmentToFragmentOverrideTests.kt b/enro/src/androidTest/java/nav/enro/core/overrides/FragmentToFragmentOverrideTests.kt index 4fde7ccd..f201f9de 100644 --- a/enro/src/androidTest/java/nav/enro/core/overrides/FragmentToFragmentOverrideTests.kt +++ b/enro/src/androidTest/java/nav/enro/core/overrides/FragmentToFragmentOverrideTests.kt @@ -7,7 +7,6 @@ import nav.enro.* import nav.enro.core.* import nav.enro.core.navigationController import nav.enro.core.createOverride -import nav.enro.core.defaultOpen import org.junit.Before import org.junit.Test @@ -40,7 +39,7 @@ class FragmentToFragmentOverrideTests() { postOpened { postOpenCalled = true } opened { openCalled = true - defaultOpen().invoke(it) + defaultOpened(it) } } ) @@ -68,7 +67,7 @@ class FragmentToFragmentOverrideTests() { preClosed { preCloseCalled = true } closed { closeOverrideCalled = true - defaultClose().invoke(it) + defaultClosed(it) } } ) @@ -103,7 +102,7 @@ class FragmentToFragmentOverrideTests() { postOpened { postOpenCalled = true } opened { openCalled = true - defaultOpen().invoke(it) + defaultOpened(it) } } ) @@ -131,7 +130,7 @@ class FragmentToFragmentOverrideTests() { preClosed { preCloseCalled = true } closed { closeOverrideCalled = true - defaultClose().invoke(it) + defaultClosed(it) } } ) @@ -166,7 +165,7 @@ class FragmentToFragmentOverrideTests() { postOpened { postOpenCalled = true } opened { openCalled = true - defaultOpen().invoke(it) + defaultOpened(it) } } ) @@ -194,7 +193,7 @@ class FragmentToFragmentOverrideTests() { preClosed { preCloseCalled = true } closed { closeOverrideCalled = true - defaultClose().invoke(it) + defaultClosed(it) } } ) diff --git a/example/src/main/java/nav/enro/example/SplashScreen.kt b/example/src/main/java/nav/enro/example/SplashScreen.kt index 6875233e..97040db2 100644 --- a/example/src/main/java/nav/enro/example/SplashScreen.kt +++ b/example/src/main/java/nav/enro/example/SplashScreen.kt @@ -26,9 +26,6 @@ class SplashScreenActivity : AppCompatActivity() { override fun onResume() { super.onResume() - navigation.executeInstruction(NavigationInstruction.Open( - navigationDirection = NavigationDirection.REPLACE_ROOT, - navigationKey = MainKey() - )) + navigation.replaceRoot(MainKey()) } } \ No newline at end of file From e2a48ff5073c61687e35b53b21c8b62dbabbebc1 Mon Sep 17 00:00:00 2001 From: Isaac Date: Sat, 26 Dec 2020 22:06:05 +1300 Subject: [PATCH 16/24] Remove todo message --- enro-core/src/main/java/nav/enro/core/NavigationContext.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/enro-core/src/main/java/nav/enro/core/NavigationContext.kt b/enro-core/src/main/java/nav/enro/core/NavigationContext.kt index 7e850dd3..11e9fb60 100644 --- a/enro-core/src/main/java/nav/enro/core/NavigationContext.kt +++ b/enro-core/src/main/java/nav/enro/core/NavigationContext.kt @@ -85,7 +85,6 @@ fun NavigationContext<*>.leafContext(): NavigationContext<*> { return childContext.leafContext() } -// TODO - Move to sit with NavigationHandleViewModel? internal fun NavigationContext<*>.getNavigationHandleViewModel(): NavigationHandleViewModel { return when (this) { is FragmentContext -> fragment.getNavigationHandle() From 2199ad94354bea3b75ec5422bf869c025bb4d378 Mon Sep 17 00:00:00 2001 From: Isaac Date: Sun, 27 Dec 2020 15:23:04 +1300 Subject: [PATCH 17/24] Split the NavigationController logic into several other objects --- build.gradle | 2 +- common.gradle | 4 + .../nav/enro/core/NavigationAnimations.kt | 1 + .../java/nav/enro/core/NavigationContext.kt | 5 + .../nav/enro/core/NavigationHandleProperty.kt | 13 +- .../nav/enro/core/NavigationInstruction.kt | 3 - .../core/activity/DefaultActivityExecutor.kt | 2 +- .../NavigationHandleActivityBinder.kt | 63 ---- .../{ => controller}/NavigationApplication.kt | 2 +- .../controller/NavigationComponentBuilder.kt | 34 ++- .../core/controller/NavigationController.kt | 286 +++--------------- .../controller/container/ExecutorContainer.kt | 122 ++++++++ .../container/NavigatorContainer.kt | 57 ++++ .../controller/container/PluginContainer.kt | 29 ++ .../InstructionInterceptorController.kt | 21 ++ .../InstructionParentInterceptor.kt | 61 ++++ .../NavigationInstructionInterceptor.kt | 14 + .../NavigationContextLifecycleCallbacks.kt | 72 +++++ .../NavigationLifecycleController.kt | 98 ++++++ .../NavigationHandleFragmentBinder.kt | 68 ----- .../handle/NavigationHandleViewModel.kt | 15 +- .../masterdetail/MasterDetailComponent.kt | 2 +- .../MultistackControllerFragment.kt | 1 + .../java/nav/enro/TestApplication.kt | 29 +- .../ActivityToActivityOverrideTests.kt | 2 +- .../ActivityToFragmentOverrideTests.kt | 1 + .../FragmentToActivityOverrideTests.kt | 2 +- .../FragmentToFragmentOverrideTests.kt | 2 +- .../nav/enro/example/ExampleApplication.kt | 1 + 29 files changed, 581 insertions(+), 431 deletions(-) delete mode 100644 enro-core/src/main/java/nav/enro/core/activity/NavigationHandleActivityBinder.kt rename enro-core/src/main/java/nav/enro/core/{ => controller}/NavigationApplication.kt (89%) create mode 100644 enro-core/src/main/java/nav/enro/core/controller/container/ExecutorContainer.kt create mode 100644 enro-core/src/main/java/nav/enro/core/controller/container/NavigatorContainer.kt create mode 100644 enro-core/src/main/java/nav/enro/core/controller/container/PluginContainer.kt create mode 100644 enro-core/src/main/java/nav/enro/core/controller/interceptor/InstructionInterceptorController.kt create mode 100644 enro-core/src/main/java/nav/enro/core/controller/interceptor/InstructionParentInterceptor.kt create mode 100644 enro-core/src/main/java/nav/enro/core/controller/interceptor/NavigationInstructionInterceptor.kt create mode 100644 enro-core/src/main/java/nav/enro/core/controller/lifecycle/NavigationContextLifecycleCallbacks.kt create mode 100644 enro-core/src/main/java/nav/enro/core/controller/lifecycle/NavigationLifecycleController.kt delete mode 100644 enro-core/src/main/java/nav/enro/core/fragment/NavigationHandleFragmentBinder.kt diff --git a/build.gradle b/build.gradle index 0c937ddd..c4604e29 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,5 @@ buildscript { - ext.kotlin_version = "1.4.10" + ext.kotlin_version = "1.4.20" repositories { mavenLocal() google() diff --git a/common.gradle b/common.gradle index 303cc60c..c67e2060 100644 --- a/common.gradle +++ b/common.gradle @@ -51,6 +51,10 @@ ext.androidLibrary = { } } + kotlin { + explicitApi() + } + dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" } diff --git a/enro-core/src/main/java/nav/enro/core/NavigationAnimations.kt b/enro-core/src/main/java/nav/enro/core/NavigationAnimations.kt index cc124f78..4df5568f 100644 --- a/enro-core/src/main/java/nav/enro/core/NavigationAnimations.kt +++ b/enro-core/src/main/java/nav/enro/core/NavigationAnimations.kt @@ -6,6 +6,7 @@ import android.os.Parcelable import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentActivity import kotlinx.android.parcel.Parcelize +import nav.enro.core.controller.navigationController import nav.enro.core.internal.getAttributeResourceId sealed class AnimationPair : Parcelable{ diff --git a/enro-core/src/main/java/nav/enro/core/NavigationContext.kt b/enro-core/src/main/java/nav/enro/core/NavigationContext.kt index 11e9fb60..8e0093a4 100644 --- a/enro-core/src/main/java/nav/enro/core/NavigationContext.kt +++ b/enro-core/src/main/java/nav/enro/core/NavigationContext.kt @@ -1,5 +1,6 @@ package nav.enro.core +import android.os.Bundle import androidx.activity.viewModels import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentActivity @@ -9,6 +10,7 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.ViewModelProvider import nav.enro.core.activity.ActivityNavigator import nav.enro.core.controller.NavigationController +import nav.enro.core.controller.navigationController import nav.enro.core.fragment.FragmentNavigator import nav.enro.core.internal.handle.NavigationHandleViewModel @@ -18,6 +20,7 @@ sealed class NavigationContext( abstract val controller: NavigationController abstract val lifecycle: Lifecycle abstract val childFragmentManager: FragmentManager + abstract val arguments: Bundle internal open val navigator: Navigator<*, ContextType>? by lazy { controller.navigatorForContextType(contextReference::class) as? Navigator<*, ContextType> @@ -31,6 +34,7 @@ internal class ActivityContext( override val lifecycle get() = contextReference.lifecycle override val navigator get() = super.navigator as? ActivityNavigator<*, ContextType> override val childFragmentManager get() = contextReference.supportFragmentManager + override val arguments: Bundle by lazy { contextReference.intent.extras ?: Bundle() } } internal class FragmentContext( @@ -40,6 +44,7 @@ internal class FragmentContext( override val lifecycle get() = contextReference.lifecycle override val navigator get() = super.navigator as? FragmentNavigator<*, ContextType> override val childFragmentManager get() = contextReference.childFragmentManager + override val arguments: Bundle by lazy { contextReference.arguments ?: Bundle() } } val NavigationContext.fragment get() = contextReference diff --git a/enro-core/src/main/java/nav/enro/core/NavigationHandleProperty.kt b/enro-core/src/main/java/nav/enro/core/NavigationHandleProperty.kt index e1874611..c6728cb9 100644 --- a/enro-core/src/main/java/nav/enro/core/NavigationHandleProperty.kt +++ b/enro-core/src/main/java/nav/enro/core/NavigationHandleProperty.kt @@ -37,17 +37,10 @@ class NavigationHandleProperty @PublishedApi internal const companion object { internal val pendingProperties = mutableMapOf>>() - fun getPendingConfig(activity: FragmentActivity): NavigationHandleConfiguration<*>? { - val pending = pendingProperties[activity.hashCode()] ?: return null + fun getPendingConfig(navigationContext: NavigationContext<*>): NavigationHandleConfiguration<*>? { + val pending = pendingProperties[navigationContext.contextReference.hashCode()] ?: return null val config = pending.get()?.config - pendingProperties.remove(activity.hashCode()) - return config - } - - fun getPendingConfig(fragment: Fragment): NavigationHandleConfiguration<*>? { - val pending = pendingProperties[fragment.hashCode()] ?: return null - val config = pending.get()?.config - pendingProperties.remove(fragment.hashCode()) + pendingProperties.remove(navigationContext.contextReference.hashCode()) return config } } diff --git a/enro-core/src/main/java/nav/enro/core/NavigationInstruction.kt b/enro-core/src/main/java/nav/enro/core/NavigationInstruction.kt index 5eaa1496..a4b96d22 100644 --- a/enro-core/src/main/java/nav/enro/core/NavigationInstruction.kt +++ b/enro-core/src/main/java/nav/enro/core/NavigationInstruction.kt @@ -15,9 +15,6 @@ enum class NavigationDirection { internal const val OPEN_ARG = "nav.enro.core.OPEN_ARG" -// TODO Put this somewhere closer to where it's being used? -internal const val CONTEXT_ID_ARG = "nav.enro.core.CONTEXT_ID" - // TODO Hide some of these properties? sealed class NavigationInstruction { @Parcelize diff --git a/enro-core/src/main/java/nav/enro/core/activity/DefaultActivityExecutor.kt b/enro-core/src/main/java/nav/enro/core/activity/DefaultActivityExecutor.kt index 64534661..8c6b0aaf 100644 --- a/enro-core/src/main/java/nav/enro/core/activity/DefaultActivityExecutor.kt +++ b/enro-core/src/main/java/nav/enro/core/activity/DefaultActivityExecutor.kt @@ -40,7 +40,7 @@ object DefaultActivityExecutor : NavigationExecutor) { context.activity.supportFinishAfterTransition() - context.controller.navigatorForContextType(context.contextReference::class) ?: return + context.navigator ?: return val animations = animationsFor(context, NavigationInstruction.Close) context.activity.overridePendingTransition(animations.enter, animations.exit) diff --git a/enro-core/src/main/java/nav/enro/core/activity/NavigationHandleActivityBinder.kt b/enro-core/src/main/java/nav/enro/core/activity/NavigationHandleActivityBinder.kt deleted file mode 100644 index 9f0b81bf..00000000 --- a/enro-core/src/main/java/nav/enro/core/activity/NavigationHandleActivityBinder.kt +++ /dev/null @@ -1,63 +0,0 @@ -package nav.enro.core.activity - -import android.app.Activity -import android.app.Application -import android.os.Bundle -import android.view.ViewGroup -import androidx.fragment.app.FragmentActivity -import nav.enro.core.* -import nav.enro.core.ActivityContext -import nav.enro.core.leafContext -import nav.enro.core.navigationContext -import nav.enro.core.internal.NoNavigationKey -import nav.enro.core.internal.handle.createNavigationHandleViewModel -import java.util.* - -internal object NavigationHandleActivityBinder : Application.ActivityLifecycleCallbacks { - override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) { - if (activity !is FragmentActivity) return - - activity.theme.applyStyle(android.R.style.Animation_Activity, false) - - val instruction = activity.intent.extras?.readOpenInstruction() - val contextId = instruction?.instructionId - ?: savedInstanceState?.getString(CONTEXT_ID_ARG) - ?: UUID.randomUUID().toString() - - val config = NavigationHandleProperty.getPendingConfig(activity) - val defaultInstruction = NavigationInstruction.Open( - instructionId = contextId, - navigationDirection = NavigationDirection.FORWARD, - navigationKey = config?.defaultKey ?: NoNavigationKey(activity::class.java, activity.intent.extras) - ) - - val controller = activity.application.navigationController - val handle = activity.createNavigationHandleViewModel( - controller, - instruction ?: defaultInstruction - ) - config?.applyTo(handle) - - val context = ActivityContext(activity) - handle.navigationContext = ActivityContext(activity) - controller.onContextCreated(context, savedInstanceState) - if(savedInstanceState == null) handle.executeDeeplink() - activity.findViewById(android.R.id.content).viewTreeObserver.addOnGlobalLayoutListener { - activity.application.navigationController.active = activity.navigationContext.leafContext().getNavigationHandleViewModel() - } - } - - override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) { - if (activity !is FragmentActivity) return - outState.putString(CONTEXT_ID_ARG, activity.getNavigationHandle().id) - } - - override fun onActivityPaused(activity: Activity) {} - override fun onActivityStarted(activity: Activity) {} - override fun onActivityDestroyed(activity: Activity) {} - override fun onActivityStopped(activity: Activity) {} - override fun onActivityResumed(activity: Activity) { - if(activity !is FragmentActivity) return - activity.application.navigationController.active = activity.navigationContext.leafContext().getNavigationHandleViewModel() - } -} \ No newline at end of file diff --git a/enro-core/src/main/java/nav/enro/core/NavigationApplication.kt b/enro-core/src/main/java/nav/enro/core/controller/NavigationApplication.kt similarity index 89% rename from enro-core/src/main/java/nav/enro/core/NavigationApplication.kt rename to enro-core/src/main/java/nav/enro/core/controller/NavigationApplication.kt index c4f4f208..f7f2a36b 100644 --- a/enro-core/src/main/java/nav/enro/core/NavigationApplication.kt +++ b/enro-core/src/main/java/nav/enro/core/controller/NavigationApplication.kt @@ -1,4 +1,4 @@ -package nav.enro.core +package nav.enro.core.controller import android.app.Application import nav.enro.core.controller.NavigationController diff --git a/enro-core/src/main/java/nav/enro/core/controller/NavigationComponentBuilder.kt b/enro-core/src/main/java/nav/enro/core/controller/NavigationComponentBuilder.kt index 266c70f8..c269c069 100644 --- a/enro-core/src/main/java/nav/enro/core/controller/NavigationComponentBuilder.kt +++ b/enro-core/src/main/java/nav/enro/core/controller/NavigationComponentBuilder.kt @@ -1,6 +1,13 @@ package nav.enro.core.controller import nav.enro.core.* +import nav.enro.core.controller.container.ExecutorContainer +import nav.enro.core.controller.container.NavigatorContainer +import nav.enro.core.controller.container.PluginContainer +import nav.enro.core.controller.interceptor.InstructionInterceptorController +import nav.enro.core.controller.interceptor.InstructionParentInterceptor +import nav.enro.core.controller.lifecycle.NavigationLifecycleController +import nav.enro.core.plugins.EnroHilt import nav.enro.core.plugins.EnroPlugin // TODO get rid of this, or give it a better name @@ -40,7 +47,28 @@ class NavigationComponentBuilder { plugins.add(enroPlugin) } - internal fun build() = NavigationController(navigators, overrides, plugins) + internal fun build(): NavigationController { + val useHilt = plugins.any { it is EnroHilt } + + val pluginContainer = PluginContainer(plugins) + val navigatorContainer = NavigatorContainer(navigators, useHilt) + val executorContainer = ExecutorContainer(overrides) + + val interceptorController = InstructionInterceptorController( + listOf( + InstructionParentInterceptor(navigatorContainer) + ) + ) + val contextController = NavigationLifecycleController(executorContainer, pluginContainer) + + return NavigationController( + pluginContainer = pluginContainer, + navigatorContainer = navigatorContainer, + executorContainer = executorContainer, + interceptorController = interceptorController, + contextController = contextController, + ) + } } /** @@ -52,9 +80,7 @@ fun NavigationApplication.navigationController(block: NavigationComponentBuilder .apply { generatedComponent?.execute(this) } .apply(block) .build() - .apply { - NavigationController.install(this@navigationController) - } + .apply { install(this@navigationController) } } private val NavigationApplication.generatedComponent get(): NavigationComponentBuilderCommand? = diff --git a/enro-core/src/main/java/nav/enro/core/controller/NavigationController.kt b/enro-core/src/main/java/nav/enro/core/controller/NavigationController.kt index 71cb4036..a2a17841 100644 --- a/enro-core/src/main/java/nav/enro/core/controller/NavigationController.kt +++ b/enro-core/src/main/java/nav/enro/core/controller/NavigationController.kt @@ -1,87 +1,21 @@ package nav.enro.core.controller import android.app.Application -import android.os.Bundle -import android.util.Log -import androidx.fragment.app.Fragment -import androidx.fragment.app.FragmentActivity import nav.enro.core.* -import nav.enro.core.activity.ActivityNavigator -import nav.enro.core.activity.DefaultActivityExecutor -import nav.enro.core.activity.NavigationHandleActivityBinder -import nav.enro.core.activity.createActivityNavigator -import nav.enro.core.fragment.DefaultFragmentExecutor -import nav.enro.core.fragment.FragmentNavigator -import nav.enro.core.fragment.NavigationHandleFragmentBinder -import nav.enro.core.fragment.internal.HiltSingleFragmentActivity -import nav.enro.core.fragment.internal.SingleFragmentActivity -import nav.enro.core.fragment.internal.SingleFragmentKey -import nav.enro.core.internal.NoKeyNavigator -import nav.enro.core.internal.NoNavigationKey -import nav.enro.core.internal.handle.NavigationHandleViewModel -import nav.enro.core.plugins.EnroHilt -import nav.enro.core.plugins.EnroPlugin -import nav.enro.core.synthetic.SyntheticDestination -import nav.enro.core.synthetic.DefaultSyntheticExecutor +import nav.enro.core.controller.container.ExecutorContainer +import nav.enro.core.controller.container.NavigatorContainer +import nav.enro.core.controller.container.PluginContainer +import nav.enro.core.controller.interceptor.InstructionInterceptorController +import nav.enro.core.controller.lifecycle.NavigationLifecycleController import kotlin.reflect.KClass -// TODO split functionality out into more focused classes (e.g. OverrideController or similar) -// TODO has too many sideways dependencies -class NavigationController( - navigators: List>, - overrides: List> = listOf(), - private val plugins: List = listOf() +class NavigationController internal constructor( + private val pluginContainer: PluginContainer, + private val navigatorContainer: NavigatorContainer, + private val executorContainer: ExecutorContainer, + private val interceptorController: InstructionInterceptorController, + private val contextController: NavigationLifecycleController, ) { - internal var active: NavigationHandle? = null - set(value) { - if(value == field) return - field = value - if(value != null) { - if(value is NavigationHandleViewModel && !value.hasKey) { - field = null - return - } - plugins.forEach { it.onActive(value) } - } - } - - private val defaultNavigators = run { - val useHilt = plugins.any { it is EnroHilt } - val singleFragmentNavigator = if(useHilt) { - createActivityNavigator() - } - else { - createActivityNavigator() - } - - val noKeyProvidedNavigator = NoKeyNavigator() - - listOf( - singleFragmentNavigator, - noKeyProvidedNavigator - ) - } - - private val navigatorsByKeyType = (navigators + defaultNavigators) - .map { - it.keyType to it - } - .toMap() - - private val navigatorsByContextType = (navigators + defaultNavigators) - .map { - it.contextType to it - } - .toMap() - - private val overrides = overrides.map { (it.fromType to it.opensType) to it }.toMap() - private val temporaryOverrides = mutableMapOf, KClass>, NavigationExecutor<*, *, *>>() - - internal val handles = mutableMapOf() - - init { - plugins.forEach { it.onAttached(this) } - } internal fun open( navigationContext: NavigationContext, @@ -90,15 +24,22 @@ class NavigationController( val navigator = navigatorForKeyType(instruction.navigationKey::class) ?: throw IllegalStateException("Attempted to execute $instruction but could not find a valid navigator for the key type on this instruction") - val executor = executorForOpen(navigationContext, instruction) + val executor = executorContainer.executorForOpen(navigationContext, navigator) + + val processedInstruction = interceptorController.intercept( + instruction, executor.context, navigator + ) + + if (processedInstruction.navigationKey::class != navigator.keyType) { + open(navigationContext, processedInstruction) + return + } val args = ExecutorArgs( executor.context, navigator, - instruction.navigationKey, - instruction - .setParentInstruction(executor.context, navigator) - .setParentContext(executor.context) + processedInstruction.navigationKey, + processedInstruction ) executor.executor.preOpened(executor.context) @@ -108,190 +49,49 @@ class NavigationController( internal fun close( navigationContext: NavigationContext ) { - val executor = executorForClose(navigationContext) + val executor = executorContainer.executorForClose(navigationContext) executor.preClosed(navigationContext) executor.close(navigationContext) } - internal fun onOpened(navigationHandle: NavigationHandle) { - plugins.forEach { it.onOpened(navigationHandle) } - } - - internal fun onClosed(navigationHandle: NavigationHandle) { - plugins.forEach { it.onClosed(navigationHandle) } - } - - internal fun onContextCreated(navigationContext: NavigationContext, savedInstanceState: Bundle?) { - if(savedInstanceState == null) { - executorForClose(navigationContext).postOpened(navigationContext) - } - } - fun navigatorForContextType( contextType: KClass<*> ): Navigator<*, *>? { - return navigatorsByContextType[contextType] + return navigatorContainer.navigatorForContextType(contextType) } fun navigatorForKeyType( keyType: KClass ): Navigator<*, *>? { - return navigatorsByKeyType[keyType] - } - - private fun overrideFor(types: Pair, KClass>): NavigationExecutor? { - return temporaryOverrides[types] ?: overrides[types] - } - - internal fun executorForOpen(fromContext: NavigationContext, instruction: NavigationInstruction.Open): OpenExecutorPair { - val navigator = navigatorForKeyType(instruction.navigationKey::class) - ?: throw IllegalStateException("Attempted to find executor for $instruction but could not find a valid navigator for the key type on this instruction") - - val opensContext = navigator.contextType - val opensContextIsActivity by lazy { - FragmentActivity::class.java.isAssignableFrom(opensContext.java) - } - - val opensContextIsFragment by lazy { - Fragment::class.java.isAssignableFrom(opensContext.java) - } - - val opensContextIsSynthetic by lazy { - SyntheticDestination::class.java.isAssignableFrom(opensContext.java) - } - - fun getOverrideExecutor(overrideContext: NavigationContext): OpenExecutorPair? { - val override = overrideFor(overrideContext.contextReference::class to opensContext) - ?: when (overrideContext.contextReference) { - is FragmentActivity -> overrideFor(FragmentActivity::class to opensContext) - is Fragment -> overrideFor(Fragment::class to opensContext) - else -> null - } - ?: overrideFor(Any::class to opensContext) - ?: when { - opensContextIsActivity -> overrideFor(overrideContext.contextReference::class to FragmentActivity::class) - opensContextIsFragment -> overrideFor(overrideContext.contextReference::class to Fragment::class) - else -> null - } - ?: overrideFor(overrideContext.contextReference::class to Any::class) - - val parentContext = overrideContext.parentContext() - return when { - override != null -> OpenExecutorPair(overrideContext, override) - parentContext != null -> getOverrideExecutor(parentContext) - else -> null - } - } - - val override = getOverrideExecutor(fromContext) - return override ?: when { - opensContextIsActivity -> OpenExecutorPair(fromContext, DefaultActivityExecutor) - opensContextIsFragment -> OpenExecutorPair(fromContext, DefaultFragmentExecutor) - opensContextIsSynthetic -> OpenExecutorPair(fromContext, DefaultSyntheticExecutor) - else -> throw IllegalStateException() - } + return navigatorContainer.navigatorForKeyType(keyType) } - @Suppress("UNCHECKED_CAST") - internal fun executorForClose(navigationContext: NavigationContext): NavigationExecutor { - val parentContextType = navigationContext.getNavigationHandleViewModel().instruction.parentContext?.kotlin - val contextType = navigationContext.contextReference::class - - val override = parentContextType?.let { parentContext -> - val parentContextIsActivity by lazy { - FragmentActivity::class.java.isAssignableFrom(parentContext.java) - } - - val parentContextIsFragment by lazy { - Fragment::class.java.isAssignableFrom(parentContext.java) - } - - overrideFor(parentContext to contextType) - ?: when { - parentContextIsActivity -> overrideFor(FragmentActivity::class to contextType) - parentContextIsFragment -> overrideFor(Fragment::class to contextType) - else -> null - } - ?: overrideFor(Any::class to contextType) - ?: when(navigationContext) { - is ActivityContext -> overrideFor(parentContext to FragmentActivity::class) - is FragmentContext -> overrideFor(parentContext to Fragment::class) - } - ?: overrideFor(parentContext to Any::class) - } as? NavigationExecutor - - return override ?: when (navigationContext) { - is ActivityContext -> DefaultActivityExecutor as NavigationExecutor - is FragmentContext -> DefaultFragmentExecutor as NavigationExecutor - } - } - - private fun NavigationInstruction.Open.setParentInstruction( - parentContext: NavigationContext<*>, - navigator: Navigator - ): NavigationInstruction.Open { - if (parentInstruction != null) return this - - fun findCorrectParentInstructionFor(instruction: NavigationInstruction.Open?): NavigationInstruction.Open? { - if (navigator is FragmentNavigator) { - return instruction - } - - if (instruction == null) return null - val keyType = instruction.navigationKey::class - val parentNavigator = navigatorForKeyType(keyType) - if (parentNavigator is ActivityNavigator) return instruction - if (parentNavigator is NoKeyNavigator) return instruction - return findCorrectParentInstructionFor(instruction.parentInstruction) - } - - val parentInstruction = when (navigationDirection) { - NavigationDirection.FORWARD -> findCorrectParentInstructionFor(parentContext.getNavigationHandleViewModel().instruction) - NavigationDirection.REPLACE -> findCorrectParentInstructionFor(parentContext.getNavigationHandleViewModel().instruction)?.parentInstruction - NavigationDirection.REPLACE_ROOT -> null - } - - return copy(parentInstruction = parentInstruction) - } + internal fun executorForOpen( + fromContext: NavigationContext<*>, + instruction: NavigationInstruction.Open + ) = executorContainer.executorForOpen( + fromContext, + navigatorForKeyType(instruction.navigationKey::class) ?: throw IllegalStateException() + ) - private fun NavigationInstruction.Open.setParentContext( - parentContext: NavigationContext<*> - ): NavigationInstruction.Open { - if(parentContext.contextReference is SingleFragmentActivity) { - return copy(parentContext = parentContext.getNavigationHandleViewModel().instruction.parentContext) - } - return copy(parentContext = parentContext.contextReference::class.java) - } + internal fun executorForClose(navigationContext: NavigationContext<*>) = + executorContainer.executorForClose(navigationContext) fun addOverride(navigationExecutor: NavigationExecutor<*, *, *>) { - temporaryOverrides[navigationExecutor.fromType to navigationExecutor.opensType] = navigationExecutor + executorContainer.addOverride(navigationExecutor) } fun removeOverride(navigationExecutor: NavigationExecutor<*, *, *>) { - temporaryOverrides.remove(navigationExecutor.fromType to navigationExecutor.opensType) + executorContainer.removeOverride(navigationExecutor) } - companion object { - fun install(navigationApplication: NavigationApplication) { - if (navigationApplication !is Application) - throw IllegalArgumentException("A NavigationApplication must extend android.app.Application") + fun install(navigationApplication: NavigationApplication) { + if (navigationApplication !is Application) + throw IllegalArgumentException("A NavigationApplication must extend android.app.Application") - navigationApplication.registerActivityLifecycleCallbacks( - NavigationHandleActivityBinder - ) + if(navigationApplication.navigationController != this) + throw IllegalArgumentException("A NavigationController can only be installed on a NavigationApplication that returns that NavigationController as its navigationController property") - navigationApplication.registerActivityLifecycleCallbacks( - NavigationHandleFragmentBinder - ) - } + contextController.install(navigationApplication) } -} - -@Suppress("UNCHECKED_CAST") -class OpenExecutorPair( - context: NavigationContext, - executor: NavigationExecutor -) { - val context = context as NavigationContext - val executor = executor as NavigationExecutor } \ No newline at end of file diff --git a/enro-core/src/main/java/nav/enro/core/controller/container/ExecutorContainer.kt b/enro-core/src/main/java/nav/enro/core/controller/container/ExecutorContainer.kt new file mode 100644 index 00000000..956507c4 --- /dev/null +++ b/enro-core/src/main/java/nav/enro/core/controller/container/ExecutorContainer.kt @@ -0,0 +1,122 @@ +package nav.enro.core.controller.container + +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentActivity +import nav.enro.core.* +import nav.enro.core.ActivityContext +import nav.enro.core.FragmentContext +import nav.enro.core.activity.DefaultActivityExecutor +import nav.enro.core.controller.NavigationController +import nav.enro.core.fragment.DefaultFragmentExecutor +import nav.enro.core.getNavigationHandleViewModel +import nav.enro.core.synthetic.DefaultSyntheticExecutor +import nav.enro.core.synthetic.SyntheticDestination +import kotlin.reflect.KClass + +internal class ExecutorContainer( + overrides: List> +) { + private val overrides = overrides.map { (it.fromType to it.opensType) to it }.toMap() + private val temporaryOverrides = mutableMapOf, KClass>, NavigationExecutor<*, *, *>>() + + fun addOverride(navigationExecutor: NavigationExecutor<*, *, *>) { + temporaryOverrides[navigationExecutor.fromType to navigationExecutor.opensType] = navigationExecutor + } + + fun removeOverride(navigationExecutor: NavigationExecutor<*, *, *>) { + temporaryOverrides.remove(navigationExecutor.fromType to navigationExecutor.opensType) + } + + private fun overrideFor(types: Pair, KClass>): NavigationExecutor? { + return temporaryOverrides[types] ?: overrides[types] + } + + internal fun executorForOpen(fromContext: NavigationContext, navigator: Navigator<*, *>): OpenExecutorPair { + val opensContext = navigator.contextType + val opensContextIsActivity by lazy { + FragmentActivity::class.java.isAssignableFrom(opensContext.java) + } + + val opensContextIsFragment by lazy { + Fragment::class.java.isAssignableFrom(opensContext.java) + } + + val opensContextIsSynthetic by lazy { + SyntheticDestination::class.java.isAssignableFrom(opensContext.java) + } + + fun getOverrideExecutor(overrideContext: NavigationContext): OpenExecutorPair? { + val override = overrideFor(overrideContext.contextReference::class to opensContext) + ?: when (overrideContext.contextReference) { + is FragmentActivity -> overrideFor(FragmentActivity::class to opensContext) + is Fragment -> overrideFor(Fragment::class to opensContext) + else -> null + } + ?: overrideFor(Any::class to opensContext) + ?: when { + opensContextIsActivity -> overrideFor(overrideContext.contextReference::class to FragmentActivity::class) + opensContextIsFragment -> overrideFor(overrideContext.contextReference::class to Fragment::class) + else -> null + } + ?: overrideFor(overrideContext.contextReference::class to Any::class) + + val parentContext = overrideContext.parentContext() + return when { + override != null -> OpenExecutorPair(overrideContext, override) + parentContext != null -> getOverrideExecutor(parentContext) + else -> null + } + } + + val override = getOverrideExecutor(fromContext) + return override ?: when { + opensContextIsActivity -> OpenExecutorPair(fromContext, DefaultActivityExecutor) + opensContextIsFragment -> OpenExecutorPair(fromContext, DefaultFragmentExecutor) + opensContextIsSynthetic -> OpenExecutorPair(fromContext, DefaultSyntheticExecutor) + else -> throw IllegalStateException() + } + } + + @Suppress("UNCHECKED_CAST") + internal fun executorForClose(navigationContext: NavigationContext): NavigationExecutor { + val parentContextType = navigationContext.getNavigationHandleViewModel().instruction.parentContext?.kotlin + val contextType = navigationContext.contextReference::class + + val override = parentContextType?.let { parentContext -> + val parentContextIsActivity by lazy { + FragmentActivity::class.java.isAssignableFrom(parentContext.java) + } + + val parentContextIsFragment by lazy { + Fragment::class.java.isAssignableFrom(parentContext.java) + } + + overrideFor(parentContext to contextType) + ?: when { + parentContextIsActivity -> overrideFor(FragmentActivity::class to contextType) + parentContextIsFragment -> overrideFor(Fragment::class to contextType) + else -> null + } + ?: overrideFor(Any::class to contextType) + ?: when(navigationContext) { + is ActivityContext -> overrideFor(parentContext to FragmentActivity::class) + is FragmentContext -> overrideFor(parentContext to Fragment::class) + } + ?: overrideFor(parentContext to Any::class) + } as? NavigationExecutor + + return override ?: when (navigationContext) { + is ActivityContext -> DefaultActivityExecutor as NavigationExecutor + is FragmentContext -> DefaultFragmentExecutor as NavigationExecutor + } + } +} + +@Suppress("UNCHECKED_CAST") +class OpenExecutorPair( + context: NavigationContext, + executor: NavigationExecutor +) { + val context = context as NavigationContext + val executor = executor as NavigationExecutor +} \ No newline at end of file diff --git a/enro-core/src/main/java/nav/enro/core/controller/container/NavigatorContainer.kt b/enro-core/src/main/java/nav/enro/core/controller/container/NavigatorContainer.kt new file mode 100644 index 00000000..4a0d752b --- /dev/null +++ b/enro-core/src/main/java/nav/enro/core/controller/container/NavigatorContainer.kt @@ -0,0 +1,57 @@ +package nav.enro.core.controller.container + +import nav.enro.core.NavigationKey +import nav.enro.core.Navigator +import nav.enro.core.activity.createActivityNavigator +import nav.enro.core.controller.NavigationController +import nav.enro.core.fragment.internal.HiltSingleFragmentActivity +import nav.enro.core.fragment.internal.SingleFragmentActivity +import nav.enro.core.fragment.internal.SingleFragmentKey +import nav.enro.core.internal.NoKeyNavigator +import nav.enro.core.plugins.EnroHilt +import kotlin.reflect.KClass + +internal class NavigatorContainer ( + private val navigators: List>, + private val useHiltDefaults: Boolean, +) { + private val defaultNavigators = run { + val singleFragmentNavigator = if(useHiltDefaults) { + createActivityNavigator() + } + else { + createActivityNavigator() + } + + val noKeyProvidedNavigator = NoKeyNavigator() + + listOf( + singleFragmentNavigator, + noKeyProvidedNavigator + ) + } + + private val navigatorsByKeyType = (navigators + defaultNavigators) + .map { + it.keyType to it + } + .toMap() + + private val navigatorsByContextType = (navigators + defaultNavigators) + .map { + it.contextType to it + } + .toMap() + + fun navigatorForContextType( + contextType: KClass<*> + ): Navigator<*, *>? { + return navigatorsByContextType[contextType] + } + + fun navigatorForKeyType( + keyType: KClass + ): Navigator<*, *>? { + return navigatorsByKeyType[keyType] + } +} \ No newline at end of file diff --git a/enro-core/src/main/java/nav/enro/core/controller/container/PluginContainer.kt b/enro-core/src/main/java/nav/enro/core/controller/container/PluginContainer.kt new file mode 100644 index 00000000..1dc923ee --- /dev/null +++ b/enro-core/src/main/java/nav/enro/core/controller/container/PluginContainer.kt @@ -0,0 +1,29 @@ +package nav.enro.core.controller.container + +import nav.enro.core.NavigationHandle +import nav.enro.core.controller.NavigationController +import nav.enro.core.plugins.EnroPlugin + +internal class PluginContainer( + private val plugins: List = listOf() +) { + fun hasPlugin(block: (EnroPlugin) -> Boolean): Boolean { + return plugins.any(block) + } + + internal fun onAttached(navigationController: NavigationController) { + plugins.forEach { it.onAttached(navigationController) } + } + + internal fun onOpened(navigationHandle: NavigationHandle) { + plugins.forEach { it.onOpened(navigationHandle) } + } + + internal fun onActive(navigationHandle: NavigationHandle) { + plugins.forEach { it.onActive(navigationHandle) } + } + + internal fun onClosed(navigationHandle: NavigationHandle) { + plugins.forEach { it.onClosed(navigationHandle) } + } +} \ No newline at end of file diff --git a/enro-core/src/main/java/nav/enro/core/controller/interceptor/InstructionInterceptorController.kt b/enro-core/src/main/java/nav/enro/core/controller/interceptor/InstructionInterceptorController.kt new file mode 100644 index 00000000..8abdba7d --- /dev/null +++ b/enro-core/src/main/java/nav/enro/core/controller/interceptor/InstructionInterceptorController.kt @@ -0,0 +1,21 @@ +package nav.enro.core.controller.interceptor + +import nav.enro.core.NavigationContext +import nav.enro.core.NavigationInstruction +import nav.enro.core.NavigationKey +import nav.enro.core.Navigator + +class InstructionInterceptorController( + private val interceptors: List +) { + + fun intercept( + instruction: NavigationInstruction.Open, + parentContext: NavigationContext<*>, + navigator: Navigator + ): NavigationInstruction.Open { + return interceptors.fold(instruction) { acc, interceptor -> + interceptor.intercept(acc, parentContext, navigator) + } + } +} \ No newline at end of file diff --git a/enro-core/src/main/java/nav/enro/core/controller/interceptor/InstructionParentInterceptor.kt b/enro-core/src/main/java/nav/enro/core/controller/interceptor/InstructionParentInterceptor.kt new file mode 100644 index 00000000..68ca6963 --- /dev/null +++ b/enro-core/src/main/java/nav/enro/core/controller/interceptor/InstructionParentInterceptor.kt @@ -0,0 +1,61 @@ +package nav.enro.core.controller.interceptor + +import nav.enro.core.* +import nav.enro.core.activity.ActivityNavigator +import nav.enro.core.controller.container.NavigatorContainer +import nav.enro.core.fragment.FragmentNavigator +import nav.enro.core.fragment.internal.SingleFragmentActivity +import nav.enro.core.internal.NoKeyNavigator + +internal class InstructionParentInterceptor( + private val navigatorContainer: NavigatorContainer +) : NavigationInstructionInterceptor{ + + override fun intercept( + instruction: NavigationInstruction.Open, + parentContext: NavigationContext<*>, + navigator: Navigator + ): NavigationInstruction.Open { + return instruction + .setParentInstruction(parentContext, navigator) + .setParentContext(parentContext) + } + + + private fun NavigationInstruction.Open.setParentInstruction( + parentContext: NavigationContext<*>, + navigator: Navigator + ): NavigationInstruction.Open { + if (parentInstruction != null) return this + + fun findCorrectParentInstructionFor(instruction: NavigationInstruction.Open?): NavigationInstruction.Open? { + if (navigator is FragmentNavigator) { + return instruction + } + + if (instruction == null) return null + val keyType = instruction.navigationKey::class + val parentNavigator = navigatorContainer.navigatorForKeyType(keyType) + if (parentNavigator is ActivityNavigator) return instruction + if (parentNavigator is NoKeyNavigator) return instruction + return findCorrectParentInstructionFor(instruction.parentInstruction) + } + + val parentInstruction = when (navigationDirection) { + NavigationDirection.FORWARD -> findCorrectParentInstructionFor(parentContext.getNavigationHandleViewModel().instruction) + NavigationDirection.REPLACE -> findCorrectParentInstructionFor(parentContext.getNavigationHandleViewModel().instruction)?.parentInstruction + NavigationDirection.REPLACE_ROOT -> null + } + + return copy(parentInstruction = parentInstruction) + } + + private fun NavigationInstruction.Open.setParentContext( + parentContext: NavigationContext<*> + ): NavigationInstruction.Open { + if(parentContext.contextReference is SingleFragmentActivity) { + return copy(parentContext = parentContext.getNavigationHandleViewModel().instruction.parentContext) + } + return copy(parentContext = parentContext.contextReference::class.java) + } +} \ No newline at end of file diff --git a/enro-core/src/main/java/nav/enro/core/controller/interceptor/NavigationInstructionInterceptor.kt b/enro-core/src/main/java/nav/enro/core/controller/interceptor/NavigationInstructionInterceptor.kt new file mode 100644 index 00000000..47b2a8b6 --- /dev/null +++ b/enro-core/src/main/java/nav/enro/core/controller/interceptor/NavigationInstructionInterceptor.kt @@ -0,0 +1,14 @@ +package nav.enro.core.controller.interceptor + +import nav.enro.core.NavigationContext +import nav.enro.core.NavigationInstruction +import nav.enro.core.NavigationKey +import nav.enro.core.Navigator + +interface NavigationInstructionInterceptor { + fun intercept( + instruction: NavigationInstruction.Open, + parentContext: NavigationContext<*>, + navigator: Navigator + ): NavigationInstruction.Open +} \ No newline at end of file diff --git a/enro-core/src/main/java/nav/enro/core/controller/lifecycle/NavigationContextLifecycleCallbacks.kt b/enro-core/src/main/java/nav/enro/core/controller/lifecycle/NavigationContextLifecycleCallbacks.kt new file mode 100644 index 00000000..6b41daea --- /dev/null +++ b/enro-core/src/main/java/nav/enro/core/controller/lifecycle/NavigationContextLifecycleCallbacks.kt @@ -0,0 +1,72 @@ +package nav.enro.core.controller.lifecycle + +import android.app.Activity +import android.app.Application +import android.os.Bundle +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentActivity +import androidx.fragment.app.FragmentManager +import nav.enro.core.* + +internal class NavigationContextLifecycleCallbacks ( + private val lifecycleController: NavigationLifecycleController +) { + + private val fragmentCallbacks = FragmentCallbacks() + private val activityCallbacks = ActivityCallbacks() + + fun install(application: Application) { + application.registerActivityLifecycleCallbacks(activityCallbacks) + } + + inner class ActivityCallbacks : Application.ActivityLifecycleCallbacks { + override fun onActivityCreated( + activity: Activity, + savedInstanceState: Bundle? + ) { + if(activity !is FragmentActivity) return + activity.supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentCallbacks, true) + lifecycleController.onContextCreated(ActivityContext(activity), savedInstanceState) + } + + override fun onActivityResumed(activity: Activity) { + if(activity !is FragmentActivity) return + lifecycleController.onContextResumed(activity.navigationContext) + } + + override fun onActivitySaveInstanceState( + activity: Activity, + outState: Bundle + ) { + if(activity !is FragmentActivity) return + lifecycleController.onContextSaved(activity.navigationContext, outState) + } + + override fun onActivityStarted(activity: Activity) {} + override fun onActivityPaused(activity: Activity) {} + override fun onActivityStopped(activity: Activity) {} + override fun onActivityDestroyed(activity: Activity) {} + } + + inner class FragmentCallbacks : FragmentManager.FragmentLifecycleCallbacks() { + override fun onFragmentCreated( + fm: FragmentManager, + fragment: Fragment, + savedInstanceState: Bundle? + ) { + lifecycleController.onContextCreated(FragmentContext(fragment), savedInstanceState) + } + + override fun onFragmentSaveInstanceState( + fm: FragmentManager, + fragment: Fragment, + outState: Bundle + ) { + lifecycleController.onContextSaved(fragment.navigationContext, outState) + } + + override fun onFragmentResumed(fm: FragmentManager, fragment: Fragment) { + lifecycleController.onContextResumed(fragment.navigationContext) + } + } +} \ No newline at end of file diff --git a/enro-core/src/main/java/nav/enro/core/controller/lifecycle/NavigationLifecycleController.kt b/enro-core/src/main/java/nav/enro/core/controller/lifecycle/NavigationLifecycleController.kt new file mode 100644 index 00000000..2da3290f --- /dev/null +++ b/enro-core/src/main/java/nav/enro/core/controller/lifecycle/NavigationLifecycleController.kt @@ -0,0 +1,98 @@ +package nav.enro.core.controller.lifecycle + +import android.app.Application +import android.os.Bundle +import android.view.ViewGroup +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleEventObserver +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.ViewModelStoreOwner +import nav.enro.core.* +import nav.enro.core.controller.NavigationApplication +import nav.enro.core.controller.NavigationController +import nav.enro.core.controller.container.ExecutorContainer +import nav.enro.core.controller.container.PluginContainer +import nav.enro.core.controller.navigationController +import nav.enro.core.internal.NoNavigationKey +import nav.enro.core.internal.handle.NavigationHandleViewModel +import nav.enro.core.internal.handle.createNavigationHandleViewModel +import java.lang.IllegalStateException +import java.util.* + +private const val CONTEXT_ID_ARG = "nav.enro.core.ContextController.CONTEXT_ID" + +internal class NavigationLifecycleController( + private val executorContainer: ExecutorContainer, + private val pluginContainer: PluginContainer +) { + + private val callbacks = NavigationContextLifecycleCallbacks(this) + + fun install(application: Application) { + application as? NavigationApplication + ?: throw IllegalStateException("Application MUST be a NavigationApplication") + + callbacks.install(application) + pluginContainer.onAttached(application.navigationController) + } + + fun onContextCreated(context: NavigationContext<*>, savedInstanceState: Bundle?) { + if(context is ActivityContext) { + context.activity.theme.applyStyle(android.R.style.Animation_Activity, false) + context.activity.findViewById(android.R.id.content).viewTreeObserver.addOnGlobalLayoutListener { + activeNavigationHandle = context.activity.navigationContext.leafContext().getNavigationHandleViewModel() + } + } + + val instruction = context.arguments.readOpenInstruction() + val contextId = instruction?.instructionId + ?: savedInstanceState?.getString(CONTEXT_ID_ARG) + ?: UUID.randomUUID().toString() + + val config = NavigationHandleProperty.getPendingConfig(context) + val defaultInstruction = NavigationInstruction.Open( + instructionId = contextId, + navigationDirection = NavigationDirection.FORWARD, + navigationKey = config?.defaultKey ?: NoNavigationKey(context.contextReference::class.java, context.arguments) + ) + val viewModelStoreOwner = context.contextReference as ViewModelStoreOwner + val handle = viewModelStoreOwner.createNavigationHandleViewModel( + context.controller, + instruction ?: defaultInstruction + ) + config?.applyTo(handle) + handle.lifecycle.addObserver(object : LifecycleEventObserver { + override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) { + if(!handle.hasKey) return + if (event == Lifecycle.Event.ON_CREATE) pluginContainer.onOpened(handle) + if (event == Lifecycle.Event.ON_DESTROY) pluginContainer.onClosed(handle) + } + }) + handle.navigationContext = context + if(savedInstanceState == null) { + executorContainer.executorForClose(context).postOpened(context) + } + if(savedInstanceState == null) handle.executeDeeplink() + } + + fun onContextSaved(context: NavigationContext<*>, outState: Bundle) { + outState.putString(CONTEXT_ID_ARG, context.getNavigationHandleViewModel().id) + } + + fun onContextResumed(context: NavigationContext<*>) { + activeNavigationHandle = context.leafContext().getNavigationHandleViewModel() + } + + private var activeNavigationHandle: NavigationHandle? = null + set(value) { + if(value == field) return + field = value + if(value != null) { + if(value is NavigationHandleViewModel && !value.hasKey) { + field = null + return + } + pluginContainer.onActive(value) + } + } +} diff --git a/enro-core/src/main/java/nav/enro/core/fragment/NavigationHandleFragmentBinder.kt b/enro-core/src/main/java/nav/enro/core/fragment/NavigationHandleFragmentBinder.kt deleted file mode 100644 index c0a1bc05..00000000 --- a/enro-core/src/main/java/nav/enro/core/fragment/NavigationHandleFragmentBinder.kt +++ /dev/null @@ -1,68 +0,0 @@ -package nav.enro.core.fragment - -import android.app.Activity -import android.app.Application -import android.os.Bundle -import androidx.fragment.app.Fragment -import androidx.fragment.app.FragmentActivity -import androidx.fragment.app.FragmentManager -import nav.enro.core.* -import nav.enro.core.internal.NoNavigationKey -import nav.enro.core.internal.handle.createNavigationHandleViewModel -import java.util.* - -internal object NavigationHandleFragmentBinder: Application.ActivityLifecycleCallbacks { - override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) { - if(activity !is FragmentActivity) return - activity.supportFragmentManager.registerFragmentLifecycleCallbacks( - FragmentCallbacks, true - ) - } - - override fun onActivityStarted(activity: Activity) {} - - override fun onActivityResumed(activity: Activity) {} - - override fun onActivityPaused(activity: Activity) {} - - override fun onActivityStopped(activity: Activity) {} - - override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {} - - override fun onActivityDestroyed(activity: Activity) {} - - private object FragmentCallbacks: FragmentManager.FragmentLifecycleCallbacks() { - override fun onFragmentCreated(fm: FragmentManager, fragment: Fragment, savedInstanceState: Bundle?) { - val instruction = fragment.arguments?.readOpenInstruction() - val contextId = instruction?.instructionId - ?: savedInstanceState?.getString(CONTEXT_ID_ARG) - ?: UUID.randomUUID().toString() - - val config = NavigationHandleProperty.getPendingConfig(fragment) - val defaultInstruction = NavigationInstruction.Open( - instructionId = contextId, - navigationDirection = NavigationDirection.FORWARD, - navigationKey = config?.defaultKey ?: NoNavigationKey(fragment::class.java, fragment.arguments) - ) - val controller = fragment.requireActivity().application.navigationController - val handle = fragment.createNavigationHandleViewModel( - controller, - instruction ?: defaultInstruction - ) - config?.applyTo(handle) - - val context = FragmentContext(fragment) - handle.navigationContext = context - controller.onContextCreated(context, savedInstanceState) - if(savedInstanceState == null) handle.executeDeeplink() - } - - override fun onFragmentSaveInstanceState(fm: FragmentManager, fragment: Fragment, outState: Bundle) { - outState.putString(CONTEXT_ID_ARG, fragment.getNavigationHandle().id) - } - - override fun onFragmentResumed(fm: FragmentManager, f: Fragment) { - f.requireActivity().application.navigationController.active = f.requireActivity().navigationContext.leafContext().getNavigationHandleViewModel() - } - } -} diff --git a/enro-core/src/main/java/nav/enro/core/internal/handle/NavigationHandleViewModel.kt b/enro-core/src/main/java/nav/enro/core/internal/handle/NavigationHandleViewModel.kt index 17ab62cc..f9030926 100644 --- a/enro-core/src/main/java/nav/enro/core/internal/handle/NavigationHandleViewModel.kt +++ b/enro-core/src/main/java/nav/enro/core/internal/handle/NavigationHandleViewModel.kt @@ -31,15 +31,7 @@ internal class NavigationHandleViewModel( internal var childContainers = listOf() internal var internalOnCloseRequested: () -> Unit = { close() } - private val lifecycle = LifecycleRegistry(this).apply { - addObserver(object : LifecycleEventObserver { - override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) { - if(!hasKey) return - if (event == Lifecycle.Event.ON_CREATE) controller.onOpened(this@NavigationHandleViewModel) - if (event == Lifecycle.Event.ON_DESTROY) controller.onClosed(this@NavigationHandleViewModel) - } - }) - } + private val lifecycle = LifecycleRegistry(this) override fun getLifecycle(): Lifecycle { return lifecycle @@ -58,10 +50,6 @@ internal class NavigationHandleViewModel( } } - init { - controller.handles[id] = this - } - private fun registerLifecycleObservers(context: NavigationContext) { context.lifecycle.addObserver(object : LifecycleEventObserver { override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) { @@ -116,7 +104,6 @@ internal class NavigationHandleViewModel( } override fun onCleared() { - controller.handles.remove(id) lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY) } } diff --git a/enro-masterdetail/src/main/java/nav/enro/masterdetail/MasterDetailComponent.kt b/enro-masterdetail/src/main/java/nav/enro/masterdetail/MasterDetailComponent.kt index 8caec902..c69b4fe2 100644 --- a/enro-masterdetail/src/main/java/nav/enro/masterdetail/MasterDetailComponent.kt +++ b/enro-masterdetail/src/main/java/nav/enro/masterdetail/MasterDetailComponent.kt @@ -12,9 +12,9 @@ import nav.enro.core.addOpenInstruction import nav.enro.core.activity import nav.enro.core.fragment import nav.enro.core.controller.NavigationController -import nav.enro.core.navigationController import nav.enro.core.activity.DefaultActivityExecutor import nav.enro.core.ExecutorArgs +import nav.enro.core.controller.navigationController import nav.enro.core.createOverride import nav.enro.core.forward import nav.enro.core.getNavigationHandle diff --git a/enro-multistack/src/main/java/nav/enro/multistack/MultistackControllerFragment.kt b/enro-multistack/src/main/java/nav/enro/multistack/MultistackControllerFragment.kt index 597596c8..4d2f2c55 100644 --- a/enro-multistack/src/main/java/nav/enro/multistack/MultistackControllerFragment.kt +++ b/enro-multistack/src/main/java/nav/enro/multistack/MultistackControllerFragment.kt @@ -14,6 +14,7 @@ import androidx.fragment.app.Fragment import androidx.lifecycle.MutableLiveData import nav.enro.core.* import nav.enro.core.activity.ActivityNavigator +import nav.enro.core.controller.navigationController import nav.enro.core.fragment.DefaultFragmentExecutor import nav.enro.core.fragment.FragmentNavigator diff --git a/enro/src/androidTest/java/nav/enro/TestApplication.kt b/enro/src/androidTest/java/nav/enro/TestApplication.kt index b4f0dedc..21a163e0 100644 --- a/enro/src/androidTest/java/nav/enro/TestApplication.kt +++ b/enro/src/androidTest/java/nav/enro/TestApplication.kt @@ -1,30 +1,21 @@ package nav.enro import android.app.Application -import nav.enro.core.NavigationApplication import nav.enro.core.activity.createActivityNavigator -import nav.enro.core.controller.NavigationController +import nav.enro.core.controller.NavigationApplication +import nav.enro.core.controller.navigationController import nav.enro.core.fragment.createFragmentNavigator -class TestApplication : Application(), - NavigationApplication { +class TestApplication : Application(), NavigationApplication { - override val navigationController = - NavigationController( - navigators = listOf( - createActivityNavigator(), + override val navigationController = navigationController { + navigator(createActivityNavigator()) - createActivityNavigator(), - createFragmentNavigator(), + navigator(createActivityNavigator()) + navigator(createFragmentNavigator()) - createActivityNavigator(), - createFragmentNavigator(), - createFragmentNavigator() - ) - ) - - override fun onCreate() { - super.onCreate() - NavigationController.install(this) + navigator(createActivityNavigator()) + navigator(createFragmentNavigator()) + navigator(createFragmentNavigator()) } } \ No newline at end of file diff --git a/enro/src/androidTest/java/nav/enro/core/overrides/ActivityToActivityOverrideTests.kt b/enro/src/androidTest/java/nav/enro/core/overrides/ActivityToActivityOverrideTests.kt index f14c05c0..77859a42 100644 --- a/enro/src/androidTest/java/nav/enro/core/overrides/ActivityToActivityOverrideTests.kt +++ b/enro/src/androidTest/java/nav/enro/core/overrides/ActivityToActivityOverrideTests.kt @@ -5,7 +5,7 @@ import androidx.test.core.app.ActivityScenario import junit.framework.Assert.assertTrue import nav.enro.* import nav.enro.core.* -import nav.enro.core.navigationController +import nav.enro.core.controller.navigationController import nav.enro.core.createOverride import org.junit.Test diff --git a/enro/src/androidTest/java/nav/enro/core/overrides/ActivityToFragmentOverrideTests.kt b/enro/src/androidTest/java/nav/enro/core/overrides/ActivityToFragmentOverrideTests.kt index 5e0288b8..1b61fe6c 100644 --- a/enro/src/androidTest/java/nav/enro/core/overrides/ActivityToFragmentOverrideTests.kt +++ b/enro/src/androidTest/java/nav/enro/core/overrides/ActivityToFragmentOverrideTests.kt @@ -5,6 +5,7 @@ import androidx.test.core.app.ActivityScenario import junit.framework.Assert.assertTrue import nav.enro.* import nav.enro.core.* +import nav.enro.core.controller.navigationController import org.junit.Test class ActivityToFragmentOverrideTests() { diff --git a/enro/src/androidTest/java/nav/enro/core/overrides/FragmentToActivityOverrideTests.kt b/enro/src/androidTest/java/nav/enro/core/overrides/FragmentToActivityOverrideTests.kt index 3f1c36d5..77c917fb 100644 --- a/enro/src/androidTest/java/nav/enro/core/overrides/FragmentToActivityOverrideTests.kt +++ b/enro/src/androidTest/java/nav/enro/core/overrides/FragmentToActivityOverrideTests.kt @@ -5,7 +5,7 @@ import androidx.test.core.app.ActivityScenario import junit.framework.Assert.assertTrue import nav.enro.* import nav.enro.core.* -import nav.enro.core.navigationController +import nav.enro.core.controller.navigationController import nav.enro.core.createOverride import org.junit.Before import org.junit.Test diff --git a/enro/src/androidTest/java/nav/enro/core/overrides/FragmentToFragmentOverrideTests.kt b/enro/src/androidTest/java/nav/enro/core/overrides/FragmentToFragmentOverrideTests.kt index f201f9de..42af9d52 100644 --- a/enro/src/androidTest/java/nav/enro/core/overrides/FragmentToFragmentOverrideTests.kt +++ b/enro/src/androidTest/java/nav/enro/core/overrides/FragmentToFragmentOverrideTests.kt @@ -5,7 +5,7 @@ import androidx.test.core.app.ActivityScenario import junit.framework.Assert.assertTrue import nav.enro.* import nav.enro.core.* -import nav.enro.core.navigationController +import nav.enro.core.controller.navigationController import nav.enro.core.createOverride import org.junit.Before import org.junit.Test diff --git a/example/src/main/java/nav/enro/example/ExampleApplication.kt b/example/src/main/java/nav/enro/example/ExampleApplication.kt index 17d60797..870d0e23 100644 --- a/example/src/main/java/nav/enro/example/ExampleApplication.kt +++ b/example/src/main/java/nav/enro/example/ExampleApplication.kt @@ -3,6 +3,7 @@ package nav.enro.example import android.app.Application import nav.enro.annotations.NavigationComponent import nav.enro.core.* +import nav.enro.core.controller.NavigationApplication import nav.enro.core.controller.navigationController import nav.enro.core.plugins.EnroLogger import nav.enro.result.EnroResult From bd39fa41fc9c2052fb684773f477964cd7afbf88 Mon Sep 17 00:00:00 2001 From: Isaac Date: Sun, 27 Dec 2020 16:05:18 +1300 Subject: [PATCH 18/24] Update modularised example so it builds, get the tests running again --- .../core/controller/NavigationController.kt | 7 ++++--- .../NavigationLifecycleController.kt | 1 - .../enro/viewmodel/EnroViewModelExtensions.kt | 21 ++++++++++++++++--- enro/build.gradle | 5 +++++ .../java/nav/enro/TestApplication.kt | 15 ++++++------- .../java/nav/enro/TestDestinations.kt | 6 ++++++ .../nav/enro/example/dashboard/Dashboard.kt | 5 +++-- .../main/java/nav/enro/example/list/List.kt | 5 +++-- .../java/nav/enro/example/login/LoginError.kt | 2 +- 9 files changed, 46 insertions(+), 21 deletions(-) diff --git a/enro-core/src/main/java/nav/enro/core/controller/NavigationController.kt b/enro-core/src/main/java/nav/enro/core/controller/NavigationController.kt index a2a17841..dfb4c5db 100644 --- a/enro-core/src/main/java/nav/enro/core/controller/NavigationController.kt +++ b/enro-core/src/main/java/nav/enro/core/controller/NavigationController.kt @@ -17,6 +17,10 @@ class NavigationController internal constructor( private val contextController: NavigationLifecycleController, ) { + init { + pluginContainer.onAttached(this) + } + internal fun open( navigationContext: NavigationContext, instruction: NavigationInstruction.Open @@ -89,9 +93,6 @@ class NavigationController internal constructor( if (navigationApplication !is Application) throw IllegalArgumentException("A NavigationApplication must extend android.app.Application") - if(navigationApplication.navigationController != this) - throw IllegalArgumentException("A NavigationController can only be installed on a NavigationApplication that returns that NavigationController as its navigationController property") - contextController.install(navigationApplication) } } \ No newline at end of file diff --git a/enro-core/src/main/java/nav/enro/core/controller/lifecycle/NavigationLifecycleController.kt b/enro-core/src/main/java/nav/enro/core/controller/lifecycle/NavigationLifecycleController.kt index 2da3290f..b399b261 100644 --- a/enro-core/src/main/java/nav/enro/core/controller/lifecycle/NavigationLifecycleController.kt +++ b/enro-core/src/main/java/nav/enro/core/controller/lifecycle/NavigationLifecycleController.kt @@ -33,7 +33,6 @@ internal class NavigationLifecycleController( ?: throw IllegalStateException("Application MUST be a NavigationApplication") callbacks.install(application) - pluginContainer.onAttached(application.navigationController) } fun onContextCreated(context: NavigationContext<*>, savedInstanceState: Bundle?) { diff --git a/enro-viewmodel/src/main/java/nav/enro/viewmodel/EnroViewModelExtensions.kt b/enro-viewmodel/src/main/java/nav/enro/viewmodel/EnroViewModelExtensions.kt index a0532447..1f370b59 100644 --- a/enro-viewmodel/src/main/java/nav/enro/viewmodel/EnroViewModelExtensions.kt +++ b/enro-viewmodel/src/main/java/nav/enro/viewmodel/EnroViewModelExtensions.kt @@ -6,8 +6,7 @@ import androidx.fragment.app.FragmentActivity import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelStore -import nav.enro.core.NavigationHandle -import nav.enro.core.getNavigationHandle +import nav.enro.core.* import kotlin.properties.ReadOnlyProperty import kotlin.reflect.KClass import kotlin.reflect.KProperty @@ -21,12 +20,28 @@ class ViewModelNavigationHandleProperty internal constructor( override fun getValue(thisRef: ViewModel, property: KProperty<*>): NavigationHandle { return navigationHandle } + + inner class TypedViewModelNavigationHandleProperty internal constructor( + private val type: KClass + ) : ReadOnlyProperty> { + private val typedNavigationHandle = navigationHandle.asTyped(type) + + override fun getValue(thisRef: ViewModel, property: KProperty<*>): TypedNavigationHandle { + return typedNavigationHandle + } + } } +fun ViewModelNavigationHandleProperty.asTyped(type: KClass) + = TypedViewModelNavigationHandleProperty(type) + +inline fun ViewModelNavigationHandleProperty.asTyped() + = asTyped(T::class) + + fun ViewModel.navigationHandle(): ViewModelNavigationHandleProperty = ViewModelNavigationHandleProperty(this::class) - @MainThread inline fun FragmentActivity.enroViewModels( noinline factoryProducer: (() -> ViewModelProvider.Factory)? = null diff --git a/enro/build.gradle b/enro/build.gradle index bfcc0d4b..159a9227 100644 --- a/enro/build.gradle +++ b/enro/build.gradle @@ -1,4 +1,5 @@ androidLibrary() +apply plugin: "kotlin-kapt" publishAndroidModule("nav.enro", "enro") dependencies { @@ -20,6 +21,10 @@ dependencies { releaseApi "nav.enro:enro-annotations:$versionName" debugApi project(":enro-annotations") + kaptAndroidTest project(":enro-processor") + + androidTestImplementation 'junit:junit:4.13.1' + androidTestImplementation 'androidx.core:core-ktx:1.3.2' androidTestImplementation 'androidx.appcompat:appcompat:1.2.0' androidTestImplementation 'androidx.fragment:fragment-ktx:1.2.5' diff --git a/enro/src/androidTest/java/nav/enro/TestApplication.kt b/enro/src/androidTest/java/nav/enro/TestApplication.kt index 21a163e0..c5add2e3 100644 --- a/enro/src/androidTest/java/nav/enro/TestApplication.kt +++ b/enro/src/androidTest/java/nav/enro/TestApplication.kt @@ -1,21 +1,18 @@ package nav.enro import android.app.Application +import nav.enro.annotations.NavigationComponent import nav.enro.core.activity.createActivityNavigator import nav.enro.core.controller.NavigationApplication import nav.enro.core.controller.navigationController import nav.enro.core.fragment.createFragmentNavigator +import nav.enro.core.plugins.EnroLogger +import nav.enro.result.EnroResult +@NavigationComponent class TestApplication : Application(), NavigationApplication { - override val navigationController = navigationController { - navigator(createActivityNavigator()) - - navigator(createActivityNavigator()) - navigator(createFragmentNavigator()) - - navigator(createActivityNavigator()) - navigator(createFragmentNavigator()) - navigator(createFragmentNavigator()) + plugin(EnroLogger()) + plugin(EnroResult()) } } \ No newline at end of file diff --git a/enro/src/androidTest/java/nav/enro/TestDestinations.kt b/enro/src/androidTest/java/nav/enro/TestDestinations.kt index 06423807..76103e7d 100644 --- a/enro/src/androidTest/java/nav/enro/TestDestinations.kt +++ b/enro/src/androidTest/java/nav/enro/TestDestinations.kt @@ -7,6 +7,7 @@ import nav.enro.core.navigationHandle @Parcelize data class DefaultActivityKey(val id: String) : NavigationKey + @NavigationDestination(DefaultActivityKey::class) class DefaultActivity : TestActivity() { private val navigation by navigationHandle { @@ -20,11 +21,13 @@ class DefaultActivity : TestActivity() { @Parcelize data class GenericActivityKey(val id: String) : NavigationKey + @NavigationDestination(GenericActivityKey::class) class GenericActivity : TestActivity() @Parcelize data class ActivityWithFragmentsKey(val id: String) : NavigationKey + @NavigationDestination(ActivityWithFragmentsKey::class) class ActivityWithFragments : TestActivity() { private val navigation by navigationHandle { @@ -37,16 +40,19 @@ class ActivityWithFragments : TestActivity() { @Parcelize data class ActivityChildFragmentKey(val id: String) : NavigationKey + @NavigationDestination(ActivityChildFragmentKey::class) class ActivityChildFragment : TestFragment() @Parcelize data class ActivityChildFragmentTwoKey(val id: String) : NavigationKey + @NavigationDestination(ActivityChildFragmentTwoKey::class) class ActivityChildFragmentTwo : TestFragment() @Parcelize data class GenericFragmentKey(val id: String) : NavigationKey + @NavigationDestination(GenericFragmentKey::class) class GenericFragment : TestFragment() diff --git a/modularised-example/feature/dashboard/src/main/java/nav/enro/example/dashboard/Dashboard.kt b/modularised-example/feature/dashboard/src/main/java/nav/enro/example/dashboard/Dashboard.kt index abc97ada..72aaa432 100644 --- a/modularised-example/feature/dashboard/src/main/java/nav/enro/example/dashboard/Dashboard.kt +++ b/modularised-example/feature/dashboard/src/main/java/nav/enro/example/dashboard/Dashboard.kt @@ -13,6 +13,7 @@ import nav.enro.core.forward import nav.enro.example.core.data.SimpleDataRepository import nav.enro.example.core.navigation.* import nav.enro.result.registerForNavigationResult +import nav.enro.viewmodel.asTyped import nav.enro.viewmodel.enroViewModels import nav.enro.viewmodel.navigationHandle @@ -78,8 +79,8 @@ class DashboardViewModel( private val repo = SimpleDataRepository() - private val navigationHandle by navigationHandle() - private val key = navigationHandle.key() + private val navigationHandle by navigationHandle().asTyped() + private val key = navigationHandle.key private val viewDetail by registerForNavigationResult(navigationHandle) { state = state.copy(userId = "${state.userId} FIRST($it)") diff --git a/modularised-example/feature/list/src/main/java/nav/enro/example/list/List.kt b/modularised-example/feature/list/src/main/java/nav/enro/example/list/List.kt index 72cc9011..60a89dff 100644 --- a/modularised-example/feature/list/src/main/java/nav/enro/example/list/List.kt +++ b/modularised-example/feature/list/src/main/java/nav/enro/example/list/List.kt @@ -23,6 +23,7 @@ import nav.enro.example.core.navigation.DetailKey import nav.enro.example.core.navigation.ListFilterType import nav.enro.example.core.navigation.ListKey import nav.enro.result.registerForNavigationResult +import nav.enro.viewmodel.asTyped import nav.enro.viewmodel.enroViewModels import nav.enro.viewmodel.navigationHandle import javax.inject.Inject @@ -73,8 +74,8 @@ class ListViewModel @ViewModelInject constructor( ) : SingleStateViewModel() { private val repo = SimpleDataRepository() - private val navigation by navigationHandle() - private val key = navigation.key() + private val navigation by navigationHandle().asTyped() + private val key = navigation.key init { hiltDependency.doSomething() diff --git a/modularised-example/feature/login/src/main/java/nav/enro/example/login/LoginError.kt b/modularised-example/feature/login/src/main/java/nav/enro/example/login/LoginError.kt index 07689102..2223c302 100644 --- a/modularised-example/feature/login/src/main/java/nav/enro/example/login/LoginError.kt +++ b/modularised-example/feature/login/src/main/java/nav/enro/example/login/LoginError.kt @@ -29,10 +29,10 @@ class LoginErrorFragment : DialogFragment() { class LoginErrorDestination : SyntheticDestination { override fun process( navigationContext: NavigationContext, + key: LoginErrorKey, instruction: NavigationInstruction.Open ) { val activity = navigationContext.activity - val key = instruction.navigationKey as LoginErrorKey AlertDialog.Builder(activity) .setTitle("Error!") .setMessage("Whoops! It looks like '${key.errorUser}' isn't a valid user.\n\nPlease try again.") From 5f33d8ed5fbff5391b367ce35f39629837509e51 Mon Sep 17 00:00:00 2001 From: Isaac Date: Mon, 28 Dec 2020 01:33:30 +1300 Subject: [PATCH 19/24] Add tests for results, remove global view tree watcher, add tests for plugins, update navigation to correctly revert the primaryNavigationFragment to it's previous state after a child in a different container is closed --- .../java/nav/enro/core/NavigationContext.kt | 2 +- .../java/nav/enro/core/NavigationExecutor.kt | 5 + .../java/nav/enro/core/NavigationHandle.kt | 2 +- .../core/NavigationHandleConfiguration.kt | 21 ++ .../nav/enro/core/NavigationInstruction.kt | 3 +- .../controller/container/ExecutorContainer.kt | 3 +- .../container/NavigatorContainer.kt | 8 + .../InstructionParentInterceptor.kt | 19 +- .../NavigationContextLifecycleCallbacks.kt | 16 +- .../NavigationLifecycleController.kt | 56 +++- .../core/fragment/DefaultFragmentExecutor.kt | 19 +- .../enro/viewmodel/EnroViewModelExtensions.kt | 42 ++- enro/src/androidTest/AndroidManifest.xml | 5 + .../java/nav/enro/TestApplication.kt | 1 + .../java/nav/enro/TestDestinations.kt | 2 +- .../nav/enro/{core => }/TestExtensions.kt | 61 +++- .../androidTest/java/nav/enro/TestPlugin.kt | 13 + .../androidTest/java/nav/enro/TestViews.kt | 115 +++++-- .../nav/enro/core/ActivityToActivityTests.kt | 1 + .../java/nav/enro/core/PluginTests.kt | 177 ++++++++++ .../nav/enro/result/ResultDestinations.kt | 96 ++++++ .../java/nav/enro/result/ResultTests.kt | 303 ++++++++++++++++++ .../nav/enro/example/ExampleApplication.kt | 34 +- .../java/nav/enro/example/MainActivity.kt | 9 +- .../nav/enro/example/dashboard/Dashboard.kt | 10 +- .../main/java/nav/enro/example/list/List.kt | 11 +- .../main/java/nav/enro/example/login/Login.kt | 6 +- 27 files changed, 905 insertions(+), 135 deletions(-) rename enro/src/androidTest/java/nav/enro/{core => }/TestExtensions.kt (61%) create mode 100644 enro/src/androidTest/java/nav/enro/TestPlugin.kt create mode 100644 enro/src/androidTest/java/nav/enro/core/PluginTests.kt create mode 100644 enro/src/androidTest/java/nav/enro/result/ResultDestinations.kt create mode 100644 enro/src/androidTest/java/nav/enro/result/ResultTests.kt diff --git a/enro-core/src/main/java/nav/enro/core/NavigationContext.kt b/enro-core/src/main/java/nav/enro/core/NavigationContext.kt index 8e0093a4..64ec7ece 100644 --- a/enro-core/src/main/java/nav/enro/core/NavigationContext.kt +++ b/enro-core/src/main/java/nav/enro/core/NavigationContext.kt @@ -95,4 +95,4 @@ internal fun NavigationContext<*>.getNavigationHandleViewModel(): NavigationHand is FragmentContext -> fragment.getNavigationHandle() is ActivityContext -> activity.getNavigationHandle() } as NavigationHandleViewModel -} +} \ No newline at end of file diff --git a/enro-core/src/main/java/nav/enro/core/NavigationExecutor.kt b/enro-core/src/main/java/nav/enro/core/NavigationExecutor.kt index dc81d4db..0604146a 100644 --- a/enro-core/src/main/java/nav/enro/core/NavigationExecutor.kt +++ b/enro-core/src/main/java/nav/enro/core/NavigationExecutor.kt @@ -4,6 +4,8 @@ import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentActivity import nav.enro.core.activity.DefaultActivityExecutor import nav.enro.core.fragment.DefaultFragmentExecutor +import nav.enro.core.synthetic.DefaultSyntheticExecutor +import nav.enro.core.synthetic.SyntheticDestination import kotlin.reflect.KClass // This class is used primarily to simplify the lambda signature of NavigationExecutor.open @@ -75,6 +77,9 @@ class NavigationExecutorBuilder DefaultFragmentExecutor::open as ((ExecutorArgs) -> Unit) + SyntheticDestination::class.java.isAssignableFrom(args.navigator.contextType.java) -> + DefaultSyntheticExecutor::open as ((ExecutorArgs) -> Unit) + else -> throw IllegalArgumentException("No default launch executor found for ${opensType.java}") }.invoke(args) } diff --git a/enro-core/src/main/java/nav/enro/core/NavigationHandle.kt b/enro-core/src/main/java/nav/enro/core/NavigationHandle.kt index 32e6ec3d..b29c76ff 100644 --- a/enro-core/src/main/java/nav/enro/core/NavigationHandle.kt +++ b/enro-core/src/main/java/nav/enro/core/NavigationHandle.kt @@ -24,7 +24,7 @@ interface TypedNavigationHandle : NavigationHandle { @PublishedApi internal class TypedNavigationHandleImpl( - private val navigationHandle: NavigationHandle + internal val navigationHandle: NavigationHandle ): TypedNavigationHandle { override val id: String get() = navigationHandle.id override val controller: NavigationController get() = navigationHandle.controller diff --git a/enro-core/src/main/java/nav/enro/core/NavigationHandleConfiguration.kt b/enro-core/src/main/java/nav/enro/core/NavigationHandleConfiguration.kt index b6070d19..af343608 100644 --- a/enro-core/src/main/java/nav/enro/core/NavigationHandleConfiguration.kt +++ b/enro-core/src/main/java/nav/enro/core/NavigationHandleConfiguration.kt @@ -39,4 +39,25 @@ class NavigationHandleConfiguration @PublishedApi internal co navigationHandleViewModel.childContainers = childContainers navigationHandleViewModel.internalOnCloseRequested = { onCloseRequested(navigationHandleViewModel.asTyped(keyType)) } } +} + +class LazyNavigationHandleConfiguration( + private val keyType: KClass +) { + + private var onCloseRequested: TypedNavigationHandle.() -> Unit = { close() } + + fun onCloseRequested(block: TypedNavigationHandle.() -> Unit) { + onCloseRequested = block + } + + fun configure(navigationHandle: NavigationHandle) { + val handle = if(navigationHandle is TypedNavigationHandleImpl<*>) { + navigationHandle.navigationHandle + } else navigationHandle + + if(handle is NavigationHandleViewModel) { + handle.internalOnCloseRequested = { onCloseRequested(navigationHandle.asTyped(keyType)) } + } + } } \ No newline at end of file diff --git a/enro-core/src/main/java/nav/enro/core/NavigationInstruction.kt b/enro-core/src/main/java/nav/enro/core/NavigationInstruction.kt index a4b96d22..21f44c4c 100644 --- a/enro-core/src/main/java/nav/enro/core/NavigationInstruction.kt +++ b/enro-core/src/main/java/nav/enro/core/NavigationInstruction.kt @@ -23,7 +23,8 @@ sealed class NavigationInstruction { val navigationKey: NavigationKey, val children: List = emptyList(), val parentInstruction: Open? = null, - val parentContext: Class? = null, + val previouslyActiveId: String? = null, + val executorContext: Class? = null, val additionalData: Bundle = Bundle(), val instructionId: String = UUID.randomUUID().toString() ) : NavigationInstruction(), Parcelable diff --git a/enro-core/src/main/java/nav/enro/core/controller/container/ExecutorContainer.kt b/enro-core/src/main/java/nav/enro/core/controller/container/ExecutorContainer.kt index 956507c4..df6eb194 100644 --- a/enro-core/src/main/java/nav/enro/core/controller/container/ExecutorContainer.kt +++ b/enro-core/src/main/java/nav/enro/core/controller/container/ExecutorContainer.kt @@ -6,7 +6,6 @@ import nav.enro.core.* import nav.enro.core.ActivityContext import nav.enro.core.FragmentContext import nav.enro.core.activity.DefaultActivityExecutor -import nav.enro.core.controller.NavigationController import nav.enro.core.fragment.DefaultFragmentExecutor import nav.enro.core.getNavigationHandleViewModel import nav.enro.core.synthetic.DefaultSyntheticExecutor @@ -79,7 +78,7 @@ internal class ExecutorContainer( @Suppress("UNCHECKED_CAST") internal fun executorForClose(navigationContext: NavigationContext): NavigationExecutor { - val parentContextType = navigationContext.getNavigationHandleViewModel().instruction.parentContext?.kotlin + val parentContextType = navigationContext.getNavigationHandleViewModel().instruction.executorContext?.kotlin val contextType = navigationContext.contextReference::class val override = parentContextType?.let { parentContext -> diff --git a/enro-core/src/main/java/nav/enro/core/controller/container/NavigatorContainer.kt b/enro-core/src/main/java/nav/enro/core/controller/container/NavigatorContainer.kt index 4a0d752b..a69abe93 100644 --- a/enro-core/src/main/java/nav/enro/core/controller/container/NavigatorContainer.kt +++ b/enro-core/src/main/java/nav/enro/core/controller/container/NavigatorContainer.kt @@ -43,6 +43,14 @@ internal class NavigatorContainer ( } .toMap() + init { + navigators.forEach { + require(navigatorsByKeyType[it.keyType] == it) { + "Found duplicated navigator binding! ${it.keyType.java.name} has been bound to multiple destinations." + } + } + } + fun navigatorForContextType( contextType: KClass<*> ): Navigator<*, *>? { diff --git a/enro-core/src/main/java/nav/enro/core/controller/interceptor/InstructionParentInterceptor.kt b/enro-core/src/main/java/nav/enro/core/controller/interceptor/InstructionParentInterceptor.kt index 68ca6963..79920197 100644 --- a/enro-core/src/main/java/nav/enro/core/controller/interceptor/InstructionParentInterceptor.kt +++ b/enro-core/src/main/java/nav/enro/core/controller/interceptor/InstructionParentInterceptor.kt @@ -3,6 +3,7 @@ package nav.enro.core.controller.interceptor import nav.enro.core.* import nav.enro.core.activity.ActivityNavigator import nav.enro.core.controller.container.NavigatorContainer +import nav.enro.core.controller.lifecycle.CONTEXT_ID_ARG import nav.enro.core.fragment.FragmentNavigator import nav.enro.core.fragment.internal.SingleFragmentActivity import nav.enro.core.internal.NoKeyNavigator @@ -18,7 +19,8 @@ internal class InstructionParentInterceptor( ): NavigationInstruction.Open { return instruction .setParentInstruction(parentContext, navigator) - .setParentContext(parentContext) + .setExecutorContext(parentContext) + .setPreviouslyActiveId(parentContext) } @@ -50,12 +52,21 @@ internal class InstructionParentInterceptor( return copy(parentInstruction = parentInstruction) } - private fun NavigationInstruction.Open.setParentContext( + private fun NavigationInstruction.Open.setExecutorContext( parentContext: NavigationContext<*> ): NavigationInstruction.Open { if(parentContext.contextReference is SingleFragmentActivity) { - return copy(parentContext = parentContext.getNavigationHandleViewModel().instruction.parentContext) + return copy(executorContext = parentContext.getNavigationHandleViewModel().instruction.executorContext) } - return copy(parentContext = parentContext.contextReference::class.java) + return copy(executorContext = parentContext.contextReference::class.java) + } + + private fun NavigationInstruction.Open.setPreviouslyActiveId( + parentContext: NavigationContext<*> + ): NavigationInstruction.Open { + if(previouslyActiveId != null) return this + return copy( + previouslyActiveId = parentContext.childFragmentManager.primaryNavigationFragment?.getNavigationHandle()?.id + ) } } \ No newline at end of file diff --git a/enro-core/src/main/java/nav/enro/core/controller/lifecycle/NavigationContextLifecycleCallbacks.kt b/enro-core/src/main/java/nav/enro/core/controller/lifecycle/NavigationContextLifecycleCallbacks.kt index 6b41daea..732c4f01 100644 --- a/enro-core/src/main/java/nav/enro/core/controller/lifecycle/NavigationContextLifecycleCallbacks.kt +++ b/enro-core/src/main/java/nav/enro/core/controller/lifecycle/NavigationContextLifecycleCallbacks.kt @@ -2,10 +2,16 @@ package nav.enro.core.controller.lifecycle import android.app.Activity import android.app.Application +import android.content.Context import android.os.Bundle +import android.util.Log +import android.view.View import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentManager +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleEventObserver +import androidx.lifecycle.LifecycleOwner import nav.enro.core.* internal class NavigationContextLifecycleCallbacks ( @@ -29,11 +35,6 @@ internal class NavigationContextLifecycleCallbacks ( lifecycleController.onContextCreated(ActivityContext(activity), savedInstanceState) } - override fun onActivityResumed(activity: Activity) { - if(activity !is FragmentActivity) return - lifecycleController.onContextResumed(activity.navigationContext) - } - override fun onActivitySaveInstanceState( activity: Activity, outState: Bundle @@ -43,6 +44,7 @@ internal class NavigationContextLifecycleCallbacks ( } override fun onActivityStarted(activity: Activity) {} + override fun onActivityResumed(activity: Activity) {} override fun onActivityPaused(activity: Activity) {} override fun onActivityStopped(activity: Activity) {} override fun onActivityDestroyed(activity: Activity) {} @@ -64,9 +66,5 @@ internal class NavigationContextLifecycleCallbacks ( ) { lifecycleController.onContextSaved(fragment.navigationContext, outState) } - - override fun onFragmentResumed(fm: FragmentManager, fragment: Fragment) { - lifecycleController.onContextResumed(fragment.navigationContext) - } } } \ No newline at end of file diff --git a/enro-core/src/main/java/nav/enro/core/controller/lifecycle/NavigationLifecycleController.kt b/enro-core/src/main/java/nav/enro/core/controller/lifecycle/NavigationLifecycleController.kt index b399b261..5850575f 100644 --- a/enro-core/src/main/java/nav/enro/core/controller/lifecycle/NavigationLifecycleController.kt +++ b/enro-core/src/main/java/nav/enro/core/controller/lifecycle/NavigationLifecycleController.kt @@ -2,30 +2,31 @@ package nav.enro.core.controller.lifecycle import android.app.Application import android.os.Bundle -import android.view.ViewGroup +import android.os.Handler +import android.os.Looper +import android.util.Log +import androidx.fragment.app.FragmentManager import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.ViewModelStoreOwner import nav.enro.core.* import nav.enro.core.controller.NavigationApplication -import nav.enro.core.controller.NavigationController import nav.enro.core.controller.container.ExecutorContainer import nav.enro.core.controller.container.PluginContainer -import nav.enro.core.controller.navigationController import nav.enro.core.internal.NoNavigationKey import nav.enro.core.internal.handle.NavigationHandleViewModel import nav.enro.core.internal.handle.createNavigationHandleViewModel -import java.lang.IllegalStateException import java.util.* -private const val CONTEXT_ID_ARG = "nav.enro.core.ContextController.CONTEXT_ID" +internal const val CONTEXT_ID_ARG = "nav.enro.core.ContextController.CONTEXT_ID" internal class NavigationLifecycleController( private val executorContainer: ExecutorContainer, private val pluginContainer: PluginContainer ) { + private val handler = Handler(Looper.getMainLooper()) private val callbacks = NavigationContextLifecycleCallbacks(this) fun install(application: Application) { @@ -38,9 +39,6 @@ internal class NavigationLifecycleController( fun onContextCreated(context: NavigationContext<*>, savedInstanceState: Bundle?) { if(context is ActivityContext) { context.activity.theme.applyStyle(android.R.style.Animation_Activity, false) - context.activity.findViewById(android.R.id.content).viewTreeObserver.addOnGlobalLayoutListener { - activeNavigationHandle = context.activity.navigationContext.leafContext().getNavigationHandleViewModel() - } } val instruction = context.arguments.readOpenInstruction() @@ -62,32 +60,58 @@ internal class NavigationLifecycleController( config?.applyTo(handle) handle.lifecycle.addObserver(object : LifecycleEventObserver { override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) { - if(!handle.hasKey) return + if (!handle.hasKey) return if (event == Lifecycle.Event.ON_CREATE) pluginContainer.onOpened(handle) if (event == Lifecycle.Event.ON_DESTROY) pluginContainer.onClosed(handle) + + handle.navigationContext?.let { + updateActiveNavigationContext(it) + } } }) handle.navigationContext = context - if(savedInstanceState == null) { + if (savedInstanceState == null) { executorContainer.executorForClose(context).postOpened(context) } - if(savedInstanceState == null) handle.executeDeeplink() + if (savedInstanceState == null) handle.executeDeeplink() } fun onContextSaved(context: NavigationContext<*>, outState: Bundle) { outState.putString(CONTEXT_ID_ARG, context.getNavigationHandleViewModel().id) } - fun onContextResumed(context: NavigationContext<*>) { - activeNavigationHandle = context.leafContext().getNavigationHandleViewModel() + private fun updateActiveNavigationContext(context: NavigationContext<*>) { + // Sometimes the context will be in an invalid state to correctly update, and will throw, + // in which case, we just ignore the exception + runCatching { + val root = context.rootContext() + root.childFragmentManager.beginTransaction() + .runOnCommit { + runCatching { + activeNavigationHandle = root.leafContext().getNavigationHandleViewModel() + } + } + .commitAllowingStateLoss() + } + runCatching { + if(context !is FragmentContext<*>) return@runCatching + val root = context.rootContext() + context.fragment.parentFragmentManager.beginTransaction() + .runOnCommit { + runCatching { + activeNavigationHandle = root.leafContext().getNavigationHandleViewModel() + } + } + .commitAllowingStateLoss() + } } private var activeNavigationHandle: NavigationHandle? = null set(value) { - if(value == field) return + if (value == field) return field = value - if(value != null) { - if(value is NavigationHandleViewModel && !value.hasKey) { + if (value != null) { + if (value is NavigationHandleViewModel && !value.hasKey) { field = null return } diff --git a/enro-core/src/main/java/nav/enro/core/fragment/DefaultFragmentExecutor.kt b/enro-core/src/main/java/nav/enro/core/fragment/DefaultFragmentExecutor.kt index 16cb9b0b..db3fffc7 100644 --- a/enro-core/src/main/java/nav/enro/core/fragment/DefaultFragmentExecutor.kt +++ b/enro-core/src/main/java/nav/enro/core/fragment/DefaultFragmentExecutor.kt @@ -98,7 +98,7 @@ object DefaultFragmentExecutor : NavigationExecutor, instruction: NavigationInstruction.Open ) { @@ -185,13 +185,20 @@ object DefaultFragmentExecutor : NavigationExecutor.getParentFragment(): Fragment? { +private fun NavigationContext.getPreviousFragment(): Fragment? { + val previouslyActiveFragment = getNavigationHandleViewModel().instruction.previouslyActiveId + ?.let { previouslyActiveId -> + fragment.parentFragmentManager.fragments.firstOrNull { + it.getNavigationHandle().id == previouslyActiveId && it.isVisible + } + } + val containerView = contextReference.getContainerId() val parentInstruction = getNavigationHandleViewModel().instruction.parentInstruction - parentInstruction ?: return null + parentInstruction ?: return previouslyActiveFragment val previousNavigator = controller.navigatorForKeyType(parentInstruction.navigationKey::class) - if(previousNavigator !is FragmentNavigator) return null + if(previousNavigator !is FragmentNavigator) return previouslyActiveFragment val previousHost = fragmentHostFor(parentInstruction.navigationKey) val previousFragment = previousHost?.fragmentManager?.findFragmentByTag(parentInstruction.instructionId) @@ -210,7 +217,7 @@ fun NavigationContext.getParentFragment(): Fragment? { ) } else -> previousHost?.fragmentManager?.findFragmentById(previousHost.containerId) - } + } ?: previouslyActiveFragment } private fun Fragment.getContainerId() = (requireView().parent as View).id \ No newline at end of file diff --git a/enro-viewmodel/src/main/java/nav/enro/viewmodel/EnroViewModelExtensions.kt b/enro-viewmodel/src/main/java/nav/enro/viewmodel/EnroViewModelExtensions.kt index 1f370b59..5446aebe 100644 --- a/enro-viewmodel/src/main/java/nav/enro/viewmodel/EnroViewModelExtensions.kt +++ b/enro-viewmodel/src/main/java/nav/enro/viewmodel/EnroViewModelExtensions.kt @@ -11,36 +11,34 @@ import kotlin.properties.ReadOnlyProperty import kotlin.reflect.KClass import kotlin.reflect.KProperty -class ViewModelNavigationHandleProperty internal constructor( - viewModelType: KClass -) : ReadOnlyProperty { +class ViewModelNavigationHandleProperty @PublishedApi internal constructor( + viewModelType: KClass, + type: KClass, + block: LazyNavigationHandleConfiguration.() -> Unit +) : ReadOnlyProperty> { private val navigationHandle = EnroViewModelNavigationHandleProvider.get(viewModelType.java) + .asTyped(type) + .apply { + LazyNavigationHandleConfiguration(type) + .apply(block) + .configure(this) + } - override fun getValue(thisRef: ViewModel, property: KProperty<*>): NavigationHandle { + override fun getValue(thisRef: ViewModel, property: KProperty<*>): TypedNavigationHandle { return navigationHandle } - - inner class TypedViewModelNavigationHandleProperty internal constructor( - private val type: KClass - ) : ReadOnlyProperty> { - private val typedNavigationHandle = navigationHandle.asTyped(type) - - override fun getValue(thisRef: ViewModel, property: KProperty<*>): TypedNavigationHandle { - return typedNavigationHandle - } - } } -fun ViewModelNavigationHandleProperty.asTyped(type: KClass) - = TypedViewModelNavigationHandleProperty(type) - -inline fun ViewModelNavigationHandleProperty.asTyped() - = asTyped(T::class) - +fun ViewModel.navigationHandle( + type: KClass, + block: LazyNavigationHandleConfiguration.() -> Unit = {} +): ViewModelNavigationHandleProperty = + ViewModelNavigationHandleProperty(this::class, type, block) -fun ViewModel.navigationHandle(): ViewModelNavigationHandleProperty = - ViewModelNavigationHandleProperty(this::class) +inline fun ViewModel.navigationHandle( + noinline block: LazyNavigationHandleConfiguration.() -> Unit = {} +): ViewModelNavigationHandleProperty = navigationHandle(T::class, block) @MainThread inline fun FragmentActivity.enroViewModels( diff --git a/enro/src/androidTest/AndroidManifest.xml b/enro/src/androidTest/AndroidManifest.xml index 01160a3a..aad399f8 100644 --- a/enro/src/androidTest/AndroidManifest.xml +++ b/enro/src/androidTest/AndroidManifest.xml @@ -7,5 +7,10 @@ + + + + + \ No newline at end of file diff --git a/enro/src/androidTest/java/nav/enro/TestApplication.kt b/enro/src/androidTest/java/nav/enro/TestApplication.kt index c5add2e3..0665f87b 100644 --- a/enro/src/androidTest/java/nav/enro/TestApplication.kt +++ b/enro/src/androidTest/java/nav/enro/TestApplication.kt @@ -14,5 +14,6 @@ class TestApplication : Application(), NavigationApplication { override val navigationController = navigationController { plugin(EnroLogger()) plugin(EnroResult()) + plugin(TestPlugin) } } \ No newline at end of file diff --git a/enro/src/androidTest/java/nav/enro/TestDestinations.kt b/enro/src/androidTest/java/nav/enro/TestDestinations.kt index 76103e7d..0f33add4 100644 --- a/enro/src/androidTest/java/nav/enro/TestDestinations.kt +++ b/enro/src/androidTest/java/nav/enro/TestDestinations.kt @@ -32,7 +32,7 @@ data class ActivityWithFragmentsKey(val id: String) : NavigationKey class ActivityWithFragments : TestActivity() { private val navigation by navigationHandle { defaultKey(ActivityWithFragmentsKey("default")) - container(R.id.content) { + container(primaryFragmentContainer) { it is ActivityChildFragmentKey || it is ActivityChildFragmentTwoKey } } diff --git a/enro/src/androidTest/java/nav/enro/core/TestExtensions.kt b/enro/src/androidTest/java/nav/enro/TestExtensions.kt similarity index 61% rename from enro/src/androidTest/java/nav/enro/core/TestExtensions.kt rename to enro/src/androidTest/java/nav/enro/TestExtensions.kt index edac8e59..549ef689 100644 --- a/enro/src/androidTest/java/nav/enro/core/TestExtensions.kt +++ b/enro/src/androidTest/java/nav/enro/TestExtensions.kt @@ -1,13 +1,16 @@ -package nav.enro.core +package nav.enro import android.app.Application -import android.os.Looper +import android.util.Log import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentActivity import androidx.test.core.app.ActivityScenario import androidx.test.platform.app.InstrumentationRegistry import androidx.test.runner.lifecycle.ActivityLifecycleMonitorRegistry import androidx.test.runner.lifecycle.Stage +import nav.enro.core.* + +private val debug = false inline fun ActivityScenario.getNavigationHandle(): TypedNavigationHandle { var result: NavigationHandle? = null @@ -21,6 +24,54 @@ inline fun ActivityScenario.get return handle.asTyped() } +class TestNavigationContext( + val context: Context, + val navigation: TypedNavigationHandle +) + +inline fun expectContext( + crossinline selector: (TestNavigationContext) -> Boolean = { true } +): TestNavigationContext { + return when { + Fragment::class.java.isAssignableFrom(ContextType::class.java) -> { + waitOnMain { + val activities = ActivityLifecycleMonitorRegistry.getInstance().getActivitiesInStage(Stage.RESUMED) + val activity = activities.firstOrNull() as? FragmentActivity ?: return@waitOnMain null + var fragment = activity.supportFragmentManager.primaryNavigationFragment + + Log.e("WAITING", "started w/ $fragment @ ${TestPlugin.activeKey}") + + while(fragment != null) { + Log.e("WAITING", "continued w/ $fragment @ ${TestPlugin.activeKey}") + if (fragment is ContextType) { + val context = TestNavigationContext( + fragment as ContextType, + fragment.getNavigationHandle().asTyped() + ) + if (selector(context)) return@waitOnMain context + } + fragment = fragment.childFragmentManager.primaryNavigationFragment + } + return@waitOnMain null + } + } + FragmentActivity::class.java.isAssignableFrom(ContextType::class.java) -> waitOnMain { + val activities = ActivityLifecycleMonitorRegistry.getInstance().getActivitiesInStage(Stage.RESUMED) + val activity = activities.firstOrNull() + if(activity !is FragmentActivity) return@waitOnMain null + if(activity !is ContextType) return@waitOnMain null + + val context = TestNavigationContext( + activity as ContextType, + activity.getNavigationHandle().asTyped() + ) + return@waitOnMain if(selector(context)) context else null + } + else -> throw RuntimeException("Failed to get context type ${ContextType::class.java.name}") + } +} + + inline fun expectActivity(crossinline selector: (FragmentActivity) -> Boolean = { it is T }): T { return waitOnMain { val activities = ActivityLifecycleMonitorRegistry.getInstance().getActivitiesInStage(Stage.RESUMED) @@ -65,7 +116,7 @@ fun expectNoActivity() { } fun waitFor(block: () -> Boolean) { - val maximumTime = 20_000 + val maximumTime = 7_000 val startTime = System.currentTimeMillis() while(true) { @@ -76,7 +127,9 @@ fun waitFor(block: () -> Boolean) { } fun waitOnMain(block: () -> T?): T { - val maximumTime = 20_000 + if(debug) { Thread.sleep(3000) } + + val maximumTime = 7_000 val startTime = System.currentTimeMillis() var currentResponse: T? = null diff --git a/enro/src/androidTest/java/nav/enro/TestPlugin.kt b/enro/src/androidTest/java/nav/enro/TestPlugin.kt new file mode 100644 index 00000000..446bcca8 --- /dev/null +++ b/enro/src/androidTest/java/nav/enro/TestPlugin.kt @@ -0,0 +1,13 @@ +package nav.enro + +import nav.enro.core.NavigationHandle +import nav.enro.core.NavigationKey +import nav.enro.core.plugins.EnroPlugin + +object TestPlugin : EnroPlugin() { + var activeKey: NavigationKey? = null + + override fun onActive(navigationHandle: NavigationHandle) { + activeKey = navigationHandle.key + } +} \ No newline at end of file diff --git a/enro/src/androidTest/java/nav/enro/TestViews.kt b/enro/src/androidTest/java/nav/enro/TestViews.kt index 712f4dfd..2f38dfc6 100644 --- a/enro/src/androidTest/java/nav/enro/TestViews.kt +++ b/enro/src/androidTest/java/nav/enro/TestViews.kt @@ -7,47 +7,78 @@ import android.view.Gravity import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.widget.FrameLayout import android.widget.LinearLayout import android.widget.TextView import androidx.appcompat.app.AppCompatActivity +import androidx.core.view.setPadding import androidx.fragment.app.Fragment import nav.enro.core.getNavigationHandle abstract class TestActivity : AppCompatActivity() { - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) + + val layout by lazy { val key = try { getNavigationHandle().key - } catch (t: Throwable) { - Log.e("TestActivity", "Failed to open!", t) - return - } + } catch(t: Throwable) {} + Log.e("TestActivity", "Opened $key") - setContentView( - LinearLayout(this).apply { - orientation = LinearLayout.VERTICAL + LinearLayout(this).apply { + orientation = LinearLayout.VERTICAL + gravity = Gravity.CENTER + + addView(TextView(this@TestActivity).apply { + text = this@TestActivity::class.java.simpleName + setTextSize(TypedValue.COMPLEX_UNIT_SP, 32.0f) + textAlignment = TextView.TEXT_ALIGNMENT_CENTER gravity = Gravity.CENTER + }) + + addView(TextView(this@TestActivity).apply { + text = key.toString() + setTextSize(TypedValue.COMPLEX_UNIT_SP, 14.0f) + textAlignment = TextView.TEXT_ALIGNMENT_CENTER + gravity = Gravity.CENTER + }) + + addView(TextView(this@TestActivity).apply { + id = debugText + setTextSize(TypedValue.COMPLEX_UNIT_SP, 14.0f) + textAlignment = TextView.TEXT_ALIGNMENT_CENTER + gravity = Gravity.CENTER + }) + + addView(FrameLayout(this@TestActivity).apply { + id = primaryFragmentContainer + setBackgroundColor(0x22FF0000) + setPadding(50) + }) + + addView(FrameLayout(this@TestActivity).apply { + id = secondaryFragmentContainer + setBackgroundColor(0x220000FF) + setPadding(50) + }) + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(layout) + } - addView(TextView(this@TestActivity).apply { - text = this@TestActivity::class.java.simpleName - setTextSize(TypedValue.COMPLEX_UNIT_SP, 32.0f) - textAlignment = TextView.TEXT_ALIGNMENT_CENTER - gravity = Gravity.CENTER - }) - - addView(TextView(this@TestActivity).apply { - text = key.toString() - setTextSize(TypedValue.COMPLEX_UNIT_SP, 14.0f) - textAlignment = TextView.TEXT_ALIGNMENT_CENTER - gravity = Gravity.CENTER - }) - } - ) + companion object { + val debugText = View.generateViewId() + val primaryFragmentContainer = View.generateViewId() + val secondaryFragmentContainer = View.generateViewId() } } abstract class TestFragment : Fragment() { + + lateinit var layout: LinearLayout + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -55,13 +86,11 @@ abstract class TestFragment : Fragment() { ): View? { val key = try { getNavigationHandle().key - } catch (t: Throwable) { - Log.e("TestFragment", "Failed to open!", t) - return null - } + } catch(t: Throwable) {} + Log.e("TestFragment", "Opened $key") - return LinearLayout(requireContext()).apply { + layout = LinearLayout(requireContext()).apply { orientation = LinearLayout.VERTICAL gravity = Gravity.CENTER @@ -78,7 +107,35 @@ abstract class TestFragment : Fragment() { textAlignment = TextView.TEXT_ALIGNMENT_CENTER gravity = Gravity.CENTER }) + + addView(TextView(requireContext()).apply { + id = debugText + setTextSize(TypedValue.COMPLEX_UNIT_SP, 14.0f) + textAlignment = TextView.TEXT_ALIGNMENT_CENTER + gravity = Gravity.CENTER + }) + + addView(FrameLayout(requireContext()).apply { + id = primaryFragmentContainer + setPadding(50) + setBackgroundColor(0x22FF0000) + }) + + addView(FrameLayout(requireContext()).apply { + id = secondaryFragmentContainer + setPadding(50) + setBackgroundColor(0x220000FF) + }) } + + return layout + } + + companion object { + val debugText = View.generateViewId() + val primaryFragmentContainer = View.generateViewId() + val secondaryFragmentContainer = View.generateViewId() + } } diff --git a/enro/src/androidTest/java/nav/enro/core/ActivityToActivityTests.kt b/enro/src/androidTest/java/nav/enro/core/ActivityToActivityTests.kt index 7f854168..078b4931 100644 --- a/enro/src/androidTest/java/nav/enro/core/ActivityToActivityTests.kt +++ b/enro/src/androidTest/java/nav/enro/core/ActivityToActivityTests.kt @@ -11,6 +11,7 @@ import nav.enro.GenericActivity import nav.enro.GenericActivityKey import org.junit.Test import java.util.* +import nav.enro.* class ActivityToActivityTests { diff --git a/enro/src/androidTest/java/nav/enro/core/PluginTests.kt b/enro/src/androidTest/java/nav/enro/core/PluginTests.kt new file mode 100644 index 00000000..00c8db6c --- /dev/null +++ b/enro/src/androidTest/java/nav/enro/core/PluginTests.kt @@ -0,0 +1,177 @@ +package nav.enro.core + +import androidx.test.core.app.ActivityScenario +import junit.framework.Assert.assertEquals +import kotlinx.android.parcel.Parcelize +import nav.enro.TestActivity +import nav.enro.TestFragment +import nav.enro.TestPlugin +import nav.enro.annotations.NavigationDestination +import nav.enro.expectContext +import org.junit.Test +import java.util.* + +class PluginTests { + + @Test + fun whenActivityIsStarted_thenActivityIsActive() { + ActivityScenario.launch(PluginTestActivity::class.java) + + assertEquals( + expectContext() + .navigation + .key, + TestPlugin.activeKey + ) + } + + @Test + fun whenFragmentIsStarted_thenFragmentIsActive() { + ActivityScenario.launch(PluginTestActivity::class.java) + + expectContext() + .navigation + .forward(PluginPrimaryTestFragmentKey()) + + assertEquals( + expectContext() + .navigation + .key, + TestPlugin.activeKey + ) + } + + @Test + fun whenFragmentIsStartedAndClosed_thenActivityIsActive() { + ActivityScenario.launch(PluginTestActivity::class.java) + + expectContext() + .navigation + .forward(PluginPrimaryTestFragmentKey()) + + expectContext() + .navigation + .close() + + assertEquals( + expectContext() + .navigation + .key, + TestPlugin.activeKey + ) + } + + @Test + fun whenFragmentIsStarted_thenSecondaryFragmentIsStarted_thenSecondaryFragmentIsActive() { + ActivityScenario.launch(PluginTestActivity::class.java) + + val activityNavigation = expectContext() + .navigation + + activityNavigation.forward(PluginPrimaryTestFragmentKey()) + expectContext() + + activityNavigation.forward(PluginSecondaryTestFragmentKey()) + + assertEquals( + expectContext() + .navigation + .key, + TestPlugin.activeKey + ) + } + + @Test + fun whenFragmentIsStarted_thenSecondaryFragmentIsStartedAndClosed_thenPrimaryFragmentIsActive() { + ActivityScenario.launch(PluginTestActivity::class.java) + + val activityNavigation = expectContext() + .navigation + + activityNavigation.forward(PluginPrimaryTestFragmentKey()) + expectContext() + + activityNavigation.forward(PluginSecondaryTestFragmentKey()) + expectContext() + .navigation + .close() + + assertEquals( + expectContext() + .navigation + .key, + TestPlugin.activeKey + ) + } + + @Test + fun whenFragmentIsStartedWithNestedChild_thenSecondaryFragmentIsStartedAndClosed_thenPrimaryFragmentIsActive() { + ActivityScenario.launch(PluginTestActivity::class.java) + + val activityNavigation = expectContext() + .navigation + + activityNavigation.forward(PluginPrimaryTestFragmentKey()) + expectContext() + .navigation + .forward(PluginPrimaryTestFragmentKey("nested")) + + activityNavigation.forward(PluginSecondaryTestFragmentKey()) + expectContext() + .navigation + .close() + + assertEquals( + expectContext { + it.navigation.key.keyId == "nested" + }.navigation.key, + TestPlugin.activeKey + ) + } +} + +@Parcelize +data class PluginTestActivityKey(val keyId: String = UUID.randomUUID().toString()) : NavigationKey + +@NavigationDestination(PluginTestActivityKey::class) +class PluginTestActivity : TestActivity() { + private val navigation by navigationHandle { + defaultKey(PluginTestActivityKey()) + container(primaryFragmentContainer) { + it is PluginPrimaryTestFragmentKey + } + container(secondaryFragmentContainer) { + it is PluginSecondaryTestFragmentKey + } + } +} + +@Parcelize +data class PluginPrimaryTestFragmentKey(val keyId: String = UUID.randomUUID().toString()) : NavigationKey + +@NavigationDestination(PluginPrimaryTestFragmentKey::class) +class PluginPrimaryTestFragment : TestFragment() { + private val navigation by navigationHandle { + container(primaryFragmentContainer) { + it is PluginPrimaryTestFragmentKey + } + container(secondaryFragmentContainer) { + it is PluginSecondaryTestFragmentKey + } + } +} + +@Parcelize +data class PluginSecondaryTestFragmentKey(val keyId: String = UUID.randomUUID().toString()) : NavigationKey + +@NavigationDestination(PluginSecondaryTestFragmentKey::class) +class PluginSecondaryTestFragment : TestFragment() { + private val navigation by navigationHandle { + container(primaryFragmentContainer) { + it is PluginPrimaryTestFragmentKey + } + container(secondaryFragmentContainer) { + it is PluginSecondaryTestFragmentKey + } + } +} \ No newline at end of file diff --git a/enro/src/androidTest/java/nav/enro/result/ResultDestinations.kt b/enro/src/androidTest/java/nav/enro/result/ResultDestinations.kt new file mode 100644 index 00000000..1e2183fb --- /dev/null +++ b/enro/src/androidTest/java/nav/enro/result/ResultDestinations.kt @@ -0,0 +1,96 @@ +package nav.enro.result + +import android.widget.TextView +import androidx.fragment.app.Fragment +import kotlinx.android.parcel.Parcelize +import nav.enro.TestActivity +import nav.enro.TestFragment +import nav.enro.annotations.NavigationDestination +import nav.enro.core.NavigationKey +import nav.enro.core.navigationHandle + +@Parcelize +class ActivityResultKey : ResultNavigationKey + +@NavigationDestination(ActivityResultKey::class) +class ResultActivity : TestActivity() + +@Parcelize +class FragmentResultKey : ResultNavigationKey + +@NavigationDestination(FragmentResultKey::class) +class ResultFragment : TestFragment() + +@Parcelize +class NestedFragmentResultKey : ResultNavigationKey + +@NavigationDestination(NestedFragmentResultKey::class) +class NestedResultFragment : TestFragment() + + +@Parcelize +class ResultReceiverActivityKey : NavigationKey + +@NavigationDestination(ResultReceiverActivityKey::class) +class ResultReceiverActivity : TestActivity() { + + private val navigation by navigationHandle { + defaultKey(ResultReceiverActivityKey()) + + container(primaryFragmentContainer) { it is NestedFragmentResultKey } + } + + var result: String? = null + val resultChannel by registerForNavigationResult { + result = it + findViewById(debugText).text = "Result: $result\nSecondary Result: $secondaryResult" + } + + var secondaryResult: String? = null + val secondaryResultChannel by registerForNavigationResult { + secondaryResult = it + findViewById(debugText).text = "Result: $result\nSecondary Result: $secondaryResult" + } +} + + +@Parcelize +class NestedResultReceiverActivityKey : NavigationKey + +@NavigationDestination(NestedResultReceiverActivityKey::class) +class NestedResultReceiverActivity : TestActivity() { + private val navigation by navigationHandle { + defaultKey(NestedResultReceiverActivityKey()) + container(primaryFragmentContainer) { it is ResultReceiverFragmentKey || it is NestedFragmentResultKey } + } +} + +@Parcelize +class SideBySideNestedResultReceiverActivityKey : NavigationKey + +@NavigationDestination(SideBySideNestedResultReceiverActivityKey::class) +class SideBySideNestedResultReceiverActivity : TestActivity() { + private val navigation by navigationHandle { + defaultKey(SideBySideNestedResultReceiverActivityKey()) + container(primaryFragmentContainer) { it is ResultReceiverFragmentKey } + container(secondaryFragmentContainer) { it is NestedFragmentResultKey } + } +} + +@Parcelize +class ResultReceiverFragmentKey : NavigationKey + +@NavigationDestination(ResultReceiverFragmentKey::class) +class ResultReceiverFragment : TestFragment() { + var result: String? = null + val resultChannel by registerForNavigationResult { + result = it + requireView().findViewById(debugText).text = "Result: $result\nSecondary Result: $secondaryResult" + } + + var secondaryResult: String? = null + val secondaryResultChannel by registerForNavigationResult { + secondaryResult = it + requireView().findViewById(debugText).text = "Result: $result\nSecondary Result: $secondaryResult" + } +} \ No newline at end of file diff --git a/enro/src/androidTest/java/nav/enro/result/ResultTests.kt b/enro/src/androidTest/java/nav/enro/result/ResultTests.kt new file mode 100644 index 00000000..9f8b4f1c --- /dev/null +++ b/enro/src/androidTest/java/nav/enro/result/ResultTests.kt @@ -0,0 +1,303 @@ +package nav.enro.result + +import androidx.test.core.app.ActivityScenario +import junit.framework.Assert.assertEquals +import junit.framework.Assert.assertFalse +import nav.enro.DefaultActivity +import nav.enro.DefaultActivityKey +import nav.enro.core.* +import nav.enro.expectActivity +import nav.enro.expectContext +import org.junit.Test +import java.util.* + +class ResultTests { + @Test + fun whenActivityRequestsResult_andResultProviderIsStandaloneFragment_thenResultIsReceived() { + val scenario = ActivityScenario.launch(ResultReceiverActivity::class.java) + val result = UUID.randomUUID().toString() + scenario.onActivity { + it.resultChannel.open(FragmentResultKey()) + } + + expectContext() + .navigation + .closeWithResult(result) + + val activity = expectActivity() + + assertEquals(result, activity.result) + } + + @Test + fun whenActivityRequestsResult_andResultProviderIsActivity_thenResultIsReceived() { + val scenario = ActivityScenario.launch(ResultReceiverActivity::class.java) + val result = UUID.randomUUID().toString() + scenario.onActivity { + it.resultChannel.open(ActivityResultKey()) + } + + val resultActivity = expectActivity() + resultActivity.getNavigationHandle() + .asTyped() + .closeWithResult(result) + + val activity = expectActivity() + + assertEquals(result, activity.result) + } + + @Test + fun whenActivityRequestsResult_andResultProviderIsNestedFragment_thenResultIsReceived() { + val scenario = ActivityScenario.launch(ResultReceiverActivity::class.java) + val result = UUID.randomUUID().toString() + scenario.onActivity { + it.resultChannel.open(NestedFragmentResultKey()) + } + + expectContext() + .navigation + .closeWithResult(result) + + val activity = expectActivity() + + assertEquals(result, activity.result) + } + + + @Test + fun whenActivityRequestsResultThroughMultipleChannels_andResultProviderIsFragment_thenChannelUniquenessIsPreserved() { + ActivityScenario.launch(ResultReceiverActivity::class.java) + val result = UUID.randomUUID().toString() + val secondaryResult = UUID.randomUUID().toString() + + expectActivity() + .resultChannel + .open(FragmentResultKey()) + + expectContext() + .navigation + .closeWithResult(result) + + expectActivity() + .secondaryResultChannel + .open(FragmentResultKey()) + + expectContext() + .navigation + .closeWithResult(secondaryResult) + + val activity = expectActivity() + + assertEquals(result, activity.result) + assertEquals(secondaryResult, activity.secondaryResult) + } + + @Test + fun whenActivityRequestsResultThroughMultipleChannels_andResultProviderIsActivity_thenChannelUniquenessIsPreserved() { + ActivityScenario.launch(ResultReceiverActivity::class.java) + val result = UUID.randomUUID().toString() + val secondaryResult = UUID.randomUUID().toString() + + expectActivity() + .resultChannel + .open(ActivityResultKey()) + + expectActivity() + .getNavigationHandle() + .asTyped() + .closeWithResult(result) + + expectActivity() + .secondaryResultChannel + .open(ActivityResultKey()) + + expectActivity() + .getNavigationHandle() + .asTyped() + .closeWithResult(secondaryResult) + + val activity = expectActivity() + + assertEquals(result, activity.result) + assertEquals(secondaryResult, activity.secondaryResult) + } + + @Test + fun whenActivityRequestsResult_andActivityIsReCreated_thenResultIsStillSent() { + val scenario = ActivityScenario.launch(ResultReceiverActivity::class.java) + val result = UUID.randomUUID().toString() + + val initialActivity = expectActivity() + val initalActivityHash = initialActivity.hashCode() + + scenario.recreate() + initialActivity.resultChannel + .open(ActivityResultKey()) + + expectContext() + .navigation + .closeWithResult(result) + + val activity = expectActivity() + + assertEquals(result, activity.result) + assertFalse(initalActivityHash == activity.hashCode()) + } + + @Test + fun whenFragmentRequestsResult_andResultProviderIsStandaloneFragment_thenResultIsReceived() { + ActivityScenario.launch(DefaultActivity::class.java) + val result = UUID.randomUUID().toString() + + expectContext() + .navigation + .forward(ResultReceiverFragmentKey()) + + expectContext() + .context + .resultChannel + .open(FragmentResultKey()) + + expectContext() + .navigation + .closeWithResult(result) + + assertEquals( + result, + expectContext() + .context + .result + ) + } + + @Test + fun whenFragmentRequestsResult_andResultProviderIsActivity_thenResultIsReceived() { + ActivityScenario.launch(DefaultActivity::class.java) + val result = UUID.randomUUID().toString() + + expectContext() + .navigation + .forward(ResultReceiverFragmentKey()) + + expectContext() + .context + .resultChannel + .open(ActivityResultKey()) + + expectContext() + .navigation + .closeWithResult(result) + + assertEquals( + result, + expectContext() + .context + .result + ) + } + + @Test + fun whenNestedFragmentRequestsResult_andResultProviderIsStandaloneFragment_thenResultIsReceived() { + ActivityScenario.launch(NestedResultReceiverActivity::class.java) + val result = UUID.randomUUID().toString() + + expectContext() + .navigation + .forward(ResultReceiverFragmentKey()) + + expectContext() + .context + .resultChannel + .open(FragmentResultKey()) + + expectContext() + .navigation + .closeWithResult(result) + + assertEquals( + result, + expectContext() + .context + .result + ) + } + + @Test + fun whenNestedFragmentRequestsResult_andResultProviderIsActivity_thenResultIsReceived() { + ActivityScenario.launch(NestedResultReceiverActivity::class.java) + val result = UUID.randomUUID().toString() + + expectContext() + .navigation + .forward(ResultReceiverFragmentKey()) + + expectContext() + .context + .resultChannel + .open(ActivityResultKey()) + + expectContext() + .navigation + .closeWithResult(result) + + assertEquals( + result, + expectContext() + .context + .result + ) + } + + @Test + fun whenNestedFragmentRequestsResult_andResultProviderIsNestedFragment_thenResultIsReceived() { + ActivityScenario.launch(NestedResultReceiverActivity::class.java) + val result = UUID.randomUUID().toString() + + expectContext() + .navigation + .forward(ResultReceiverFragmentKey()) + + expectContext() + .context + .resultChannel + .open(NestedFragmentResultKey()) + + expectContext() + .navigation + .closeWithResult(result) + + assertEquals( + result, + expectContext() + .context + .result + ) + } + + @Test + fun whenNestedFragmentRequestsResult_andResultProviderIsNestedFragmentSideBySideWithFragment_thenResultIsReceived() { + ActivityScenario.launch(SideBySideNestedResultReceiverActivity::class.java) + val result = UUID.randomUUID().toString() + + expectContext() + .navigation + .forward(ResultReceiverFragmentKey()) + + expectContext() + .context + .resultChannel + .open(NestedFragmentResultKey()) + + expectContext() + .navigation + .closeWithResult(result) + + assertEquals( + result, + expectContext() + .context + .result + ) + } +} \ No newline at end of file diff --git a/modularised-example/app/src/main/java/nav/enro/example/ExampleApplication.kt b/modularised-example/app/src/main/java/nav/enro/example/ExampleApplication.kt index 6651a29b..c85867ce 100644 --- a/modularised-example/app/src/main/java/nav/enro/example/ExampleApplication.kt +++ b/modularised-example/app/src/main/java/nav/enro/example/ExampleApplication.kt @@ -3,9 +3,9 @@ package nav.enro.example import android.app.Application import dagger.hilt.android.HiltAndroidApp import nav.enro.annotations.NavigationComponent -import nav.enro.core.NavigationApplication -import nav.enro.core.navigationController -import nav.enro.core.activity.DefaultActivityExecutor +import nav.enro.core.AnimationPair +import nav.enro.core.controller.NavigationApplication +import nav.enro.core.controller.navigationController import nav.enro.core.plugins.EnroHilt import nav.enro.core.plugins.EnroLogger import nav.enro.example.core.data.UserRepository @@ -18,29 +18,15 @@ import nav.enro.result.EnroResult class ExampleApplication : Application(), NavigationApplication { override val navigationController = navigationController { - withPlugin(EnroHilt()) - withPlugin(EnroResult()) - withPlugin(EnroLogger()) + plugin(EnroHilt()) + plugin(EnroResult()) + plugin(EnroLogger()) - override( - launch = { - DefaultActivityExecutor.open(it) - it.fromContext.activity.overridePendingTransition(R.anim.fragment_fade_enter, R.anim.enro_no_op_animation) - }, - close = { - DefaultActivityExecutor.close(it) + override { + animation { + AnimationPair.Resource(R.anim.fragment_fade_enter, R.anim.enro_no_op_animation) } - ) - - override( - launch = { - DefaultActivityExecutor.open(it) - it.fromContext.activity.overridePendingTransition(R.anim.fragment_fade_enter, R.anim.enro_no_op_animation) - }, - close = { - DefaultActivityExecutor.close(it) - } - ) + } } override fun onCreate() { diff --git a/modularised-example/app/src/main/java/nav/enro/example/MainActivity.kt b/modularised-example/app/src/main/java/nav/enro/example/MainActivity.kt index 39bb53e4..bf0243dc 100644 --- a/modularised-example/app/src/main/java/nav/enro/example/MainActivity.kt +++ b/modularised-example/app/src/main/java/nav/enro/example/MainActivity.kt @@ -48,6 +48,9 @@ class MainActivity : AppCompatActivity() { super.onPause() findViewById(android.R.id.content) .animate() + .apply { + start() + } .cancel() } } @@ -56,15 +59,15 @@ class MainActivity : AppCompatActivity() { class LaunchDestination : SyntheticDestination { override fun process( navigationContext: NavigationContext, + key: LaunchKey, instruction: NavigationInstruction.Open ) { val navigation = navigationContext.activity.getNavigationHandle() val userRepo = UserRepository.instance - val user = userRepo.activeUser - val key = when (user) { + val nextKey = when (val user = userRepo.activeUser) { null -> LoginKey() else -> DashboardKey(user) } - navigation.replaceRoot(key) + navigation.replaceRoot(nextKey) } } \ No newline at end of file diff --git a/modularised-example/feature/dashboard/src/main/java/nav/enro/example/dashboard/Dashboard.kt b/modularised-example/feature/dashboard/src/main/java/nav/enro/example/dashboard/Dashboard.kt index 72aaa432..94dcbf08 100644 --- a/modularised-example/feature/dashboard/src/main/java/nav/enro/example/dashboard/Dashboard.kt +++ b/modularised-example/feature/dashboard/src/main/java/nav/enro/example/dashboard/Dashboard.kt @@ -13,7 +13,6 @@ import nav.enro.core.forward import nav.enro.example.core.data.SimpleDataRepository import nav.enro.example.core.navigation.* import nav.enro.result.registerForNavigationResult -import nav.enro.viewmodel.asTyped import nav.enro.viewmodel.enroViewModels import nav.enro.viewmodel.navigationHandle @@ -79,7 +78,11 @@ class DashboardViewModel( private val repo = SimpleDataRepository() - private val navigationHandle by navigationHandle().asTyped() + private val navigationHandle by navigationHandle { + onCloseRequested { + state = state.copy(closeRequested = true) + } + } private val key = navigationHandle.key private val viewDetail by registerForNavigationResult(navigationHandle) { @@ -101,9 +104,6 @@ class DashboardViewModel( otherPublicMessageCount = data.count { it.isPublic && it.ownerId != userId } ) -// navigationHandle.onCloseRequested { -// state = state.copy(closeRequested = true) -// } } fun test(boolean: Boolean) { diff --git a/modularised-example/feature/list/src/main/java/nav/enro/example/list/List.kt b/modularised-example/feature/list/src/main/java/nav/enro/example/list/List.kt index 60a89dff..c0be939e 100644 --- a/modularised-example/feature/list/src/main/java/nav/enro/example/list/List.kt +++ b/modularised-example/feature/list/src/main/java/nav/enro/example/list/List.kt @@ -22,8 +22,8 @@ import nav.enro.example.core.data.SimpleDataRepository import nav.enro.example.core.navigation.DetailKey import nav.enro.example.core.navigation.ListFilterType import nav.enro.example.core.navigation.ListKey +import nav.enro.result.closeWithResult import nav.enro.result.registerForNavigationResult -import nav.enro.viewmodel.asTyped import nav.enro.viewmodel.enroViewModels import nav.enro.viewmodel.navigationHandle import javax.inject.Inject @@ -74,7 +74,11 @@ class ListViewModel @ViewModelInject constructor( ) : SingleStateViewModel() { private val repo = SimpleDataRepository() - private val navigation by navigationHandle().asTyped() + private val navigation by navigationHandle { + onCloseRequested { + closeWithResult(state.result) + } + } private val key = navigation.key init { @@ -96,9 +100,6 @@ class ListViewModel @ViewModelInject constructor( } ) -// navigation.onCloseRequested { -// navigation.closeWithResult(state.result) -// } } fun setResult(it: Boolean) { diff --git a/modularised-example/feature/login/src/main/java/nav/enro/example/login/Login.kt b/modularised-example/feature/login/src/main/java/nav/enro/example/login/Login.kt index e08d26cb..769ceb9b 100644 --- a/modularised-example/feature/login/src/main/java/nav/enro/example/login/Login.kt +++ b/modularised-example/feature/login/src/main/java/nav/enro/example/login/Login.kt @@ -6,9 +6,11 @@ import androidx.core.widget.doOnTextChanged import androidx.lifecycle.observe import kotlinx.android.synthetic.main.login.* import nav.enro.annotations.NavigationDestination +import nav.enro.core.NavigationKey import nav.enro.core.forward import nav.enro.core.navigationHandle import nav.enro.core.replaceRoot +import nav.enro.example.core.base.SingleStateViewModel import nav.enro.example.core.data.UserRepository import nav.enro.example.core.navigation.DashboardKey import nav.enro.example.core.navigation.LoginErrorKey @@ -50,9 +52,9 @@ data class LoginState( val username: String = "" ) -class LoginViewModel : nav.enro.example.core.base.SingleStateViewModel() { +class LoginViewModel : SingleStateViewModel() { - private val navigationHandle by navigationHandle() + private val navigationHandle by navigationHandle() private val userRepo = UserRepository.instance From 9c421bf8bc1a7283db278fdcfbc86953e5e88d12 Mon Sep 17 00:00:00 2001 From: Isaac Date: Mon, 28 Dec 2020 01:34:28 +1300 Subject: [PATCH 20/24] Remove debug logs --- enro/src/androidTest/java/nav/enro/TestExtensions.kt | 3 --- 1 file changed, 3 deletions(-) diff --git a/enro/src/androidTest/java/nav/enro/TestExtensions.kt b/enro/src/androidTest/java/nav/enro/TestExtensions.kt index 549ef689..d34d9b4f 100644 --- a/enro/src/androidTest/java/nav/enro/TestExtensions.kt +++ b/enro/src/androidTest/java/nav/enro/TestExtensions.kt @@ -39,10 +39,7 @@ inline fun expectCont val activity = activities.firstOrNull() as? FragmentActivity ?: return@waitOnMain null var fragment = activity.supportFragmentManager.primaryNavigationFragment - Log.e("WAITING", "started w/ $fragment @ ${TestPlugin.activeKey}") - while(fragment != null) { - Log.e("WAITING", "continued w/ $fragment @ ${TestPlugin.activeKey}") if (fragment is ContextType) { val context = TestNavigationContext( fragment as ContextType, From 665eeabcc0a2d3f4d0a4fbac2ee9eeec1629bd39 Mon Sep 17 00:00:00 2001 From: Isaac Date: Mon, 28 Dec 2020 01:48:24 +1300 Subject: [PATCH 21/24] Don't re-add the existing fragments for the master/detail controller --- .../java/nav/enro/masterdetail/MasterDetailComponent.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/enro-masterdetail/src/main/java/nav/enro/masterdetail/MasterDetailComponent.kt b/enro-masterdetail/src/main/java/nav/enro/masterdetail/MasterDetailComponent.kt index c69b4fe2..2580a2c3 100644 --- a/enro-masterdetail/src/main/java/nav/enro/masterdetail/MasterDetailComponent.kt +++ b/enro-masterdetail/src/main/java/nav/enro/masterdetail/MasterDetailComponent.kt @@ -108,7 +108,11 @@ class MasterDetailProperty( navigationController.addOverride(masterOverride) navigationController.addOverride(detailOverride) - (lifecycleOwner as FragmentActivity).getNavigationHandle().forward(initialMasterKey()) + val activity = lifecycleOwner as FragmentActivity + val masterFragment = activity.supportFragmentManager.findFragmentById(masterContainer) + if(masterFragment == null) { + activity.getNavigationHandle().forward(initialMasterKey()) + } } if(event == Lifecycle.Event.ON_START) { From 1c4a522c1c13888d2bf047fb47006309fea8e95d Mon Sep 17 00:00:00 2001 From: Isaac Date: Mon, 28 Dec 2020 01:57:40 +1300 Subject: [PATCH 22/24] Ignore stopped activities when updating the active navigation context --- .../core/controller/lifecycle/NavigationLifecycleController.kt | 2 ++ example/src/main/java/nav/enro/example/ResultExample.kt | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/enro-core/src/main/java/nav/enro/core/controller/lifecycle/NavigationLifecycleController.kt b/enro-core/src/main/java/nav/enro/core/controller/lifecycle/NavigationLifecycleController.kt index 5850575f..f4fa6baf 100644 --- a/enro-core/src/main/java/nav/enro/core/controller/lifecycle/NavigationLifecycleController.kt +++ b/enro-core/src/main/java/nav/enro/core/controller/lifecycle/NavigationLifecycleController.kt @@ -81,6 +81,8 @@ internal class NavigationLifecycleController( } private fun updateActiveNavigationContext(context: NavigationContext<*>) { + if (!context.activity.lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) return + // Sometimes the context will be in an invalid state to correctly update, and will throw, // in which case, we just ignore the exception runCatching { diff --git a/example/src/main/java/nav/enro/example/ResultExample.kt b/example/src/main/java/nav/enro/example/ResultExample.kt index b42aa91d..661ce0fc 100644 --- a/example/src/main/java/nav/enro/example/ResultExample.kt +++ b/example/src/main/java/nav/enro/example/ResultExample.kt @@ -55,7 +55,7 @@ class RequestExampleFragment : Fragment() { class RequestExampleViewModel() : ViewModel() { - private val navigation by navigationHandle() + private val navigation by navigationHandle() private val mutableResults = MutableLiveData>().apply { emptyList() } val results = mutableResults as LiveData> From f666898d5a0629fccbabb93b0d92e8eee57aee15 Mon Sep 17 00:00:00 2001 From: Isaac Date: Mon, 28 Dec 2020 02:05:42 +1300 Subject: [PATCH 23/24] Tweak the NavigationLifecycleController to only rely on a single fragment transaction --- .../lifecycle/NavigationLifecycleController.kt | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/enro-core/src/main/java/nav/enro/core/controller/lifecycle/NavigationLifecycleController.kt b/enro-core/src/main/java/nav/enro/core/controller/lifecycle/NavigationLifecycleController.kt index f4fa6baf..49e49137 100644 --- a/enro-core/src/main/java/nav/enro/core/controller/lifecycle/NavigationLifecycleController.kt +++ b/enro-core/src/main/java/nav/enro/core/controller/lifecycle/NavigationLifecycleController.kt @@ -87,18 +87,12 @@ internal class NavigationLifecycleController( // in which case, we just ignore the exception runCatching { val root = context.rootContext() - root.childFragmentManager.beginTransaction() - .runOnCommit { - runCatching { - activeNavigationHandle = root.leafContext().getNavigationHandleViewModel() - } - } - .commitAllowingStateLoss() - } - runCatching { - if(context !is FragmentContext<*>) return@runCatching - val root = context.rootContext() - context.fragment.parentFragmentManager.beginTransaction() + val fragmentManager = when (context) { + is FragmentContext -> context.fragment.parentFragmentManager + else -> root.childFragmentManager + } + + fragmentManager.beginTransaction() .runOnCommit { runCatching { activeNavigationHandle = root.leafContext().getNavigationHandleViewModel() From 9e4b70197b8f87c94da348bb92c8e32f429a347d Mon Sep 17 00:00:00 2001 From: Isaac Date: Mon, 28 Dec 2020 15:11:36 +1300 Subject: [PATCH 24/24] Update version.properties to 1.3.0-beta01 --- version.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/version.properties b/version.properties index 72fbf069..008422bf 100644 --- a/version.properties +++ b/version.properties @@ -1,2 +1,2 @@ -versionName=1.2.6 -versionCode=24 \ No newline at end of file +versionName=1.3.0-beta01 +versionCode=25 \ No newline at end of file