Skip to content

Commit

Permalink
Add additional tests for AndroidX Navigation Interop, change the way …
Browse files Browse the repository at this point in the history
…that the AndroidX Navigation Interop is executed
  • Loading branch information
isaac-udy committed Oct 11, 2022
1 parent 884e5ac commit 1e9f3d5
Show file tree
Hide file tree
Showing 5 changed files with 76 additions and 35 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,6 @@ object DefaultFragmentExecutor : NavigationExecutor<Any, Fragment, NavigationKey
}

override fun close(context: NavigationContext<out Fragment>) {
if (interceptCloseInstructionForAndroidxNavigation(context)) return

if(!tryExecutePendingTransitions(context.fragment.parentFragmentManager)) {
mainThreadHandler.post {
/*
Expand Down
Original file line number Diff line number Diff line change
@@ -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<out Fragment> 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<out Fragment> 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<out Fragment>): 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
Expand Down
Original file line number Diff line number Diff line change
@@ -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(
Expand All @@ -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

Expand Down Expand Up @@ -66,8 +64,10 @@ internal open class NavigationHandleViewModel(

private fun registerOnBackPressedListener(context: NavigationContext<out Any>) {
if (context is ActivityContext<out FragmentActivity>) {
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()
}
}
}
Expand Down Expand Up @@ -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()
}
})
}
Original file line number Diff line number Diff line change
@@ -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<JetpackNavigationFragment> {
Expand All @@ -43,9 +47,38 @@ class AndroidxNavigationInteropTest {
}
}

@Test
fun givenSingleAndroidxNavigationFragment_whenNavigationBackButtonIsPressed_thenActivityIsClosed() {
val scenario = ActivityScenario.launch(JetpackNavigationActivity::class.java)
expectFragment<JetpackNavigationFragment> {
it.navigationArgument == 0
}
scenario.onActivity { it.onBackPressed() }
expectNoActivity()
}

@Test
fun givenActivityIsLaunched_andFragmentHasCustomBackNavigation_whenBackButtonIsPressed_thenCustomNavigationIsExecuted() {
val scenario = ActivityScenario.launch<JetpackNavigationActivity>(
Intent(InstrumentationRegistry.getInstrumentation().context, JetpackNavigationActivity::class.java).apply {
putExtra("shouldRegisterBackNavigation", true)
}
)
expectFragment<JetpackNavigationFragment> {
it.navigationArgument == 0
}
Espresso.pressBack()
expectFragment<JetpackNavigationFragment> {
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)
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion enro/src/androidTest/res/navigation/navigation.xml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,6 @@
app:argType="integer"
android:defaultValue="0" />
</action>
>

</fragment>
</navigation>

0 comments on commit 1e9f3d5

Please sign in to comment.