#android #android-espresso #android-architecture-navigation
Вопрос:
В моем тесте Эспрессо я настроил создание действия с фрагментом и навигационным графиком:
protected inline fun <reified A : AppCompatActivity, reified F : Fragment> launchFragment(
@NavigationRes navigationResource: Int,
@IdRes fragmentId: Int
) {
launchFragmentInHiltContainer<A, F> {
navController.setGraph(navigationResource)
navController.setCurrentDestination(fragmentId) //<-- I believe the problem is here
this.viewLifecycleOwnerLiveData.observeForever {
//navController.setCurrentDestination(fragmentId)
Navigation.setViewNavController(this.requireView(), navController)
}
}
}
inline fun <reified A : AppCompatActivity, reified F : Fragment> launchFragmentInHiltContainer(
fragmentArgs: Bundle? = null,
fragmentFactory: FragmentFactory? = null,
crossinline action: Fragment.() -> Unit = {}
) {
val startActivityIntent = Intent.makeMainActivity(
ComponentName(
ApplicationProvider.getApplicationContext(),
A::class.java
)
)
ActivityScenario.launch<A>(startActivityIntent).onActivity { activity ->
fragmentFactory?.let {
activity.supportFragmentManager.fragmentFactory = it
}
val fragment: Fragment = activity.supportFragmentManager.fragmentFactory.instantiate(
Preconditions.checkNotNull(F::class.java.classLoader),
F::class.java.name
)
fragment.arguments = fragmentArgs
activity.supportFragmentManager
.beginTransaction()
.add(android.R.id.content, fragment, "") //<-- I have also tried the Id of the fragment container in the activity
.commitNow()
fragment.action()
}
}
И моя активность и фрагмент загружаются нормально, но если я зарегистрирую результат findNavController().currentDestination
, он покажет мне первый экран в моем потоке (я пытаюсь протестировать с 4-го экрана).
Это не такая уж большая проблема, за исключением того, что, когда в моей тестовой установке фрагмент пытается открыть новый фрагмент, я получаю следующую ошибку:
Caused by: java.lang.IllegalArgumentException: Navigation action/destination packageName:id/action_screen4_to_ErrorScreen cannot be found from the current destination Destination(packageName:id/screen1Fragment) label=screen1 class=screen1Fragment
Таким Navigation
образом, он думает, что я нахожусь на startDestination
фрагменте, а не на фрагменте, который я пытаюсь загрузить, поэтому я не могу уйти от своего фрагмента в тесте. Я не пытаюсь выскочить, я знаю, что не было бы никакого запаса.
Следующий код-это то, что я использую для навигации внутри своего фрагмента, который отлично работает «в потоке».
findNavController()
.navigate(
R.id.action_screen4_to_ErrorScreen,
null
)
Я пытался
- доступ к
requireView()
вместо - с помощью
binding.root.findNavController()
requireActivity().findNavController(requireView().id)
Navigation.findNavController(requireActivity(), R.id.fragmentContainerId)
Navigation.findNavController(requireView())
Я создал репо, которое демонстрирует проблему: https://github.com/qbalsdon/espressoTestFramework
Комментарии:
1. Каким способом вы ведете журнал
findNavController().currentDestination
?2. В обоих
onCreateView
и метод, который реагирует наSingleLiveEvent
то, что запускает навигацию3.Ну, ваш
observeForever
не будет уволен к тому времениonCreateView
, как будет вызван — это называется только послеonCreateView
того, как в соответствии с его документацией. Вы видите то же самоеonViewCreated()
при использованииbinding.root.findNavController()
? Это то, что вы установили, когда делаетеNavigation.setViewNavController
4. Да, это то же самое для
onViewCreated()
. Но теперь он бросилjava.lang.IllegalStateException: Fragment did not return a View from onCreateView() or this was called before onCreateView().
— даже несмотря на то, что onCreateView возвращает представление5. Я в замешательстве, в чем дело? Это не может быть одновременно «одним и тем же», а также где-то создавать исключение. Можете ли вы поделиться своим кодом?
Ответ №1:
Итак, я выяснил, что это вполне возможно, и вы можете загрузить фрагмент в любое действие, если используете глубокую привязку, а не пытаетесь установить место назначения после загрузки действия:
protected inline fun <reified A : AppCompatActivity> launchFragment(
@NavigationRes navigationResource: Int,
@IdRes fragmentId: Int,
fragmentArgs: Bundle? = null
) {
launchFragmentWithDeepLink<A>(fragmentArgs, null, navigationResource, fragmentId)
}
inline fun <reified A : AppCompatActivity> launchFragmentWithDeepLink(
fragmentArgs: Bundle? = null,
fragmentFactory: FragmentFactory? = null,
@NavigationRes navigationResource: Int,
@IdRes fragmentId: Int
) {
val launchIntent = NavDeepLinkBuilder(InstrumentationRegistry.getInstrumentation().targetContext)
.setGraph(navigationResource)
.setComponentName(A::class.java)
.setDestination(fragmentId)
.setArguments(fragmentArgs)
.createTaskStackBuilder().intents[0]
ActivityScenario.launch<A>(launchIntent).onActivity { activity ->
fragmentFactory?.let {
activity.supportFragmentManager.fragmentFactory = it
}
}
}
А затем использование:
@get:Rule(order = 1)
override val hiltRule = HiltAndroidRule(this)
@get:Rule(order = 2)
val activityTestRule = ActivityScenarioRule(MainActivity::class.java)
private fun showFragment() {
launchFragment<MainActivity>(
R.navigation.navigation_graph,
R.id.my_fragment_id
)
}