Let's say I have two activities called A_Activity and B_Activity. A_Activity has 3 fragments named A_Fragment, B_Fragment, and C_Fragment. A_Activity acts as a base activity for the three fragments.
From B_Fragment, it is possible to navigate to B_Activity and from B_Activityit is possible to navigate up to B_Fragment. from A_Activity it is possible to navigate to A_Fragment, B_Fragment, orC_Fragment. Lastly, from A_Fragment, B_Fragment, or C_Fragment, it is possible to navigate to any other fragment.
Here's an arbitrary representation of the hierarchy of activities and their respective fragments:
A_Activity
A_Fragment
B_Fragment
B_Activity
C_Fragment
Let it also be a given that A_Fragment is the default fragment for A_Activity.
My current solution to this is: in B_Activity's home icon selected, I am creating a new intent:
Intent intent = new Intent(B_Activity.this, A_Activity.class);
intent.putExtra("data_to_start_B_Fragment", "B"); // instead of starting A Fragment
startActivity(intent);
What is the proper way to navigate to B_Fragment from B_Activity?
From B_Fragment, starting B_Activity using startActivityForResult(...) by getApplicationcontext not getActivity().getApplicationContext().
In Activity B, you use setResult(data here) to return fragment and finish ActivityB to back fragment B.
From B_Activity to B_Fragment (if you start B_Activity from B_Fragment), you just need to detect action from either home button or system back button and then kill the Activity by calling
finish()
e.g. in B_Activity
#Override
public boolean onOptionsItemSelected(MenuItem item) {
if (id == android.R.id.home) {
finish();
}
return super.onOptionsItemSelected(item);
}
Related
I have an activity A which uses a fragment A which has a list. The activity A can call on a search activity. The problem that I am seeing is if I were to go the search activity and then go back to the activity A, from there fragment A gets loaded. If I select an item from Fragment A i get taken to Fragment B, if i were to press the back button i have to click it 2 or 3 times. Any ideas? Do i need to start the search activity with a parameter so it does not add to the back stack. I have tried the Flag to Intent.FLAG_ACTIVITY_CLEAR_TOP when starting the search activity but the issue occurs.
Java:
Activity A:
public void popFragment(){
if(getSupportFragmentManager().getBackStackEntryCount() > 1) {
getSupportFragmentManager().popBackStack();
getSupportFragmentManager().executePendingTransactions();
}
}
Fragment A:
private void showSearchActivity(){
Intent intent = new Intent(context, SearchActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivityForResult(intent, 0)
}
I want to add back navigation to toolbar. I need to get from a fragment in an activity to a specific fragment in another activity.
It looks a little like this, where every orange line means navigating to a new activity or fragment:
How do I move from fragment B to fragment A from OtherActivity?
Consider these steps:
From Activity 1 holding Fragment A , you want to directly load Fragment B in Activity 2.
Now, I am thinking first, then you press a button in Fragment A, you can directly go to Activity B.
Then it means, you can simply load Fragment B as soon as you arrive in Activity 2.
Since you are dealing with back navigation (I believe you mean the upNavigation?), you can override the following:
But watch clearly, because if you need to load an exact fragment in Activity 2, you need to know somehow:
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
Intent intent = new Intent(this, Activity2.class);
intent.putExtra("frag", "fragmentB");
startActivity(intent);
return true;
default:
return super.onOptionsItemSelected(item);
}
}
As you can see, when you click the back arrow on the toolbar, we pass a value through our intent to identity which fragment we want to load.
Next, in your Activity2, simply get the intent extra and do a switch or an if statement:
#Override
public void onResume(){
super.onResume();
Intent intent = getIntent();
String frag = intent.getExtras().getString("frag");
switch(frag){
case "fragmentB":
//here you can set Fragment B to your activity as usual;
fragmentManager.beginTransaction().replace(R.id.container_body, new FragmentB()).commit();
break;
}
}
From here, you should have your Fragment B showing in Activity 2.
Now you can handle the same thing while inside Activity 2 to decide where to go when a user clicks the back home arrow!
I hope this helps you get an idea.
Note: I thought about the interface approach and realized it is not necessary since this can be done easily with this approach!
To navigate from one Activity to another Activity's Fragment, with Kotlin version 1.4.0 and, for example, calling a click listener on a button it works so:
binding.yourButton.setOnClickListener {
supportFragmentManager.beginTransaction().replace(R.id.yourLayout, NameOfYourFragment()).commit()
}
Use this code to change your fragment
fragmentManager.beginTransaction().replace(R.id.container_body, new FragmentC()).commit();
and to show navigation on custom toolbar add
Toolbar toolbar = (Toolbar) rootView.findViewById(R.id.toolbar);
((AppCompatActivity)getActivity()).setSupportActionBar(toolbar);
((AppCompatActivity)getActivity()).getSupportActionBar().setDisplayHomeAsUpEnabled(true)
((AppCompatActivity)getActivity()).getSupportActionBar().setHomeButtonEnabled(true);
I have three activites, lets call them A, B, and C.
I initially have activity A passing data with intent and calling activity B to open, displaying the data passed from activity A.
Now the issue is when I open activity C from activity B, and use the up navigation that I set up by setting activity B as the parent of Activity C in manifest, none of the data is displayed from Activity A in Activity B. If i simply make a button and call finish(); on the button instead, and not use the up navigation, the activity still contains all data from activity A just how I want it in Activity B.
I'm assuming this has to do with the lifecycle of using up navigation? I even tried using the intent from activity B to C by passing data, and then onResult have it return back to activity B, but it seems the onActivityResult is never called when up navigation is clicked. Any ideas? Maybe I can override this up navigation to just call finish(), like my button does, and nothing more?
Assuming you already have the parent activities defined at the Manifest, make sure you override the following methods:
#Override
public void onCreate(Bundle savedInstanceState) {
// You code here
getActionBar().setDisplayHomeAsUpEnabled(true);
// or getSupportActionBar().setDisplayHomeAsUpEnabled(true) if using support actionbar, i.e., for targets < 3.0;
}
Now you will be able to catch the action of the "Up" button with the Id android.R.id.home.
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
NavUtils.navigateUpFromSameTask(this);
return true;
}
return super.onOptionsItemSelected(item);
}
So at the moment I have an activity that can be reached from two different activities, the problem is that I can only set one activity as the parent activity in the manifest XML file. Obviously this is bad UX/UI design because the activity may send the user back to the wrong activity they were at previously and so I'm trying to dynamically set which activity is the parent activity.
The trouble is I'm not quite sure how to go about this, whether in code or XML so any pointers are appreciated. :)
For future readers here's an example of how to actually implement the official/proper solution as per the developer guides (scroll to the paragraph beginning with "This is appropriate when the parent activity may be different...").
Note that this solution assumes you are using the Support Library to implement your ActionBar and that you can at least set a 'default' parent Activity in your manifest XML file to fallback on if the Activity you are backing out of is in a 'task' that doesn't belong to your app (read the linked docs for clarification).
// Override BOTH getSupportParentActivityIntent() AND getParentActivityIntent() because
// if your device is running on API 11+ it will call the native
// getParentActivityIntent() method instead of the support version.
// The docs do **NOT** make this part clear and it is important!
#Override
public Intent getSupportParentActivityIntent() {
return getParentActivityIntentImpl();
}
#Override
public Intent getParentActivityIntent() {
return getParentActivityIntentImpl();
}
private Intent getParentActivityIntentImpl() {
Intent i = null;
// Here you need to do some logic to determine from which Activity you came.
// example: you could pass a variable through your Intent extras and check that.
if (parentIsActivityA) {
i = new Intent(this, ActivityA.class);
// set any flags or extras that you need.
// If you are reusing the previous Activity (i.e. bringing it to the top
// without re-creating a new instance) set these flags:
i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
// if you are re-using the parent Activity you may not need to set any extras
i.putExtra("someExtra", "whateverYouNeed");
} else {
i = new Intent(this, ActivityB.class);
// same comments as above
i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
i.putExtra("someExtra", "whateverYouNeed");
}
return i;
}
NOTE: If you do not set a default parent Activity in the manifest XML file then you'll also need to implement onCreateSupportNavigateUpTaskStack() since the system will have no idea how to generate a backstack for your task. I have not provided any example for this part.
My thoughts on the finish() type solutions
On my searching for a solution to this problem I came across a few answers that advocate the strategy of overriding onOptionsItemSelected() and intercepting the android.R.id.home button so they could simply finish() the current Activity to return to the previous screen.
In many cases this will achieve the desired behavior, but I just want to point out that this is definitely not the same as a proper UP navigation. If you were navigating to the child Activity through one of the parent Activities, then yes finish() will return you to the proper previous screen, but what if you entered the child Activity through a notification? In that case finish()ing by hitting the UP button would drop you right back onto the home screen or whatever app you were viewing before you hit the notification, when instead it should have sent you to a proper parent Activity within your app.
Like this way you can navigate dynamically to your parent activity:
getActionBar().setDisplayHomeAsUpEnabled(true);
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
// Respond to the action bar's Up/Home button
case android.R.id.home:
finish();
return true;
}
return super.onOptionsItemSelected(item);
}
NOTE: It redirects you to the activity or fragment where you came from, no matter whether it's a parent or not. Clicking on the action bar Up/Home button will just finish the current activity.
There are two concepts in play here 'Up' and 'Back'. 'Back' is the obvious one: take me to where I was just before I came here. Usually you don't need to be concerned with 'Back', as the system will handle it just fine. 'Up' is not so obvious - it's analogous to Zoom Out - from an element to the collection, from a detail to the wider picture.
Which of these fits your use case?
As per comment below: the up button pulls the destination from the android manifest, but it can be customized programmatically.
The method to override is getParentActivityIntent.
Here is my code and works perfectly fine.
#Override
public Intent getParentActivityIntent() {
Intent parentIntent= getIntent();
String className = parentIntent.getStringExtra("ParentClassSource");
Intent newIntent=null;
try {
//you need to define the class with package name
newIntent = new Intent(OnMap.this, Class.forName(className));
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return newIntent;
}
From the parent activities;
Intent i = new Intent(DataDetailsItem.this, OnMap.class);
i.putExtra("ParentClassSource", "com.package.example.YourParentActivity");
startActivity(i);
To find out how to use Up Navigation properly see this Android Dev Guide.
Note that there is a big flaw in the above Android Dev Guide as the NavUtils functions work differently for ICS(and lower) and JellyBean(and higher). This flaw in NavUtils is explained beautifully here.
Generally, a 'detail' type of activity will have to provide the 'up' navigation if it has nested/related contents. The 'back' navigation is handled by the system so you really don't have to worry about it.
Now for the extra effort to support the 'up' navigation, there are a few ways of doing it:
Activity that has the parent activity defined in the AndroidManifest.
Your Android Manifest
---------------------
<activity
android:name="com.example.app.DetailActivity"
android:parentActivityName="com.example.app.MainActivity" >
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="com.example.app.MainActivity" />
</activity>
Your Detail Activity
--------------------
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
NavUtils.navigateUpFromSameTask(this);
return true;
}
return super.onOptionsItemSelected(item);
}
This works well if there's only one parent activity meaning if the (DetailActivity) always gets launched from (MainActivity). Otherwise this solution will not work if (DetailActivity) gets launched from different places.
More here: http://developer.android.com/training/implementing-navigation/ancestral.html
(Easier and Recommended) Activity with Fragment and Fragment back-stack:
Your Detail Activity
--------------------
protected void replaceFragment(Bundle fragmentArguments, boolean addToBackStack) {
DetailFragment fragment = new DetailFragment();
fragment.setArguments(fragmentArguments);
// get your fragment manager, native/support
FragmentTransaction tr = fragmentManager.beginTransaction();
tr.replace(containerResId, fragment);
if (addToBackStack) {
tr.addToBackStack(null);
}
tr.commit();
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
finish();
return true;
}
return super.onOptionsItemSelected(item);
}
In this solution, if the user presses 'back', the fragment will be popped from the fragment backstack and the user is taken back to the previous fragment while still remaining in the same activity. If the user presses the 'up', the activity dismisses and the user is lead back to the previous activity (being the parent activity). The key here is to use the Fragment as your UI and the activity as the host of the fragment.
Hope this helps.
You can override the Up button to act like the back button as following:
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
onBackPressed();
return true;
}
return super.onOptionsItemSelected(item);
}
You will need to keep track of the parent activity. One way to do this is by storing it as an extra in the intent you create to start the child activity). For example, if Activity A starts Activity B, store A's intent in the intent created for B. Then in B's onOptionsItemSelected where you handle the up navigation, retrieve A's intent and start it with the desired intent flags.
The following post has a more complex use-case with a chain of child activites. It explains how you can navigate up to the same or new instance of the parent and finish the intermediate activities while doing so.
Android up navigation for an Activity with multiple parents
Kotlin 2020
My activity launches from different activities so the AndroidManifest only works with 1 parent activity.
You can return to the previous activity like this:
supportActionBar?.setDisplayHomeAsUpEnabled(true)
override fun onOptionsItemSelected(item: MenuItem?): Boolean {
when(item!!.itemId){
android.R.id.home -> {
finish()
return true
}
}
return super.onOptionsItemSelected(item)
}
In the main activity ActivityA I replace FragmentA by fragment FragmentB. From FragmentB the user can start a new activity ActivityB. By hitting the back button in ActivityB, ActivityA is displayed showing FragmentA. I was expecting to see FragmentB with its last state. Do I have to save the state of the previous activities separately to provide this behaviour?
ActivityA(FragmentA) -> ActivityA(FragmentB) -> ActivityB
BACK
ActivityA(FragmentB)
In the main activity I set the current fragment using:
FragmentManager fragmentManager = getFragmentManager();
fragmentManager.beginTransaction()
.replace(R.id.a_main_frame_content, new FragmentB())
.addToBackStack(null)
.commit();
From the fragment I start a new activity using:
Intent intent = new Intent(getActivity(), ActivityB.class);
getActivity().startActivity(intent);
ActivityA is set as parent activity for ActivityB to provide proper navigation.
[UPDTATE] It looks like the problem lies in the different behaviour of navigating back and navigating up. If I navigate back, the activity is displayed in its last state while navigating up forces the activity to recreate.
Lets try this:
In the intent of the parentActivity(if you can set it before you create parentActivity its best, otherwise you may have to use setIntent):
currentActivityIntent.putExtra("random-unique-key-for-each-activity",
random-unique-key-for-each-activity);
And before you create a child activity, u put following in a map:
myKeyIntentMap.put(random-unique-key-for-each-activity, currentActivityIntent);
In the method triggered on "Up" event :
{
String parentKey = currentActivity.parentActivity.getIntent.getStringExtra("random-unique-key-for-each-activity");
Intent intentToLaunch = (Intent)myKeyIntentMap.get(parentKey);
intentToLaunch.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP|Intent.FLAG_ACTIVITY_SINGLE_TOP );
startActivity(intentToLaunch);
}
This way, using the intent, even if your History Stack is A-someAct1-someAct2-B, and u launch intent resolving to A, it will be "brought to front" killing someActs.
P.S. I havent done any null checks and havent kept in mind the exact method names, just given you an approach.
Behaviour of "up" is sometimes misleading indeed. When I faced similar problem I preferred to save my time and not deal with saving states.
You can quickly solve it by catching your navigation "up" event in your Activity:
#Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home) {
onBackPressed();
}
return true;
}
This is happening because when you are moving to ActivityB activityA is going to puse and destroy state respectively, thus when you back to activity ActivityA ActivityA is starting again thus you are getting fragmentA as view. you need to save the state using sharedPfer. Use some flag to save the state in your onCreateView() check the state and set correct fragment for the view. Hope you have got my point.
public static final String PREFS_NAME = "mypref";
boolean isVisited;
//check sharedpref
SharedPreferences settings = getSharedPreferences(PREFS_NAME, 0);
isVisited= settings.getBoolean("isVisited", false);
if(!isVisited){
// set fragmentA
}else{
// set fragmentB
}
// inside fragment transaction block
Editor edit = settings.editor();
isVisited.setBoolean(true);
edit.commit();