使用导航的操作而不是直接片段 ID 会使应用程序崩溃
Using Navigation's actions instead of direct fragment id crashes app
我在使用导航组件执行一项非常简单的任务时遇到了问题。
我只有 2 个屏幕:MainFragment 和 SearchFragment。
当我尝试通过导航操作从 MainFragment 转到 SearchFragment 时,它工作得很好。然后我按下后退按钮,它自然会回到 MainFragment。
问题是,当我第二次单击同一个按钮再次转到 SearchFragment 时,我收到以下错误:
java.lang.IllegalArgumentException: Navigation action/destination action_mainFragment_to_searchFragment cannot be found from the current destination Destination(searchFragment)
我正在从 MainFragment 导航到搜索,如下所示:
findNavController().navigate(MainFragmentDirections.actionMainFragmentToSearchFragment())
我尝试重做 nav_graph.xml 但没有成功。
如果我直接使用 id 导航,它工作正常,我可以来回多次
findNavController().navigate(R.id.searchFragment)
关于如何解决安全参数问题的任何想法?
编辑:
这是我的nav_graph
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/nav_graph_main"
app:startDestination="@id/mainFragment">
<fragment
android:id="@+id/mainFragment"
android:name="news.presentation.main.MainFragment"
android:label="fragment_main"
tools:layout="@layout/fragment_main">
<action
android:id="@+id/action_mainFragment_to_searchFragment"
app:destination="@id/searchFragment" />
</fragment>
<fragment
android:id="@+id/searchFragment"
android:name="news.presentation.search.SearchFragment"
android:label="fragment_search"
tools:layout="@layout/fragment_search" />
</navigation>
这是activity(它基本上只是片段的容器):
这是activity_home.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.fragment.app.FragmentContainerView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/homeFragContainer"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:navGraph="@navigation/nav_graph_main"
tools:context=".presentation.HomeActivity" />
这是 HomeActivity.kt
@AndroidEntryPoint
class HomeActivity : AppCompatActivity(R.layout.activity_home)
这是 HomeFragment:
@AndroidEntryPoint
class MainFragment : Fragment(R.layout.fragment_main) {
private val binding by viewBinding(FragmentMainBinding::bind)
private val viewModel by viewModels<MainViewModel>()
private val articlesAdapter = ArticlesAdapter(::onSubscriptionClicked)
private lateinit var layoutManager: LinearLayoutManager
override fun onViewCreated(view: View, bundle: Bundle?) {
super.onViewCreated(view, bundle)
setupViews()
setupViewModel()
setupRecyclerView()
}
private fun setupViews() {
binding.toolbar.title = getString(R.string.app_name)
binding.fab.setOnClickListener {
viewModel.intent.offer(MainViewModel.Intent.SearchClicked)
}
}
private fun setupViewModel() {
viewModel.state
.onEach(::handleState)
.launchIn(lifecycleScope)
viewModel.feedFlow
.onEach(articlesAdapter::submitList)
.launchIn(lifecycleScope)
}
private fun setupRecyclerView() {
layoutManager = LinearLayoutManager(requireContext())
binding.recycler.layoutManager = layoutManager
binding.recycler.adapter = articlesAdapter
binding.recycler.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
val canScroll = recyclerView.canScrollVertically(-1)
binding.toolbar.isLifted = canScroll
}
})
}
private fun onSubscriptionClicked(article: Article) {
viewModel.intent.offer(MainViewModel.Intent.ItemClicked(article))
}
private fun handleState(state: MainViewModel.State) = when (state) {
NavigateToSearch -> findNavController().navigate(MainFragmentDirections.actionMainFragmentToSearchFragment())
is FeedReady -> binding.progress.isVisible = false
Loading -> binding.progress.isVisible = true
is NavigateToArticle -> {
// works
// findNavController().navigate(
// R.id.articleFragment,
// bundleOf("articleLink" to state.link)
// )
}
}
}
这是它的XML:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipChildren="false"
android:clipToPadding="false"
tools:context=".presentation.main.MainFragment">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@id/bottomNav"
app:layout_constraintTop_toBottomOf="@id/toolbar"
tools:listitem="@layout/item_article_big" />
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/standard"
android:text="Add Feed"
android:textAlignment="center"
app:icon="@drawable/ic_add"
app:layout_constraintBottom_toTopOf="@id/bottomNav"
app:layout_constraintEnd_toEndOf="parent" />
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottomNav"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:menu="@menu/menu_main" />
<tgo1014.news.presentation.customview.LiftableToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipChildren="false"
android:clipToPadding="false"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.progressindicator.LinearProgressIndicator
android:id="@+id/progress"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:indeterminate="true"
app:layout_constraintBottom_toBottomOf="@id/toolbar"
app:layout_constraintTop_toBottomOf="@id/toolbar"
tools:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout>
错误是由于生命周期造成的。
替换为:
viewModel.state
.onEach(::handleState)
.launchIn(lifecycleScope)
来自
viewModel.state
.onEach(::handleState)
.launchIn(viewLifecycleOwner.lifecycleScope)
我在使用导航组件执行一项非常简单的任务时遇到了问题。 我只有 2 个屏幕:MainFragment 和 SearchFragment。
当我尝试通过导航操作从 MainFragment 转到 SearchFragment 时,它工作得很好。然后我按下后退按钮,它自然会回到 MainFragment。
问题是,当我第二次单击同一个按钮再次转到 SearchFragment 时,我收到以下错误:
java.lang.IllegalArgumentException: Navigation action/destination action_mainFragment_to_searchFragment cannot be found from the current destination Destination(searchFragment)
我正在从 MainFragment 导航到搜索,如下所示:
findNavController().navigate(MainFragmentDirections.actionMainFragmentToSearchFragment())
我尝试重做 nav_graph.xml 但没有成功。
如果我直接使用 id 导航,它工作正常,我可以来回多次
findNavController().navigate(R.id.searchFragment)
关于如何解决安全参数问题的任何想法?
编辑:
这是我的nav_graph
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/nav_graph_main"
app:startDestination="@id/mainFragment">
<fragment
android:id="@+id/mainFragment"
android:name="news.presentation.main.MainFragment"
android:label="fragment_main"
tools:layout="@layout/fragment_main">
<action
android:id="@+id/action_mainFragment_to_searchFragment"
app:destination="@id/searchFragment" />
</fragment>
<fragment
android:id="@+id/searchFragment"
android:name="news.presentation.search.SearchFragment"
android:label="fragment_search"
tools:layout="@layout/fragment_search" />
</navigation>
这是activity(它基本上只是片段的容器):
这是activity_home.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.fragment.app.FragmentContainerView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/homeFragContainer"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:navGraph="@navigation/nav_graph_main"
tools:context=".presentation.HomeActivity" />
这是 HomeActivity.kt
@AndroidEntryPoint
class HomeActivity : AppCompatActivity(R.layout.activity_home)
这是 HomeFragment:
@AndroidEntryPoint
class MainFragment : Fragment(R.layout.fragment_main) {
private val binding by viewBinding(FragmentMainBinding::bind)
private val viewModel by viewModels<MainViewModel>()
private val articlesAdapter = ArticlesAdapter(::onSubscriptionClicked)
private lateinit var layoutManager: LinearLayoutManager
override fun onViewCreated(view: View, bundle: Bundle?) {
super.onViewCreated(view, bundle)
setupViews()
setupViewModel()
setupRecyclerView()
}
private fun setupViews() {
binding.toolbar.title = getString(R.string.app_name)
binding.fab.setOnClickListener {
viewModel.intent.offer(MainViewModel.Intent.SearchClicked)
}
}
private fun setupViewModel() {
viewModel.state
.onEach(::handleState)
.launchIn(lifecycleScope)
viewModel.feedFlow
.onEach(articlesAdapter::submitList)
.launchIn(lifecycleScope)
}
private fun setupRecyclerView() {
layoutManager = LinearLayoutManager(requireContext())
binding.recycler.layoutManager = layoutManager
binding.recycler.adapter = articlesAdapter
binding.recycler.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
val canScroll = recyclerView.canScrollVertically(-1)
binding.toolbar.isLifted = canScroll
}
})
}
private fun onSubscriptionClicked(article: Article) {
viewModel.intent.offer(MainViewModel.Intent.ItemClicked(article))
}
private fun handleState(state: MainViewModel.State) = when (state) {
NavigateToSearch -> findNavController().navigate(MainFragmentDirections.actionMainFragmentToSearchFragment())
is FeedReady -> binding.progress.isVisible = false
Loading -> binding.progress.isVisible = true
is NavigateToArticle -> {
// works
// findNavController().navigate(
// R.id.articleFragment,
// bundleOf("articleLink" to state.link)
// )
}
}
}
这是它的XML:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipChildren="false"
android:clipToPadding="false"
tools:context=".presentation.main.MainFragment">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@id/bottomNav"
app:layout_constraintTop_toBottomOf="@id/toolbar"
tools:listitem="@layout/item_article_big" />
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/standard"
android:text="Add Feed"
android:textAlignment="center"
app:icon="@drawable/ic_add"
app:layout_constraintBottom_toTopOf="@id/bottomNav"
app:layout_constraintEnd_toEndOf="parent" />
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottomNav"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:menu="@menu/menu_main" />
<tgo1014.news.presentation.customview.LiftableToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipChildren="false"
android:clipToPadding="false"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.progressindicator.LinearProgressIndicator
android:id="@+id/progress"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:indeterminate="true"
app:layout_constraintBottom_toBottomOf="@id/toolbar"
app:layout_constraintTop_toBottomOf="@id/toolbar"
tools:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout>
错误是由于生命周期造成的。 替换为:
viewModel.state
.onEach(::handleState)
.launchIn(lifecycleScope)
来自
viewModel.state
.onEach(::handleState)
.launchIn(viewLifecycleOwner.lifecycleScope)