In my app, I want the process of creating a new record to be divided into several steps in which users are queried about different information. So I created an activity with NavHostFragment and want to use a navigation graph to switch between fragments using a next button in the toolbar of this activity.
Is it possible to configure the button to navigate between fragments based on a navigation graph? Is this a good approach? Should I rather use a new activity for each step? I am new to android development so I am not sure what is the best way to do this.
You can handle it with your navigation graph
Handle toolbar click events in your fragments:
https://stackoverflow.com/a/30077965/11982611
While handling each fragment's next button click event implement your navigation code
findNavController().navigate(Your action)
Handle all navigation process in your Activity's OnItemOpotionsSelected Listener
override fun onOptionsItemSelected(item: MenuItem): Boolean {
val navHostFragment = supportFragmentManager.primaryNavigationFragment
val fragment = navHostFragment?.childFragmentManager?.fragments?.get(0)
when(item.itemId)
{
android.R.id.home->super.onBackPressed()
R.id.next-> {
if(fragment is Fragment1)
{
fragment.findNavController().navigate(Fragment1toFragmen2 action)}
if(fragment is Fragment2)
{
fragment.findNavController().navigate(Fragment2toFragmen3 action)}
}
return true
}
Related
I have anAndroid navigation drawer with a menu that tapping on items open fragments. Good so far.
Some of those fragments need to open a new one for more details. I do that using this code from the first fragment:
getParentFragmentManager().beginTransaction().replace(R.id.nav_host_fragment_content_main, new MySecondFragment(), null).commit();
Tappin on the backbutton (the backarrow on the top-left corner) doesn't take me back to MyFirstFragment. It throughs an exceptions:
java.lang.IllegalStateException: Fragment MyFirstFragment not
associated with a fragment manager.
I can use this other code:
getParentFragmentManager().beginTransaction().add(R.id.nav_host_fragment_content_main, new MySecondFragment(), null).commit();
But with this code both fragments are visible at the same time. I mean, MySecondFragment views are displayed but I see in the background MyFirstFragment views.
Any idea on how I can open MySecondFragment from MyFirstFragment , and then, if the back arrow is pressed I want to return to the same place MyFirstFragment was before.
Thanks
NEW ANSWER:
Using the navigation drawer, it seems the fragment transactions happen under the NavHostFragment and its FragmentTransactionManager.
This forces us to get its childFragmentManager() and use it for checking the backStackEntryCount.
Therefore just adding the nested fragment to the parent backstack is not enough. We would need to override onBackPressed and onSupportNavigateUp and take into account the backstack of the NavHostFragment when going back and poping it.
private fun handleNestedFragmentsBackStack(): Boolean {
val navHostChildFragmentManager = supportFragmentManager
.findFragmentById(R.id.nav_host_fragment_content_main)?.childFragmentManager
return if (navHostChildFragmentManager?.backStackEntryCount!! > 1) {
navHostChildFragmentManager.popBackStack()
false
} else {
val navController = findNavController(R.id.nav_host_fragment_content_main)
navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp()
}
}
override fun onBackPressed() {
handleNestedFragmentsBackStack()
}
override fun onSupportNavigateUp(): Boolean {
return handleNestedFragmentsBackStack()
}
Now when adding your second fragment to the parent's backstack, remember that the parent isn't the Activity, but the NavHostFragment.
You can proceed by doing the fragment transaction as suggested bellow in the initial answer.
INITIAL ANSWER:
Should work for standard fragment transactions
Adding your fragment to the backstack should solve your issue
FragmentTransaction ft = getParentFragmentManager().beginTransaction()
ft.replace(R.id.nav_host_fragment_content_main, new MySecondFragment(), null)
ft.addToBackStack(MySecondFragment.class.getName()) // you can use a string here, using the class name is just convenient
ft.commit();
When pressing the back button, you will navigate through the back stack.
The same in Kotlin code:
parentFragmentManager.commit {
replace(R.id.nav_host_fragment_content_main, MySecondFragment())
addToBackStack(MySecondFragment::class.java.name)
}
Filip Petrovski solution In Java code:
In the AppCompatActivity with the Navigation Drawer:
#Override
public boolean onSupportNavigateUp() {
FragmentManager oChildFragmentManager = getSupportFragmentManager().findFragmentById(R.id.nav_host_fragment_content_main).getChildFragmentManager();
if(oChildFragmentManager.getBackStackEntryCount() > 1){
oChildFragmentManager.popBackStack();
return true;
}
NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment_content_main);
return NavigationUI.navigateUp(navController, mAppBarConfiguration) || super.onSupportNavigateUp();
}
#Override
public void onBackPressed() {
FragmentManager oChildFragmentManager = getSupportFragmentManager().findFragmentById(R.id.nav_host_fragment_content_main).getChildFragmentManager();
if(oChildFragmentManager.getBackStackEntryCount() > 1){
oChildFragmentManager.popBackStack();
return;
}
super.onBackPressed();
}
Any time you open a fragment do not forget addToBackStack:
getParentFragmentManager().beginTransaction().replace(R.id.nav_host_fragment_content_main, new MyNewFragment()).addToBackStack("").commit();
But with this code both fragments are visible at the same time.
You can set background color to its root layout.xml
Also you can use fragmentTransaction.addToBackStack(null) to add this transaction to the back stack
I have an activity with two fragments. Either fragment is located on container in activity. Also activity has toolbar with settings button. I should pass on settings fragment using Navigation by click on settings button, but i have problems with it, because findNavController(R.id.home_nav).navigate(R.id.action_second_fragment_to_settings_fragment)
works if we pass to settings screen and second fragment is active, but from first active will be crash.
What do i need to do for solving this problem? I used just actions in Navigation component for passing between fragment. May be there is useful solution for this task
if your navController in MainActivity, with this code you can access to navController and send data to fragment.
load new fragment :
val bundle = Bundle()
bundle.putString("id", id)
(activity as MainActivity).navController.navigate(R.id.orderDetailFragment, bundle)
for get data in onViewCreated in fragment :
arguments?.let {
if (it.getSerializable("id") != null) {
val id = it.getString("id")
}
}
i hope it's useful
I have used mvvm using Kotlin. I have login to dashboard with side menu. I have attached navigation with fragments.I need to add logoout option from fragment dashboard.In fragments it did not get onbackpress. I have tried bellow code in welcome activity.
override fun onBackPressed() {
val fragments: List<Fragment> =
supportFragmentManager.fragments
if (fragments != null) {
for (f in fragments) {
if (f is Welcome_Dash_Board) {
//Log out action
}
}
}
}
Welcome_Dash_Board is the dashboard Fragment. Only this fragment will called logout options when system back button press. But Welcome_Dash_Board is not found.
How to used backpress on specific fragment programatically using mvvm?
I'm studying menu and toolbar/actionbar. I'm trying the way: one activity container and many fragments. I created and setup a toolbar as actionbar for MainActivity (onCreate):
val myToolbar = this.findViewById<Toolbar>(R.id.myToolbar)
setSupportActionBar(myToolbar)
Then, I add itens by normal way with onCreateOptionMenu and handle click with onOptionsItemSelected
When a call Fragment 1, I change action bar and add back button like this (onCreate):
val actBar = (activity as AppCompatActivity).supportActionBar
actBar?.setDisplayHomeAsUpEnabled(true)
actBar?.setDisplayShowHomeEnabled(true)
actBar?.setDisplayUseLogoEnabled(false)
actBar?.title = "Fragment 1 toolbar"
actBar?.subtitle = ""
setHasOptionsMenu(true)
Then from Fragment 1, the Fragment 2 is called and setup as same way:
To handle back button click in fragments, in onOptionsItemSelected:
return if (item.itemId == android.R.id.home) {
activity?.onBackPressed()
true
} else return when (item?.itemId){
...
}
else -> super.onOptionsItemSelected(item)
}
And override onBackPressedin MainActivity:
override fun onBackPressed() {
if(supportFragmentManager.backStackEntryCount > 0){
supportFragmentManager.popBackStackImmediate()
}else{
super.onBackPressed()
}
}
The problem is: if I click on back button, it's backing as expected but the last object of action bar is showed. In MainActivity, only action itens are showed as expected:
How I can sync the bar according fragment and activity?
Note:
I'm using Kotlin, but Java solution are welcome (to convert to kotlin
later)
The fragments are added to back stack
I found a solution. I leave here for whoever interests:
I applied OnBackStackChangedListener that watch changes on back stack. Then, You can make any changes on UI.
supportFragmentManager.addOnBackStackChangedListener {
//UI changes
}
Inside, I check if has some fragment current using the fragment container:
supportFragmentManager.addOnBackStackChangedListener {
val currentFragment = supportFragmentManager.findFragmentById(R.id.you_fragment_container)
if (currentFragment == null){
//rebuild action bar here or make any another changes
}
}
In my case, I compare null that mean container has no fragment. So, if null, the root activity is on screen.
This can be used to make changes for any fragment you want to.
That's it.
I am trying to tell when a user selects a different fragment in my navigation drawer. I was trying to use
override fun setUserVisibleHint(isVisibleToUser: Boolean) {
super.setUserVisibleHint(isVisibleToUser)
}
How i switch fragments in my MainActivity:
override fun onNavigationItemSelected(item: MenuItem): Boolean {
// Handle navigation view item clicks here.
when (item.itemId) {
R.id.nav_camera -> {
// Handle the camera action
val fragment: HomeFragment = HomeFragment()
supportFragmentManager.beginTransaction().replace(R.id.content_main, fragment).commit()
}
R.id.nav_manage -> {
val fragment: SettingFragment = SettingFragment()
fragmentManager.beginTransaction().replace(R.id.content_main, fragment).commit()
}
R.id.nav_share -> {
onInviteClicked()
}
R.id.nav_send -> {
val emailIntent: Intent = Intent(android.content.Intent.ACTION_SEND)
emailIntent.type = Constants.FEEDBACK_EMAIL_TYPE
emailIntent.putExtra(android.content.Intent.EXTRA_EMAIL,
arrayOf(Constants.FEEDBACK_EMAIL_ADDRESS))
emailIntent.putExtra(android.content.Intent.EXTRA_SUBJECT,
Constants.FEEDBACK_EMAIL_SUBJECT)
startActivity(Intent.createChooser(
emailIntent, Constants.FEEDBACK_TITLE))
}
}
val drawer: DrawerLayout = findViewById(R.id.drawer_layout)
drawer.closeDrawer(GravityCompat.START)
return true
}
However this does not seem to get called at all. For example, in my NavigationDrawer activity, it shows Fragment A. The user opens the navigation drawer and selects Fragment B. setUserVisibleHint() does not get called in fragment A so my code can know it is no longer shown. I need my code that is isolated in fragment A to know when it is not shown so it can call .stop() on some variables. This is the same use case as onPause() in an activity.
You can simply call
if (myFragment.isVisible()) {...}
or another way is
public boolean isFragmentUIActive() {
return isAdded() && !isDetached() && !isRemoving();
}
Here are a few things I can think of...
Use a consistent fragment, either Support or Native, not both. And, some say the Support fragment is preferable (better maintained).
Make sure the fragment container is not hard coded in XML. If you intend to replace a fragment, then the initial fragment should be loaded dynamically by your code (you typically will load into a FrameLayout using the id as your R.id.{frameLayoutId}).
Do Use the Frament lifecycle events. onPause fires when you replace a fragment, so does onDetach. That will tell you when your old fragment is no longer visible (or will be invisible shortly). If it does not fire, then you have another issue in your code, possibly mixing of Fragment types, or a hardcoded fragment in XML?
Use setUserVisibleHint only in a fragment pager, or be prepared to set it manually. this answer has a little more to say about the use of setUserVisibleHint. When using a pager, multiple fragments can be attached at once, so an additional means (some call it lifecycle event) was needed to tell if a fragment was "really, truly" visible, hence setUserVisibleHint was introduced.
Bonus: If appropriate for your app, use the back stack for backing up by calling addToBackStack after replace. I add this mainly as an addition lifecycle item one would typically want in their app. The code looks like this...
// to initialize your fragment container
supportFragmentManager
.beginTransaction()
.add(R.id.content_fragment, fragment)
.addToBackStack("blank")
.commit()
// to update your fragment container
supportFragmentManager
.beginTransaction()
.replace(R.id.content_fragment, fragment)
.addToBackStack("settings")
.commit()
//in your XML, it can be as simple as adding the FrameLayout below,
// if you start with the Android Studio template for Navigation drawer,
// you can replace the call that includes the "content_main" layout
<!--<include layout="#layout/content_main" /> -->
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/content_fragment" />
I hope this helps.