In the "System Back after cross navigation to lower hierarchy levels" section of the Navigation Drawer, they say:
If the user navigates to a lower hierarchy screen from the navigation
drawer and the screen has a direct parent, then the Back stack is
reset and Back points to the target screen’s parent. This Back
behavior is the same as when a user navigates into an app from a
notification.
I know the back stack can be reset by starting an activity with FLAG_ACTIVITY_CLEAR_TOP and FLAG_ACTIVITY_NEW_TASK, but that does not seem to be usable here, as it would not create a back stack for Lower 1.1.1.
Any idea how to remove TopView2 from the stack and at the same time add the TopView1 -> Lower 1.1 back stack when starting Lower 1.1.1 ? I'm expecting a simple solution, considering this is mentioned in the Navigation Drawer document.
EDIT Synthesized version:
1)Declare the hierarchical navigation structure of your app in your manifest file.
2)The root activity of your app should perform a view switch between the TopViews in your app hierarchy.*
3) Activities lower in the hierarchy should perform 'Selective Up' navigation.
*Important: you should not add transactions to the back stack when the transaction is for horizontal navigation such as when switching tabs or navigation drawer top views.
Full description:
You should avoid the use of Intent Flags with the new navigation patters like Navigation Drawer for the next reasons:
Intent Flags are not really an API.
Some flags only work in exact combinations.
Many flags are not relevant for most 3rd party apps.
Overlap/conflict with activity launchMode.
Confusing documentation.
Implementation can become a process of trial and error.
Instead, opt for the new Navigation API:
Native Up navigation for Jelly Bean and above.
Based on hierarchical metadata specified for each <activity> in your manifest.
The support library provides equivalent functionality for earlier android versions via NavUtils.
TaskStackBuilder offers additional utilities for cross-task navigation.
So to answer your question the general idea is:
1) You need to declare the logical parent of each activity in your manifest file, using the android:parentActivityName attribute (and corresponding <meta-data> element) like:
<application ... >
...
<!-- The main/home activity (it has no parent activity) -->
<activity
android:name="com.example.myapp.RootDrawerActivity" ...>
...
</activity>
<!-- A child of the root activity -->
<activity
android:name="com.example.myapp.Lower11 "
android:label="#string/lower11"
android:parentActivityName="com.example.myapp.RootDrawerActivity" >
<!-- Parent activity meta-data to support 4.0 and lower -->
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="com.example.myapp.RootDrawerActivity" />
</activity>
<activity
android:name="com.example.myapp.Lower111 "
android:label="#string/lower111"
android:parentActivityName="com.example.myapp.Lower11" >
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="com.example.myapp.Lower11" />
</activity>
</application>
2) In your root Activity, drawer item selection should initiate a 'view switch' action by replacing the Activity's current fragment content.
A view switch follows the same basic policies as list or tab navigation in that a view switch does not create navigation history.
This pattern should only be used at the root activity of a task, leaving some form of Up navigation active for activities further down the navigation hierarchy (In your case Lower 1.1 & Lower 1.1.1). The important thing here is that you don't need to remove TopView2 from the stack but to perform a view switch as commented before passing the position of the view (or fragment id) as an extra.
In your root Activity do something like this:
#Override
protected void onDrawerItemSelected(int position) {
// Update the main content by replacing fragments
CategoryFragment fragment = new CategoryFragment();
Bundle args = new Bundle();
args.putInt(RootDrawerActivity.ARG_SORT, position);
fragment.setArguments(args);
FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.beginTransaction()
.replace(R.id.content_frame, fragment).commit();
// Update selected item and title, then close the drawer
setDrawerItemChecked(position, true);
setTitle(getResources().getStringArray(R.array.drawer_array)[position]);
closeDrawer();
}
3) Then lower in the hierarchy (i.e. Lower1.1) you should perform 'Selective Up' navigation, recreating the task stack in the process.
Selective Up allows a user to jump across an app's navigation hierarchy at will. The application should treat this as it treats Up navigation from a different task, replacing the current task stack (This is what you want!) using TaskStackBuilder or similar. This is the only form of navigation drawer that should be used outside of the root activity of a task.
#Override
protected void onDrawerItemSelected(int position) {
TaskStackBuilder.create(this)
.addParentStack(RootDrawerActivity.class)
.addNextIntent(new Intent(this, RootDrawerActivity.class)
.putExtra(RootDrawerActivity.ARG_SORT, position))
.startActivities();
}
Refs:
http://developer.android.com/training/implementing-navigation/ancestral.html
https://speakerdeck.com/jgilfelt/this-way-up-implementing-effective-navigation-on-android
Related
I have a question regarding to NavController:
There is the scheme (sorry for my painting, hope it helps :D)
I have MainActivity and BottomNavigationView with 3 tabs: A, B, C.
When I tap to A it opens Fragment A1 and there is next button which opens Fragment A2.
In Fragment A2 there are buttons back, next, no problems with navigation here.
The problem is when I need to navigate from Fragment A2 to section B the same like a click on B in BottomNavigationView.
The problem is that it's different graph, how to switch them?
My ideas:
I found work-around: requireActivity().bottomBar.selectedItemId = R.id.graph_b but it's not good idea.
I would like to achieve it using navigation component. I was trying to do findNavController().navigate(R.id.graph_b), but it leads to crash:
java.lang.IllegalArgumentException: navigation destination
com.my.app.staging:id/graph_b is unknown to this NavController
How to make it using NavController ?
There is Google Example project with all architecture:
https://github.com/googlesamples/android-architecture-components/tree/master/NavigationAdvancedSample
And to simplify my question I've added a button in this project, where on click should opens different screen:
You graphs are defined in your Parent Activity and thats where you will be able to control them.
Your first way is actually the solution. Your graphs are defined within your activity. When you are inside any destinations inside your graph A (say A1, A2 etc.) they have no knowledge of the your other graphs B & C. The only way to get to the graph is through parent activity, and hence
requireActivity().bottomBar.selectedItemId = R.id.graph_b
The second way that you have tried will definitely not work because findNavController().navigate(R.id.graph_b) is used when you have nested navigation. In other words graph_b should be inside graph_a which is not your case.
That being said what you can do is just write
requireActivity().bottomBar.selectedItemId = R.id.graph_b
fancier. Instead of running inside your fragments, its better to run inside your activity.
// In your fragment
requireActivity?.moveToGraphB()
// and in your activity
fun moveToGraphB() {
bottomBar.selectedItemId = R.id.graph_b
}
Or more more fancier would be using SharedViewModel which I don't think is necessary.
In graph B you can add the line
where nav_graphA is the name of the graph you want to navigate to when the button is clicked. Then you can add
` <fragment
android:id="#+id/aboutFragment"
android:name="com.mycompany.aboutFragment"
tools:layout="#layout/fragment_about"
android:label="About" >
<action
android:id="#+id/action_aboutFragment_to_nav_graphA"
app:destination="#id/nav_graphA" />
</fragment>
`
to create the action to navigate when the button is clicked.
I've been following the docs from Navigation Architecture Component to understand how this new navigation system works.
To go/back from one screen to another you need a component which implements NavHost interface.
The NavHost is an empty view whereupon destinations are swapped in and
out as a user navigates through your app.
But, it seems that currently only Fragments implement NavHost
The Navigation Architecture Component’s default NavHost implementation is NavHostFragment.
So, my questions are:
Even if I have a very simple screen which can be implemented with an Activity, in order to work with this new navigation system, a Fragment needs to be hosted containing the actual view?
Will Activity implement NavHost interface in a near future?
--UPDATED--
Based on ianhanniballake's answer, I understand that every activity contains its own navigation graph. But if I want to go from one activity to another using the nav component (replacing "old" startActivity call), I can use activity destinations. What is activity destinations is not clear to me because the docs for migration don't go into any detail:
Separate Activities can then be linked by adding activity destinations to the navigation graph, replacing existing usages of startActivity() throughout the code base.
Is there any benefit on using ActivityNavigator instead of startActivity?
What is the proper way to go from activities when using the nav component?
The navigation graph only exists within a single activity. As per the Migrate to Navigation guide, <activity> destinations can be used to start an Activity from within the navigation graph, but once that second activity is started, it is totally separate from the original navigation graph (it could have its own graph or just be a simple activity).
You can add an Activity destination to your navigation graph via the visual editor (by hitting the + button and then selecting an activity in your project) or by manually adding the XML:
<activity
android:id="#+id/secondActivity"
android:name="com.example.SecondActivity" />
Then, you can navigate to that activity (i.e., start the activity) by using it just like any other destination:
Navigation.findNavController(view).navigate(R.id.secondActivity);
I managed to navigate from one activity to another without hosting a Fragment by using ActivityNavigator.
ActivityNavigator(this)
.createDestination()
.setIntent(Intent(this, SecondActivity::class.java))
.navigate(null, null)
I also managed to navigate from one activity to another without hosting a Fragment by using ActivityNavigator.
Kotlin:
val activityNavigator = ActivityNavigator( context!!)
activityNavigator.navigate(
activityNavigator.createDestination().setIntent(
Intent(
context!!,
SecondActivity::class.java
)
), null, null, null
)
Java:
ActivityNavigator activityNavigator = new ActivityNavigator(this);
activityNavigator.navigate(activityNavigator.createDestination().setIntent(new Intent(this, SecondActivity.class)), null, null, null);
nav_graph.xml
<fragment android:id="#+id/fragment"
android:name="com.codingwithmitch.navigationcomponentsexample.SampleFragment"
android:label="fragment_sample"
tools:layout="#layout/fragment_sample">
<action
android:id="#+id/action_confirmationFragment_to_secondActivity"
app:destination="#id/secondActivity" />
</fragment>
<activity
android:id="#+id/secondActivity"
android:name="com.codingwithmitch.navigationcomponentsexample.SecondActivity"
android:label="activity_second"
tools:layout="#layout/activity_second" />
Kotlin:
lateinit var navController: NavController
navController = Navigation.findNavController(view)
navController!!.navigate(R.id.action_confirmationFragment_to_secondActivity)
I need to navigate from one page to its corresponding previous page(there are more than one previous page for current page in my app) . How can I implement it using Up Navigation / Back button.
Android documentation
Setup parent in Manifest file and then on Activity
#Override
public boolean onOptionsItemSelected(MenuItem item)
{
switch(item.getItemId())
{
case android.R.id.home:
NavUtils.navigateUpFromSameTask(this);
return true;
}
return super.onOptionsItemSelected(item);
}
or You can call finish() instead of
NavUtils.navigateUpFromSameTask(this)
You have to use either ActionBar or ToolBar.
Then simply add setDisplayHomeAsUpEnabled(true).
On activities it is by default getting back to previous using back button, but no up button. If you want up, put in Manifest:
android:parentActivityName=".MainActivity"
on Fragments:
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.addToBackStack("Tag");
. . .
You can specify parent activities in the manifest
Beginning in Android 4.1 (API level 16), you can declare the logical parent of each activity by specifying the android:parentActivityName attribute in the element
If your app supports Android 4.0 and lower, include the Support Library with your app and add a element inside the . Then specify the parent activity as the value for android.support.PARENT_ACTIVITY, matching the android:parentActivityName attribute.
For example:
<application ... >
...
<!-- The main/home activity (it has no parent activity) -->
<activity
android:name="com.example.myfirstapp.MainActivity" ...>
...
</activity>
<!-- A child of the main activity -->
<activity
android:name="com.example.myfirstapp.DisplayMessageActivity"
android:label="#string/title_activity_display_message"
android:parentActivityName="com.example.myfirstapp.MainActivity" >
<!-- The meta-data element is needed for versions lower than 4.1 -->
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="com.example.myfirstapp.MainActivity" />
</activity>
</application>
With the parent activity declared this way, you can use the NavUtils APIs to synthesize a new back stack by identifying which activity is the appropriate parent for each activity.
You can refer How to implement the Android ActionBar back button? for implement UpNavigation in your activity
I hope it helps.
In my app, each activity has the same menu in the action bar, which provides access to the parameters activity.
When I am in the parameters activity, I would like to get to my parent activity by clicking on the left-orriented arrow like in my other activities.
The unique parent of others activities is defined in manifest.xml.
But for the parameters it's impossible as it has multiple parents:
mother > child > parameters is possible
mother > parameters is also possible!
We can find this comportment in the gmail app:
main > parameters, in which you can get back to main
main > email > parameters, in which you can get back to the specific email you were looking at.
So my question is how to get this gmail comportment? Can we change dynamically the parent activity of parameters?
There are two ways in which you can accomplish this:
Use a TaskStackBuilder and specify the parent chain in the manifest using android:parentActivityName and the associated support-v4 meta-data tag as described in the corresponding Providing Up Navigation training. This is the recommended method as it obeys the hierarchical task stack.
Provide some extra into the launch intent and navigate up depending on that custom flag. Note that launching activities through this mechanism will not show the back animation of the current activity closing.
Comments mess up well indented answer so I anwser my question :
I finally ended up with a simple custom code,
because I had already visited a few times your link (http://developer.android.com/training/implementing-navigation/ancestral.html#NavigateUp) and it didnt helped me much.
Here's what I did :
(in the else of http://developer.android.com/training/implementing-navigation/ancestral.html#BuildBackStack)
// particular case
// if this activity is the Parameter/Help activity, just finish it
if(this.getClass().equals(ActivityParametre.class) || this.getClass().equals(ActivityAide.class)){
finish();
return true;
}
// general case
// otherwise, use pre defined code
else {
NavUtils.navigateUpTo(this, upIntent);
}
I have an app with multiple activities, each of which may employ multiple fragments.
One activity (called IBAsTabsActivity) extends SherlockFragmentActivity and I am using some example code where a different fragment gets loaded up depending on which tab is selected. One of the possible fragments is called "SearchableListFragment". This is all working fine. I have a "home" activity with two buttons, one to take me to IBAsTabsActivity and another to take me to AnAlternativeActivity. If I press the button to take me to IBAsTabsActivity and look at several tabs - then if I press the back button I jump straight to "home".
Now I want to get AnAlternativeActivity up and running. It has no tabs and so does not employ any Sherlock code. Instead I have this:
public class AnAlternativeActivity extends FragmentActivity implements OnClickListener
{
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.blank_fragment_holder);
FragmentManager fm = getSupportFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
SearchableListFragment fb = new SearchableListFragment();
ft.replace(R.id.fragment_holding_layout, fb);
ft.addToBackStack("replacingFragmentA");
ft.commit();
}
This all works fine in as much as if I click on the button on the home screen to call AnAlternativeActivity, then I see the SearchableListFragment() (inside fragment_holding_layout) and its contents correctly. But now if I click the back button the fragment within fragment_holding_layout disappears, but leaves AnAlternativeActivity still running. I have to press the back button again in order to exit AnAlternativeActivity and get back to my home activity.
I could probably cook up some ugly code to resolve this issue - but I suspect that there is a standard way to cope with this scenario. Any ideas?
You should be able to use the notion of parent-activity to resolve this easily. All you need to do is to tell Android that HomeActivity is the parent of your AnAlternativeActivity and the navigation will be handled by you. In your AndroidManifest.xml file add android:parentActivityName attribute for AnAlternativeActivity like so:
<activity
android:name="com.mick.AnAlternativeActivity"
android:label="#string/activity_title"
android:parentActivityName="com.mick.HomeActivity" >
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="com.example.mick.HomeActivity" />
</activity>
You need the regular attribute for new devices and meta-data tag for older devices. More information can be found here: http://developer.android.com/training/basics/firstapp/starting-activity.html (look under Add it to the manifest subheading).
Within AnAlternativeActivity's onPause you could call finish() to end it.
public void onPause() {
super.onPause();
finish();
}
Your call to ft.addToBackStack("replacingFragmentA"); seems to be the cause for the need for tapping 'Back' twice. Adding the transaction to the back stack will have that effect: adding a layer in the history stack.
So when you press 'Back' the first time that transaction will be 'undone' so to speak, and will replace your SearchableListFragment with the R.id.fragment_holding_layout. The second time you press 'Back' will move from the AnAlternativeActivity back to your 'home' activity.