我创建了一个演示,并在GitHub上发布了它。
该演示包含3个片段:HomeFragment
,ProfileFragment
和LoginFragment
。在ProfileFragment
中,存在一个条件检查,用于检查用户是否连接。如果用户未连接,则会将其发送到LoginFragment
。
ProfileFragment
@AndroidEntryPoint
class ProfileFragment : Fragment(R.layout.fragment_profile) {
private val mainViewModel: MainViewModel by activityViewModels()
private val navController by lazy { findNavController() }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val currentBackStackEntry = navController.currentBackStackEntry!!
currentBackStackEntry.savedStateHandle.getLiveData<Boolean>(LoginFragment.LOGIN_SUCCESSFUL)
.observe(currentBackStackEntry, { success ->
if (!success) {
val startDestination = navController.graph.startDestination
val navOptions = NavOptions.Builder()
.setPopUpTo(startDestination, true)
.build()
navController.navigate(startDestination, null, navOptions)
}
})
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
mainViewModel.user.observe(viewLifecycleOwner, {
if(!it){
navController.navigate(R.id.loginFragment)
}
})
}
}
如文档所述,在onCreate()
方法中,我正在观察SavedStateHandle
中存储的LOGIN_SUCCESSFUL
值,以便在用户未登录时重定向他。
LoginFragment
@AndroidEntryPoint
class LoginFragment : Fragment() {
companion object {
const val LOGIN_SUCCESSFUL: String = "LOGIN_SUCCESSFUL"
}
private val viewModel: LoginViewModel by viewModels()
private val navController by lazy { findNavController() }
private lateinit var savedStateHandle: SavedStateHandle
private lateinit var binding: FragmentLoginBinding
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = DataBindingUtil.inflate(inflater, R.layout.fragment_login, container, false)
binding.viewModel = viewModel
binding.lifecycleOwner = this
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
savedStateHandle = navController.previousBackStackEntry!!.savedStateHandle
savedStateHandle.set(LOGIN_SUCCESSFUL, false)
viewModel.login.observe(viewLifecycleOwner, ResultObserver {
if (it.status == Status.ERROR) {
Snackbar.make(requireView(), it.message ?: "Error", Snackbar.LENGTH_SHORT).show()
} else if (it.status == Status.SUCCESS) {
savedStateHandle.set(LOGIN_SUCCESSFUL, true)
navController.popBackStack()
}
})
}
}
测试
@Test
fun profileFragment_unauthenticated() {
setAuthentication(false)
val navController = TestNavHostController(ApplicationProvider.getApplicationContext())
launchFragmentInHiltContainer<ProfileFragment> {
navController.setViewModelStore(ViewModelStore())
navController.setGraph(R.navigation.nav_main)
navController.setCurrentDestination(R.id.profileFragment)
Navigation.setViewNavController(requireView(), navController)
}
assertEquals(navController.currentDestination?.id, R.id.loginFragment)
}
因为我正在使用Hilt进行依赖注入,所以需要将片段附加到带有
@AndroidEntryPoint
注释的活动中:launchFragmentInHiltContainer。在测试期间,我创建了一个
TestNavHostController
实例并将其分配给片段。问题是,在
onCreate
方法中访问navController
时,navController
在RESUMED
状态期间实例化(参见FragmentScenario),从而导致 java.lang.IllegalStateException: Fragment ProfileFragment ... does not have a NavController set
。我知道可以通过向片段的
viewLifecycleOwnerLiveData
添加观察者来更早地使navController
可用。但是,这还不够早。那么,在这种情况下如何实现测试导航呢?
可能的解决方案:
经过几天的寻找片段测试的方法,我认为我找到了一种解决方案。我的想法是将
navController
附加到活动上。然而,我对结果并不完全满意,这就是为什么我没有将此解决方案发布为答案。对于任何有兴趣的人,我将我的解决方案添加到GitHub存储库中。