From 1e9f3d5a2f7ca6923a2e96a74d22602d0d78374f Mon Sep 17 00:00:00 2001 From: Isaac Udy Date: Tue, 11 Oct 2022 19:53:53 +1300 Subject: [PATCH] Add additional tests for AndroidX Navigation Interop, change the way that the AndroidX Navigation Interop is executed --- .../core/fragment/DefaultFragmentExecutor.kt | 2 - .../handle}/AndroidxNavigationInterop.kt | 23 +++++---- .../handle/NavigationHandleViewModel.kt | 37 ++++++--------- .../core/AndroidxNavigationInteropTest.kt | 47 ++++++++++++++++++- .../androidTest/res/navigation/navigation.xml | 2 +- 5 files changed, 76 insertions(+), 35 deletions(-) rename enro-core/src/main/java/dev/enro/core/{fragment => internal/handle}/AndroidxNavigationInterop.kt (55%) diff --git a/enro-core/src/main/java/dev/enro/core/fragment/DefaultFragmentExecutor.kt b/enro-core/src/main/java/dev/enro/core/fragment/DefaultFragmentExecutor.kt index a45b3178..880b27c0 100644 --- a/enro-core/src/main/java/dev/enro/core/fragment/DefaultFragmentExecutor.kt +++ b/enro-core/src/main/java/dev/enro/core/fragment/DefaultFragmentExecutor.kt @@ -104,8 +104,6 @@ object DefaultFragmentExecutor : NavigationExecutor) { - if (interceptCloseInstructionForAndroidxNavigation(context)) return - if(!tryExecutePendingTransitions(context.fragment.parentFragmentManager)) { mainThreadHandler.post { /* diff --git a/enro-core/src/main/java/dev/enro/core/fragment/AndroidxNavigationInterop.kt b/enro-core/src/main/java/dev/enro/core/internal/handle/AndroidxNavigationInterop.kt similarity index 55% rename from enro-core/src/main/java/dev/enro/core/fragment/AndroidxNavigationInterop.kt rename to enro-core/src/main/java/dev/enro/core/internal/handle/AndroidxNavigationInterop.kt index f346bb7e..b276fdcf 100644 --- a/enro-core/src/main/java/dev/enro/core/fragment/AndroidxNavigationInterop.kt +++ b/enro-core/src/main/java/dev/enro/core/internal/handle/AndroidxNavigationInterop.kt @@ -1,29 +1,36 @@ package dev.enro.core.fragment +import androidx.activity.OnBackPressedCallback import androidx.fragment.app.Fragment import androidx.navigation.fragment.NavHostFragment import dev.enro.core.NavigationContext -import dev.enro.core.fragment +import dev.enro.core.activity /** * In applications that contain both AndroidX Navigation and Enro, the back pressed behaviour * that is set by the NavigationHandleViewModel takes precedence over the back pressed behaviours that * are set by AndroidX Navigation. * - * This method checks whether or not a given NavigationContext is a part of the AndroidX Navigation, + * This method checks whether or not a given NavigationContext<*> is a part of the AndroidX Navigation, * by checking whether or not the parent fragment is a NavHostFragment. If we see that it is a NavHostFragment, - * we'll execute popBackStack (which is the same behaviour the back pressed behaviour set by AndroidX Navigation), - * and then return true. + * we'll disable the back pressed callback, repeat the activity.onBackPressed, and then return true * - * If we decide that the NavigationContext does **not** belong to AndroidX Navigation, and + * If we decide that the NavigationContext<*> does **not** belong to AndroidX Navigation, and * is either part of Enro, or not part of any navigation framework, then we return false, to indicate that no * action was performed. */ -internal fun interceptCloseInstructionForAndroidxNavigation(context: NavigationContext): Boolean { +internal fun interceptBackPressForAndroidxNavigation( + backPressedCallback: OnBackPressedCallback, + context: NavigationContext<*>, +): Boolean { + val fragment = context.contextReference as? Fragment ?: return false if (!isAndroidxNavigationOnTheClasspath) return false - val parent = context.fragment.parentFragment + + val parent = fragment.parentFragment if (parent is NavHostFragment) { - parent.navController.popBackStack() + backPressedCallback.isEnabled = false + context.activity.onBackPressed() + backPressedCallback.isEnabled = true return true } return false diff --git a/enro-core/src/main/java/dev/enro/core/internal/handle/NavigationHandleViewModel.kt b/enro-core/src/main/java/dev/enro/core/internal/handle/NavigationHandleViewModel.kt index 0d29359d..d23f0ad6 100644 --- a/enro-core/src/main/java/dev/enro/core/internal/handle/NavigationHandleViewModel.kt +++ b/enro-core/src/main/java/dev/enro/core/internal/handle/NavigationHandleViewModel.kt @@ -1,15 +1,12 @@ package dev.enro.core.internal.handle import android.os.Bundle -import android.os.Handler -import android.os.Looper -import android.util.Log -import androidx.activity.OnBackPressedCallback -import androidx.fragment.app.Fragment +import androidx.activity.addCallback import androidx.fragment.app.FragmentActivity import androidx.lifecycle.* import dev.enro.core.* import dev.enro.core.controller.NavigationController +import dev.enro.core.fragment.interceptBackPressForAndroidxNavigation import dev.enro.core.internal.NoNavigationKey internal open class NavigationHandleViewModel( @@ -21,12 +18,13 @@ internal open class NavigationHandleViewModel( internal val hasKey get() = instruction.navigationKey !is NoNavigationKey - override val key: NavigationKey get() { - if(instruction.navigationKey is NoNavigationKey) throw IllegalStateException( - "The navigation handle for the context ${navigationContext?.contextReference} has no NavigationKey" - ) - return instruction.navigationKey - } + override val key: NavigationKey + get() { + 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 override val additionalData: Bundle get() = instruction.additionalData @@ -66,8 +64,10 @@ internal open class NavigationHandleViewModel( private fun registerOnBackPressedListener(context: NavigationContext) { if (context is ActivityContext) { - context.activity.addOnBackPressedListener { - context.leafContext().getNavigationHandleViewModel().requestClose() + context.activity.onBackPressedDispatcher.addCallback(this) { + val leafContext = context.leafContext() + if (interceptBackPressForAndroidxNavigation(this, leafContext)) return@addCallback + leafContext.getNavigationHandleViewModel().requestClose() } } } @@ -110,21 +110,12 @@ internal open class NavigationHandleViewModel( } } - private fun Lifecycle.onEvent(on: Lifecycle.Event, block: () -> Unit) { addObserver(object : LifecycleEventObserver { override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) { - if(on == 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/src/androidTest/java/dev/enro/core/AndroidxNavigationInteropTest.kt b/enro/src/androidTest/java/dev/enro/core/AndroidxNavigationInteropTest.kt index 2416eabe..28b8650e 100644 --- a/enro/src/androidTest/java/dev/enro/core/AndroidxNavigationInteropTest.kt +++ b/enro/src/androidTest/java/dev/enro/core/AndroidxNavigationInteropTest.kt @@ -1,23 +1,27 @@ package dev.enro.core +import android.content.Intent import android.os.Bundle import android.view.View import android.widget.LinearLayout import android.widget.TextView +import androidx.activity.addCallback import androidx.appcompat.app.AppCompatActivity import androidx.core.os.bundleOf import androidx.core.view.children import androidx.navigation.fragment.findNavController import androidx.test.core.app.ActivityScenario import androidx.test.espresso.Espresso +import androidx.test.platform.app.InstrumentationRegistry import dev.enro.TestFragment import dev.enro.expectFragment +import dev.enro.expectNoActivity import org.junit.Test class AndroidxNavigationInteropTest { @Test - fun whenBackButtonIsPressed_thenJetpackNavigationReceivesBackButtonPress() { + fun givenMultipleAndroidxNavigationFragments_whenBackButtonIsPressed_thenAndroidxNavigationReceivesBackButtonPress() { val scenario = ActivityScenario.launch(JetpackNavigationActivity::class.java) expectFragment { @@ -43,9 +47,38 @@ class AndroidxNavigationInteropTest { } } + @Test + fun givenSingleAndroidxNavigationFragment_whenNavigationBackButtonIsPressed_thenActivityIsClosed() { + val scenario = ActivityScenario.launch(JetpackNavigationActivity::class.java) + expectFragment { + it.navigationArgument == 0 + } + scenario.onActivity { it.onBackPressed() } + expectNoActivity() + } + + @Test + fun givenActivityIsLaunched_andFragmentHasCustomBackNavigation_whenBackButtonIsPressed_thenCustomNavigationIsExecuted() { + val scenario = ActivityScenario.launch( + Intent(InstrumentationRegistry.getInstrumentation().context, JetpackNavigationActivity::class.java).apply { + putExtra("shouldRegisterBackNavigation", true) + } + ) + expectFragment { + it.navigationArgument == 0 + } + Espresso.pressBack() + expectFragment { + it.executedCustomBackPressed + } + } } internal class JetpackNavigationActivity : AppCompatActivity() { + val shouldRegisterBackNavigation by lazy { + intent.getBooleanExtra("shouldRegisterBackNavigation", false) + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(dev.enro.test.R.layout.jetpack_navigation_activity_layout) @@ -57,6 +90,18 @@ internal class JetpackNavigationFragment : TestFragment() { requireArguments().getInt("argument", 0) } + var executedCustomBackPressed = false + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + val activity = requireActivity() as JetpackNavigationActivity + if (activity.shouldRegisterBackNavigation && navigationArgument == 0) { + activity.onBackPressedDispatcher.addCallback(this) { + executedCustomBackPressed = true + } + } + } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { val layout = requireView() as LinearLayout val title = layout.children.first() as TextView diff --git a/enro/src/androidTest/res/navigation/navigation.xml b/enro/src/androidTest/res/navigation/navigation.xml index facbebf2..6d101c5a 100644 --- a/enro/src/androidTest/res/navigation/navigation.xml +++ b/enro/src/androidTest/res/navigation/navigation.xml @@ -21,6 +21,6 @@ app:argType="integer" android:defaultValue="0" /> - > + \ No newline at end of file