OnBackPressed does not work correctly in some fragments - android

In MainActivity I have next method:
override fun onBackPressed() {
val onBackPressListener = currentFragmentOnTop() as? OnBackPressListener
if (onBackPressListener?.onBackPress() != true) super.onBackPressed()
}
But sometimes he doesn't work correctly.
For example:
In my startDestination fragment I call dialog(bottomMenuDialog) with menuItems, by clicking on which I call fragments:
findNavController().navigate(R.id.settings_list)
Here is code of my nav_graph.xml:
<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/authorizationsListFragment">
<fragment
android:id="#+id/authorizationsListFragment"
android:name="com.mandarine.sai.features.authorizations.list.AuthorizationsListFragment"
android:label="AuthorizationsListFragment">
<action
android:id="#+id/settings_list"
app:destination="#+id/settingsListFragment" />
<action
android:id="#+id/connections_list"
app:destination="#+id/connectionsListFragment" />
<action
android:id="#+id/bottom_menu_dialog"
app:destination="#+id/bottomMenuDialog" />
</fragment>
But I can't click on the back button to go to the start fragment.
Behavior: it opens the start fragment and immediately goes back to the current one, to settings_list.

override fun onBackPressed() {
finish()
}

Related

Activity navigation back arrow on fragments

I have a StorageAvctivity with 2 fragments inside.
class StorageActivity : BaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_storage)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setDisplayShowHomeEnabled(true)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
android.R.id.home -> finish()
}
return super.onOptionsItemSelected(item)
}
}
This activity has a navigation 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/storage_nav_graph"
app:startDestination="#id/downloadFragment">
<fragment
android:id="#+id/downloadFragment"
android:name=".ui.fragments.storage.DownloadFragment"
android:label="fragment_download"
tools:layout="#layout/fragment_download" >
<action
android:id="#+id/action_downloadFragment_to_largeDownloadFragment"
app:destination="#id/largeDownloadFragment" />
</fragment>
<fragment
android:id="#+id/largeDownloadFragment"
android:name=".ui.fragments.storage.LargeDownloadFragment"
android:label="fragment_large_download"
tools:layout="#layout/fragment_large_download" />
</navigation>
So when the StorageActivity opens, the DownloadFragment appears. If i press the back button storageActivity finishes so i go back to MainActivity. However, from DownloadFragment i can go to LargeDownloadFragment. So in that case if i press the back arrow button still the StorageActivity finishes and gets me back to MainActivity. What i want is to navigate me back to DownloadFragment. How can i do that?

How to navigate to first fragment without recreating the fragment

So example i have 3 fragment
fragment A with 1 edittext, and 1 button
fragment B with 1 textview, and 1 button
framgnet C with 2 textview and 1 button
fragment A with edittext data example "this is test" -> fragment B -> fragment C -> fragment A again
how to prevent fragment A recreate again when from fragment C, so the edittext input in fragment A not empty.
here my 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/my_nav"
app:startDestination="#id/firstFragment">
<fragment
android:id="#+id/firstFragment"
android:name="com.fjr.simplenavigation.FirstFragment"
android:label="fragment_first"
tools:layout="#layout/fragment_first" >
<action
android:id="#+id/action_firstFragment_to_secondFragment"
app:destination="#id/secondFragment"/>
</fragment>
<fragment
android:id="#+id/secondFragment"
android:name="com.fjr.simplenavigation.SecondFragment"
android:label="fragment_second"
tools:layout="#layout/fragment_second" >
<action
android:id="#+id/action_secondFragment_to_thirdFragment"
app:destination="#id/thirdFragment" />
<argument
android:name="data"
app:argType="string"
app:nullable="true" />
</fragment>
<fragment
android:id="#+id/thirdFragment"
android:name="com.fjr.simplenavigation.ThirdFragment"
android:label="fragment_third"
tools:layout="#layout/fragment_third">
<action
android:id="#+id/action_thirdFragment_to_firstFragment"
app:destination="#id/firstFragment"
app:popUpTo="#+id/firstFragment"
app:popUpToInclusive="true"/>
</fragment>
</navigation>
if i replace the code for action_thirdFragment_to_firstFragment to like this
<fragment
android:id="#+id/thirdFragment"
android:name="com.fjr.simplenavigation.ThirdFragment"
android:label="fragment_third"
tools:layout="#layout/fragment_third">
<action
android:id="#+id/action_thirdFragment_to_firstFragment"
app:popUpTo="#+id/firstFragment"
app:popUpToInclusive="false"/>
</fragment>
it will solve the problem, fragment A will not recreated and the edittext still have the input like on the description, but is there any other way?
the second question is how to passing the data back to fragment A (but with edittext still have an input like on the description)?
You can use either an Activity scoped or NavGraph scoped ViewModel, and store your variable in there.
class FragmentA : Fragment {
...
private val viewModel by activityViewModels<ViewModelA>()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
...
myEditText.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {}
override fun afterTextChanged(s: Editable) {
viewModel.text = s.toString()
}
})
}
}
class ViewModelA : ViewModel() {
...
var text: String = ""
}
In the above example, your ViewModel would live as long as your Activity lives. If you'd like to modify your ViewModel, you would reach it the same way in any other Fragment, and get the same instance of the ViewModelA
class FragmentC : Fragment {
...
private val viewModel by activityViewModels<ViewModelA>() <-- gives you the same instance of the ViewModel
}
Read more about "Sharing data between Fragments" here:
https://developer.android.com/topic/libraries/architecture/viewmodel#sharing

Android Navigation Component: back button pressed multiple times before it closes the app

I have a navigation graph with 4 fragments. Each fragment is a stand-alone view: can only navigate to the next fragment, but never navigate back to the previous one.
Expected behaviour:
Pressing back button should close the app. If the fragment has a pager, then the back button should navigate back the pager until it reaches index 0, then after that should close the app.
SplashFragment: a splash screen
LanguageFragment: a view to setup language
BoardingFragment: has a pager and serves as an on-boarding flow
StartupFragment: has a pager and serves as an initial set up flow
The Problem:
All works fine from Frag1 (SplashFragment) to Frag3 (BoardingFragment). But once navigated to Frag4 (StartupFragment) and pager is at index 0, I still have to press back button 7 times before it closes the app. No crashes nor error thrown.
<?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/splash_nav_graph"
app:startDestination="#id/splash_fragment">
<fragment
android:id="#+id/splash_fragment"
android:name=".ui.fragments.SplashFragment"
android:label="fragment_splash"
tools:layout="#layout/fragment_splash" >
<action
android:id="#+id/splash_to_lang_action"
app:destination="#id/language_fragment"
app:popUpTo="#id/splash_fragment"
app:popUpToInclusive="true"/>
</fragment>
<fragment
android:id="#+id/language_fragment"
android:name=".ui.fragments.LanguageFragment"
android:label="fragment_language"
tools:layout="#layout/fragment_language" >
<action
android:id="#+id/lang_to_boarding_action"
app:destination="#id/boarding_fragment"
app:popUpTo="#id/splash_nav_graph"
app:popUpToInclusive="true"/>
</fragment>
<fragment
android:id="#+id/boarding_fragment"
android:name=".ui.fragments.BoardingFragment"
android:label="fragment_boarding"
tools:layout="#layout/fragment_boarding" >
<action
android:id="#+id/boarding_to_startup_action"
app:destination="#id/startup_fragment"
app:popUpTo="#id/splash_nav_graph"
app:popUpToInclusive="true"/>
</fragment>
<fragment
android:id="#+id/startup_fragment"
android:name=".ui.fragments.StartupFragment"
android:label="fragment_startup"
tools:layout="#layout/fragment_startup" />
</navigation>
In SplashFragment:
// navigate after an action
navController.navigate(
R.id.splash_to_lang_action, null, null,
FragmentNavigatorExtras(binding.logo to binding.logo.transitionName)
)
In LanguageFragment:
// navigate after an action
navController.navigate(R.id.lang_to_boarding_action)
In BoardingFragment:
// navigate after an action
navController.navigate(R.id.boarding_to_startup_action)
// manage back-press for pager
activity?.onBackPressedDispatcher?.addCallback {
if (binding.uspPager.currentItem != 0) binding.uspPager.currentItem--
else {
isEnabled = false
activity?.onBackPressed()
}
}
In StartupFragment:
// manage back-press for pager
activity?.onBackPressedDispatcher?.addCallback {
if (binding.uspPager.currentItem != 0) binding.uspPager.currentItem--
else {
isEnabled = false
activity?.onBackPressed()
}
}
I appreciate any help to the max. Thanks in advance.

Dynamically change startDestination of nested navigation graph inside of a BottomNavigationView

I'm trying to update my app to use BottomNavigationView. The first tab contains a HostFragment with a loading spinner that performs a network request to determine which fragment, either HomeFragment or LockedFragment, should be shown in that tab.
MainActivity handles the initial setup of the BottomNavigationView:
class MainActivity : AppCompatActivity() {
private lateinit var navController: NavController
private lateinit var appBarConfiguration: AppBarConfiguration
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val navHostFragment = supportFragmentManager.findFragmentById(
R.id.nav_host_container
) as NavHostFragment
navController = navHostFragment.navController
val bottomNavigationView = findViewById<BottomNavigationView>(R.id.bottom_nav)
bottomNavigationView.setupWithNavController(navController)
appBarConfiguration = AppBarConfiguration(
setOf(R.id.mainFragment)
)
setupActionBarWithNavController(navController, appBarConfiguration)
}
My main nav graph looks like this:
<?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/home">
<include app:graph="#navigation/home"/>
<include app:graph="#navigation/list"/>
<include app:graph="#navigation/form"/>
</navigation>
with the home graph looking like:
<?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/home"
app:startDestination="#+id/hostFragment">
<fragment
android:id="#+id/hostFragment"
android:name="com.example.android.bottomnav.homescreen.HostFragment"
android:label="Host">
<action
android:id="#+id/action_hostFragment_to_homeFragment"
app:destination="#id/homeFragment" />
<action
android:id="#+id/action_hostFragment_to_lockedFragment"
app:destination="#id/lockedFragment" />
</fragment>
<fragment
android:id="#+id/homeFragment"
android:name="com.example.android.bottomnav.homescreen.HomeFragment"
android:label="Home" />
<fragment
android:id="#+id/lockedFragment"
android:name="com.example.android.bottomnav.homescreen.LockedFragment"
android:label="Locked"/>
</navigation>
HostFragment get's shown fine and loads it's data:
class HostFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_host, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
determineFragmentToShow()
}
private fun determineFragmentToShow() {
lifecycleScope.launchWhenStarted {
// mock network call to determine tab
delay(1500)
// show HomeFragment for the sake of the example, but note that
// this would be dependent on the network call's result above
findNavController().navigate(R.id.action_hostFragment_to_homeFragment)
}
}
}
which successfully navigates us to HomeFragment.
Now the problem is that whenever I press the back button from HomeFragment it goes back to HostFragment instead of closing the app.You can see the behavior in this video here.
I tried to set the popUpTo and popUpInclusive tags inside of home.xml like this:
<fragment
android:id="#+id/hostFragment"
android:name="com.example.android.bottomnav.homescreen.HostFragment"
android:label="Host">
<action
android:id="#+id/action_hostFragment_to_homeFragment"
app:destination="#id/homeFragment"
app:popUpTo="#id/home"
app:popUpToInclusive="true"/>
<action
android:id="#+id/action_hostFragment_to_lockedFragment"
app:destination="#id/lockedFragment"
app:popUpTo="#id/home"
app:popUpToInclusive="true"/>
</fragment>
That got the app to close when pressing back from HomeFragment, but now each time I switch to a new tab, it creates a new instance of the fragment and adds it to the backstack. Pressing the back button then will traverse them all backwards. You can see that behavior in this video here.
So how can I update the start destination of a nested navigation graph?
I'm using the latest 2.4.0-alpha10 of navigation component so that I can get native support for multiple backstacks. Any help is greatly appreciated!
I was able to utilize the answer here to get a solution working for me.
Inside of determineFragmentToShow() in HostFragment, I just replaced findNavController().navigate(R.id.action_hostFragment_to_homeFragment) with
val navController = findNavController()
val graph = navController.graph
val walletGraph = graph.findNode(R.id.home) as NavGraph
walletGraph.setStartDestination(R.id.homeFragment)
navController.navigate(R.id.action_hostFragment_to_homeFragment)
I still needed to include the popUpTo and popUpInclusive tags here
<fragment
android:id="#+id/hostFragment"
android:name="com.example.android.bottomnav.homescreen.HostFragment"
android:label="Host">
<action
android:id="#+id/action_hostFragment_to_homeFragment"
app:destination="#id/homeFragment"
app:popUpTo="#id/home"
app:popUpToInclusive="true"/>
<action
android:id="#+id/action_hostFragment_to_lockedFragment"
app:destination="#id/lockedFragment"
app:popUpTo="#id/home"
app:popUpToInclusive="true"/>
</fragment>
but this got me the back behavior I was looking for!

Handle onBackPressed in Android Navigation Component

I have implemented navigation Drawer with Navigation Components in Android. I have 5 fragments that I want to go back to my HomeFragment when I click on back pressed. For the moment they stay onBackStack and do not go to my desired fragment but go to whatever fragment was first.
This is my 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"
app:startDestination="#id/setupFragment"
android:id="#+id/treasure_nav"
android:label="Pick a country">
<fragment android:id="#+id/homeFragment"
android:name="com.stavro_xhardha.pockettreasure.ui.home.HomeFragment"
android:label="Home"
tools:layout="#layout/fragment_home">
<action android:id="#+id/action_home_fragment_to_namesFragment2"
app:popUpTo="#id/homeFragment"
app:destination="#id/namesFragment"/>
<action android:id="#+id/action_home_fragment_to_quranFragment"
app:popUpTo="#id/homeFragment"
app:destination="#id/quranFragment"/>
<action android:id="#+id/action_homeFragment_to_tasbeehFragment"
app:popUpTo="#id/homeFragment"
app:destination="#id/tasbeehFragment"/>
<action android:id="#+id/action_homeFragment_to_galleryFragment"
app:popUpTo="#id/homeFragment"
app:destination="#id/galleryFragment"/>
<action android:id="#+id/action_homeFragment_to_newsFragment"
app:popUpTo="#id/homeFragment"
app:destination="#id/newsFragment"/>
<action android:id="#+id/action_homeFragment_to_settingsFragment"
app:popUpTo="#id/homeFragment"
app:destination="#id/settingsFragment"/>
</fragment>
<fragment
android:id="#+id/namesFragment"
android:name="com.stavro_xhardha.pockettreasure.ui.names.NamesFragment"
android:label="Names of Allah"
tools:layout="#layout/fragment_names"/>
<fragment
android:id="#+id/quranFragment"
android:name="com.stavro_xhardha.pockettreasure.ui.quran.QuranFragment"
android:label="Quran"
tools:layout="#layout/fragment_quran"/>
<fragment android:id="#+id/tasbeehFragment"
android:name="com.stavro_xhardha.pockettreasure.ui.tasbeeh.TasbeehFragment"
android:label="Tasbeeh"
tools:layout="#layout/fragment_tasbeeh"/>
<fragment android:id="#+id/galleryFragment"
android:name="com.stavro_xhardha.pockettreasure.ui.gallery.GalleryFragment"
android:label="Gallery"
tools:layout="#layout/fragment_gallery"/>
<fragment android:id="#+id/newsFragment"
android:name="com.stavro_xhardha.pockettreasure.ui.news.NewsFragment"
android:label="News"
tools:layout="#layout/fragment_news"/>
<fragment android:id="#+id/settingsFragment"
android:name="com.stavro_xhardha.pockettreasure.ui.settings.SettingsFragment"
android:label="Settings"
tools:layout="#layout/fragment_settings"/>
<fragment android:id="#+id/setupFragment"
android:name="com.stavro_xhardha.pockettreasure.ui.setup.SetupFragment"
android:label="Pick country"
tools:layout="#layout/fragment_setup">
<action android:id="#+id/action_setupFragment_to_homeFragment3"
app:destination="#+id/homeFragment"
app:launchSingleTop="true"
app:popUpTo="#+id/treasure_nav"
app:popUpToInclusive="true"/>
</fragment>
</navigation>
And this is my onBackPressed in my MainActivity (and the only one) :
override fun onBackPressed() {
if (drawer_layout.isDrawerOpen(GravityCompat.START)) {
drawer_layout.closeDrawer(GravityCompat.START)
} else {
super.onBackPressed()
}
}
Edit: When i remove the super.onBackPressed() and replace it with :
findNavController(R.id.nav_host_fragment).popBackStack(R.id.homeFragment, false) I achieve what I want. The only problem is that when I am in the homeFragment I want to end the app but I can't.
If my understanding is correct, you want to go back to HomeFragment wherever you are in the navigation flow. For this case you could try registering OnBackPressedCallback on your Fragments via addOnBackPressedCallback, and call popBackStack to navigate to your HomeFragment. Try adding this to Fragments' onViewCreated that need to go back to HomeFragment on backpress:
#Override
public void onViewCreated(#NonNull View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
navController = Navigation.findNavController(view);
requireActivity().addOnBackPressedCallback(getViewLifecycleOwner(), () -> {
navController.popBackStack(R.id.homeFragment, false);
});
return true;
});
If you want to close app when press back in HomeFragment, it's just specified these attributes of the last action that navigates you to this destination:
app:popUpToInclusive to true
app:popUpTo to of the last fragment(SetupFragment) that navigates you here(HomeFragment)
It means change your code like this:
<fragment android:id="#+id/setupFragment"
android:name="com.stavro_xhardha.pockettreasure.ui.setup.SetupFragment"
android:label="Pick country"
tools:layout="#layout/fragment_setup">
<action android:id="#+id/action_setupFragment_to_homeFragment3"
app:destination="#+id/homeFragment"
app:launchSingleTop="true"
app:popUpTo="#+id/setupFragment" // this line changes
app:popUpToInclusive="true" /> // and this requires too
</fragment>

Categories

Resources