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
Related
Hello Android community,
I have a very simple app, composed of two fragments A & B and there is a shared element between those two fragments.
When I open Fragment B, the transition is working well, and I'm able to listen when the element has finished the transition. However, when I pop FragmentB, I'd like FragmentA to be informed that the return transition is over.
I made a project example here: https://github.com/JulienDev/shared_element_listener
As you can see, I've tried many different things without success.
Do you know if that's something possible?
Thank you so much :)
I think issue has been solved. First change goToB() method as below :
fun goToB(view: View) {
supportFragmentManager.beginTransaction()
.addSharedElement(view, "view_transition")
.replace(findViewById<ViewGroup>(R.id.preorder_content).id, FragmentB())
//.addToBackStack("")
.disallowAddToBackStack() // Prevent from adding this fragment to Back stack
.commit()
}
Right now, we are inside Fragment B. If back button is clicked now, then I have used call back to handle it programmatically by adding callback in Fragment B. Like :
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val transitionView = requireView().findViewById<View>(R.id.view_transition)
transitionView.setOnClickListener {
(activity as MainActivity).goToC(transitionView)
}
// This callback will only be called when Fragment B is at least Started.
requireActivity().onBackPressedDispatcher.addCallback(this) {
(activity as MainActivity).goFromBToA(transitionView)
}
}
I have put another method in Activity.
fun goFromBToA(view: View){
supportFragmentManager.beginTransaction()
.addSharedElement(view, "view_transition")
.replace(findViewById<ViewGroup>(R.id.preorder_content).id, FragmentA())
.disallowAddToBackStack()
.commit()
}
Now I can see transition end log when pop up to Fragment A from B.
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
}
I'm trying to find if a fragment is already in the backStack of my NavHostFragment (so it automatically manages the fragment transactions and backstack), in order to pop back to it when the user selects that destination from my Side Menu, instead of adding another new fragment to the backstack.
Here's the catch: Many of my fragments are the same Class, let's call it ArticleListFragment, and their contentId param (a simple string id) changes what is being displayed in those fragments.
This means I cannot use nav_host_fragment.childFragmentManager.findFragmentById() since multiple of my fragments in the backstack have the same fragment id.
What I've tried so far is this
var foundIndex = -1
for (i in 0 until nav_host_fragment.childFragmentManager.backStackEntryCount) {
val currFragmentTag = nav_host_fragment.childFragmentManager.getBackStackEntryAt(i).name
val currFragmentId = nav_host_fragment.childFragmentManager.getBackStackEntryAt(i).id
//val currFragment = nav_host_fragment.childFragmentManager.findFragmentByTag(currFragmentTag) // always returns null
val currFragment = nav_host_fragment.childFragmentManager.findFragmentById(currFragmentId) // always returns null
// currentFragment is null so the check always fails
if (currFragment is ArticleListFragment && currFragment.contentId == "a value I need to check") {
foundIndex = i
break
}
}
Am I doing something wrong? Is there another way to check if a fragment, added by the Android Navigation Component, is already in the back stack?
Well, I had the same issue, i can not say that this is a perfect solution but you can get it with
findNavController()
.backStack
.firstOrNull { it.destination.id == ID_YOU_WANT_TO_CHECK }
backstack is actually restricted so the method needs to be Suppressed.
#SuppressLint("RestrictedApi")
In my application I have a host fragment for a group of views that a user can navigate to via a BottomNavigationView. This BottomNavigationView is connected to my nav controller via setupWithNavController.
My host fragment receives a bundle with some information that I would like each fragment to receive as it is navigated to (via the bottom nav view) as a bundle.
My current solution looks like
mutableListOf<NavDestination>().apply {
addIfNotNull(graph.findNode(R.id.frag1))
addIfNotNull(graph.findNode(R.id.frag2))
addIfNotNull(graph.findNode(R.id.frag3))
forEach {
// args is a safe args object for this host fragment
it.addArgument("argName", NavArgument.Builder().setDefaultValue(args.argName).build())
}
}
While this works it will not scale very well as I am manually adding the arguments for each destination. Since I am not manually navigating to each destination, rather it is done by the BottomNavigationView I'm not sure how to manually add this bundle.
navController.addOnDestinationChangedListener { controller, dest, args ->
when (dest.label) {
"YOUR_LABEL_HERE" -> {
val arg01 = NavArgument.Builder().setDefaultValue("SOME VALUE").build()
val arg02 = NavArgument.Builder().setDefaultValue("SOME OTHER VALUE").build()
dest.addArgument("KEY_NAME", arg01)
dest.addArgument("OTHER_KEY_NAME", arg02)
}
}
}
try this. It should work fine.
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.