Reputation: 1322
I am finally adopting Navigation Architecture Components in a new project and I am already encountering some issues that the docs don't seem to address.
With an ActionBar menu setup in the activity, when I navigate to another fragment and then try to use the ActionBar menu I receive
java.lang.IllegalArgumentException: Navigation action/destination X cannot be found from the current destination
It would appear that I must also add actions from all possible destinations to goto all other destinations which seems like overkill, this just cannot be. There must be a solution to this problem that I am not finding.
I intend to open the app which inflates MainActivity and then MainFragment within the fragment element. The MainActivity should still handle top level navigation within the ActionBar. There is absolutely no reason I need to dupe menu click work in every fragment and define destinations to all other areas of the app.
MainActivity
class MainActivity : AppCompatActivity() {
private lateinit var navController: NavController
private lateinit var appBarConfiguration: AppBarConfiguration
private var menu: Menu? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val controller by lazy { findNavController(R.id.fragment_container) }
navController = controller
val appBarConfig by lazy { AppBarConfiguration(navController.graph) }
appBarConfiguration = appBarConfig
setSupportActionBar(toolbar)
setupActionBarWithNavController(navController, appBarConfiguration)
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.appbar_menu, menu)
this.menu = menu
return true
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
var direction: NavDirections? = null
when(item.itemId) {
R.id.action_messages -> {
direction = MainFragmentDirections.actionMainFragmentToMessagesFragment()
}
R.id.action_menu -> {
direction = MainFragmentDirections.actionMainFragmentToMessagesFragment()
}
else -> super.onOptionsItemSelected(item)
}
if (direction != null) navController.navigate(direction)
return true
}
override fun onSupportNavigateUp(): Boolean {
return navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp()
}
}
activity_main
<?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:id="@+id/card_id_test"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?actionBarSize"
android:background="@color/colorPrimary"
android:elevation="4dp"
android:clipToPadding="false"
app:menu="@menu/appbar_menu"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:titleTextColor="@color/white" />
<fragment
android:id="@+id/fragment_container"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="0dp"
app:defaultNavHost="true"
app:navGraph="@navigation/nav_graph"
app:layout_constraintTop_toBottomOf="@+id/toolbar"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
MainFragment
class MainFragment : Fragment() {
private var _binding: FragmentMainBinding? = null
private val binding get() = _binding!!
private var mActivity : MainActivity? = null
private var mView: View? = null
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
super.onCreateView(inflater, container, savedInstanceState)
_binding = FragmentMainBinding.inflate(inflater, container, false)
mView = binding.root
mActivity = (activity as MainActivity)
return mView
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
btn_dashboard.setOnClickListener {
findNavController().navigate(
MainFragmentDirections.actionMainFragmentToDashboardFragment())
}
}
}
fragment_main
<?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"
android:id="@+id/content_test"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.appcompat.widget.AppCompatTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:text="Main Fragment"
android:textSize="24dp" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_dashboard"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:text="Dashboard" />
</androidx.constraintlayout.widget.ConstraintLayout>
DashboardFragment
class DashboardFragment : Fragment() {
private var _binding: FragmentDashboardBinding? = null
private val binding get() = _binding!!
private var mActivity : MainActivity? = null
private var mView: View? = null
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
super.onCreateView(inflater, container, savedInstanceState)
_binding = FragmentDashboardBinding.inflate(inflater, container, false)
mView = binding.root
mActivity = (activity as MainActivity)
return mView
}
}
fragment_dashboard
<?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"
android:id="@+id/content_test"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.appcompat.widget.AppCompatTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:text="Dashboard Fragment"
android:textSize="24dp" />
</androidx.constraintlayout.widget.ConstraintLayout>
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"
android:id="@+id/nav_graph"
app:startDestination="@id/mainFragment">
<fragment
android:id="@+id/mainFragment"
android:name="com.example.appbarnavigation.MainFragment"
android:label="Main" >
<action
android:id="@+id/action_mainFragment_to_messagesFragment"
app:destination="@id/action_messages" />
<action
android:id="@+id/action_mainFragment_to_menuFragment"
app:destination="@id/action_menu" />
<action
android:id="@+id/action_mainFragment_to_dashboardFragment"
app:destination="@id/dashboardFragment" />
</fragment>
<fragment
android:id="@+id/action_messages"
android:name="com.example.appbarnavigation.MessagesFragment"
android:label="Messages" />
<fragment
android:id="@+id/action_menu"
android:name="com.example.appbarnavigation.MenuFragment"
android:label="Menu" />
<fragment
android:id="@+id/dashboardFragment"
android:name="com.example.appbarnavigation.DashboardFragment"
android:label="Dashboard" />
</navigation>
appbar_menu
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_messages"
android:orderInCategory="1"
android:title="Messages"
android:icon="@drawable/ic_message"
app:showAsAction="always"/>
<item
android:id="@+id/action_menu"
android:orderInCategory="2"
android:title="Menu"
android:icon="@drawable/ic_menu"
app:showAsAction="always"/>
</menu>
Upvotes: 1
Views: 978
Reputation: 1322
This was resolved by defining and using global actions.
<?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"
android:id="@+id/nav_graph"
app:startDestination="@id/mainFragment">
<fragment
android:id="@+id/mainFragment"
android:name="com.example.appbarnavigation.MainFragment"
android:label="Main" >
<action
android:id="@+id/action_mainFragment_to_dashboardFragment"
app:destination="@id/dashboardFragment" />
</fragment>
<fragment
android:id="@+id/action_messages"
android:name="com.example.appbarnavigation.MessagesFragment"
android:label="Messages" />
<fragment
android:id="@+id/action_menu"
android:name="com.example.appbarnavigation.MenuFragment"
android:label="Menu" />
<fragment
android:id="@+id/dashboardFragment"
android:name="com.example.appbarnavigation.DashboardFragment"
android:label="Dashboard" />
<action android:id="@+id/action_global_action_messages" app:destination="@id/action_messages" />
<action android:id="@+id/action_global_action_menu" app:destination="@id/action_menu" />
</navigation>
and in the MainActivity
override fun onOptionsItemSelected(item: MenuItem) = when(item.itemId) {
R.id.action_messages -> {
navController.navigate(R.id.action_global_action_messages)
true
}
R.id.action_menu -> {
navController.navigate(R.id.action_global_action_menu)
true
}
// This is used for menu buttons or anything not explicitly defined here
else -> {
super.onOptionsItemSelected(item)
}
}
Upvotes: 1