I have an activity (AppCompatActivity) (called Activity) and two fragments (called Fragment A and Fragment B).
When Activity is lunched, I use setContentView and Fragment A was generated.
When I try to start Activity with specific parameter I lunch Fragment B.
Fragment A and Fragment B has the same layout, I change only the data to be rendered.
In Fragment A, I have a menu with 1 item, this one performs an action on my view: change list to a grid layout.
In fragment B, I have this menu too, but, when I click on the item nothing changes. So I've tried this in debug mode. I've seen that when I press item in the menu, for Fragment B, this called menu action of Fragment A.
How can I resolve this cycle?
When you select a menu item, the system will dispatch that event to the active Activity as well as all currently active Fragments. The event will be dispatched to the Fragments in the order they were added to the Activity.
If Fragment A and Fragment B both include an onOptionsItemSelected() implementation that handles the same R.id constant, this will cause the problem you're seeing.
For example, imagine Fragment A has this code:
#Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == R.id.action_settings) {
// do some Fragment A thing
}
...
}
And imagine Fragment B has this code:
#Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == R.id.action_settings) {
// do some Fragment B thing
}
...
}
Even if the user clicks on R.id.action_settings inside Fragment B, the system will dispatch the event to Fragment A, and Fragment A will happily handle it!
There are a few different solutions to this problem.
First, you could change your menus in your two fragments so that they have the same text labels but use different android:id attributes (and then update your Fragment code accordingly). Now only the "right" fragment will be able to handle the item selection event.
Second, you could update your onOptionsItemSelected() code to determine whether the Fragment is currently active, and ignore the event if it is not:
#Override
public boolean onOptionsItemSelected(MenuItem item) {
if (!isAdded() || !isVisible()) {
return false;
}
if (item.getItemId() == R.id.action_settings) {
// do some Fragment A thing
}
}
Finally, you could use dynamically-generated IDs for your menu items. I'd recommend against this approach as it is much more complicated than the others.
Related
I am using the navigation component architecture. I have a menu item in the action bar where the user can click to go to the settings. I am using a one activity to many fragments approach. So this action bar, with the menu item is across all of my fragments.
Lets say I have 3 fragments. and fragment A is the main fragment. While I am in the settings preferences the 'UP' button always takes me back to fragment A. Even if I called it from frag B or C.
Here's what I mean by the up button (as it is called here):
And here is my actual NavGraph
From this you can see that Frag A is linked by an action to settings. I did this because this is the home fragment where the host activity for the fragments begins. It doesn't make a difference anyway I deleted this action and it still behaved in the same way.
Here is my onOptionsItemSelected
//Preform action when selected
#Override
public boolean onOptionsItemSelected(MenuItem item) {
return NavigationUI.onNavDestinationSelected(item, navController)
|| super.onOptionsItemSelected(item);
}
So, how do I, if going to settings from Fragment C, get back to fragment C. Instead of Fragment A?
NavigationUI contains a onNavDestinationSelected method for handling menu items. This method pops the whole back-stack by default. The fix is to add android:menuCategory="secondary" to your xml for the items in your menu as is mentioned here:
So, how do I, if going to settings from Fragment C, get back to fragment C. Instead of Fragment A?
I am assuming that you already added a navigation action from Fragment C to Settings fragment so that you can navigate from Fragment C to the SettingsFragment.
So, here is a demo sample of this action at the navigation graph:
<fragment
android:id="#+id/cFragment"
android:name="...."
android:label="fragment_c"
tools:layout="#layout/fragment_c" >
....
<action
android:id="#+id/action_cFragment_to_settingsFragment"
app:destination="#id/settingsFragment"
app:popUpTo="#id/cFragment" />
</fragment>
Now you want to hit the up/home button on the SettingsFragment to back to Fragment C (or whatever fragment invokes the SettingsFragment.
You can use the back stack to do that; where the top fragment at the back stack is now FragmentC which is determined by the action action_cFragment_to_settingsFragment.
You can use onBackPressed() of the sole activity to achieve this, so that now the SettingFragmentcan back to whatever next fragment at the back stack.
To do that: Create the SettingsFragment like below:
public class SettingsFragment extends Fragment {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_settings, container, false);
}
#Override
public void onViewCreated(#NonNull View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
setHasOptionsMenu(true);
((AppCompatActivity) requireActivity()).getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}
#Override
public boolean onOptionsItemSelected(#NonNull MenuItem item) {
if (item.getItemId() == android.R.id.home) {
requireActivity().onBackPressed(); // Back to the next fragment at the back stack
return true;
}
return super.onOptionsItemSelected(item);
}
}
Note: if you see the home button on other fragments you can remove it by:
setHasOptionsMenu(false);
((AppCompatActivity) requireActivity()).getSupportActionBar().setDisplayHomeAsUpEnabled(false);
I am creating an android application with three tabs using PageSlidingtabStrip as a library to create a swipe view.And it has three fragments.Each fragments has a list view.When the item of the listview is clicked it opens an activity and display the details.
The problem is how can i come back to the fragment in the main screen using back button in actionbar in the activity
And how can i go to the corresponding Fragment(Tab)
Try something like this :
#Override
public void onBackPressed() {
// TODO Auto-generated method stub
super.onBackPressed();
Intent intent = new Intent(YourCurrentClass.this , ClassThatYouWantToGo.class);
startActivity(intent)
}
Or actually like #TommyTopas said, you can just Override onBackPressed and put this.finish();.
EDIT
As I've understood you want to use a button on your AcitonBar, then you have tod o something like this :
First set the HomeButton enabled doing :
getActionBar().setDisplayHomeAsUpEnabled(true); Then Override onOptionsItemSelected
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
// or onBackPressed();
this.finish()
}
return true;
}
As I understand, when you return to the "Tab" Activity, you want to display the same tab in which the list item had been clicked. What you can do is, when a list item in any tab is clicked, save the tab number in onSavedInstanceState(), and when the Activity is recreated, then set the previously selected tab (if one was selected previously). You will get the savedInstanceState that you saved in onSavedInstanceState() back in the onCreate() of the same Activity.
You can provide an Up navigation by writing getActionBar().setDisplayHomeAsUpEnabled(true); and then in the onOptionsItemSelected method in the activity, if the item's id is android.R.id.home call the activity's method onBackPressed(); which will close your current activity and come back to your fragment
I have a simple 2 activity application. The main activity populates a listFragment, and the second activity populates a fragment with fields to add a custom object (list items) to the main activity.
In the second activity I have a "save" icon in the action bar. I'm trying to figure out how to listen for this button click in the fragment, so I can package up the textFields and pass it back to the activity via the interface.
I tried to override onOptionItemSelectedbut it doesn't ever hit. How would I handle this?
Okay, so the trick is in the fragments onCreate method, you have to call
setHasOptionsMenu(true);
then all you have to do is override the onOptionsItemSelected in the fragment, and handle the action bar click there!!
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_save : {
Log.i(TAG, "Save from fragment");
return true;
}
}
return super.onOptionsItemSelected(item);
}
I'm using a navigation drawer in a main activity with many fragments
For example :
I have fragment 1 for presentation
And fragment2 that contains a listView.
If I click an item in the listView it will launch another activity
When i click the action bar back button i want to go to the main activity fragment2
But the fragment1 is used
How can I use the second fragment
Thanks for your answers
In your second activity's onCreate, put(although i think you already have) :
getActionBar().setDisplayHomeAsUpEnabled(true);
and inside second activity's options handling(i think you've done this too) :
#Override
public boolean onOptionsItemSelected(MenuItem item)
{
switch (item.getItemId())
{
case android.R.id.home:
//perhaps use intent if needed but i'm sure there's a specific intent action for up you can use to handle
finish();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
and inside your first activity's onCreate's intent handler for example :
if(Intent.ACTION_VIEW.equals(action))
you set the current page of the viewpager with viewpager.setCurrentItem(position). If you want to change the page for different items you could use intent.putExtra() in your second activity and then retrieve it in your first activity.
I have added the code below in my second activity :
#Override
public boolean onOptionsItemSelected(MenuItem item) {
// TODO Auto-generated method stub
onBackPressed();
return true;
}
It's working as I expected
is it a clean solution ?
Thanks
I have an activity which can contain several fragments. Each of the fragments can have their own menu entries in the ActionBar. This works fine so far and each item is clickable and performs the desired action.
My problem is the following. In the MainActivity I declared the following lines to intercept calls to the HomeIcon of the ActionBar:
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
clearBackStack();
setHomeFragment();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
I declared it in the Activity because I wanted that every Fragment should call this so that I don't have to catch the android.R.id.home case in each fragment.
In one Fragment I am using setDisplayHomeAsUpEnabled(true), so that I get the little arrow left of the ActionBar Icon. When the HomeIcon is clicked in this fragment I don't want to set the HomeFragment, I want to set the Fragment which was last displayed. So I have a onOptionsItemSelected - Method in the Fragment:
#Override
public boolean onOptionsItemSelected(MenuItem menuItem) {
switch (menuItem.getItemId()) {
case android.R.id.home:
setLastFragment();
return true;
...
However this does not work the way I wanted it to work. The Activity's onOptionsItemSelected is called first, catches the MenuItem and redirects to the HomeFragment. With the other MenuItems declared in other fragments i can check the see the same behaviour. Activity is called first, doesn't catch the MenuItem (default case) and then redirects to super.onOptionsItemSelected(item).
So it seems that this is the case how Android handles the Menu Clicks. First Activity, then Fragment. Is there a way to change this? I don't want to put the android.R.id.home-case in every fragment and handle it there. Is there a nicer way to do this?
I just encounter this problem, and I have made it work using following code.
In the activity's onOptionsItemSelectedfunction, add:
if (id == android.R.id.home){
Fragment currentFragment = getSupportFragmentManager().findFragmentById(R.id.container);
if(null != currentFragment && currentFragment.onOptionsItemSelected(item)){
return true;
}
}
And in the fragment's onOptionsItemSelected method, you handle the corresponding things.
In this way, if the fragment has any things to do for the menu item, it will do it and return true to stop any other process.
And if the fragment does not have anything to do with this item, it will return false or call super.onOptionsItemSelected method which may eventually return false to let others process it.
According to the developers reference,
"Return false to allow normal menu processing to proceed, true to consume it here."
So I would try returning 'false' by default in the Activity's implementation of onOptionsItemSelected(), this way the event will pass on to the Fragment's implementation if it is not caught.
Not sure if it's possible. In the official docs available here:
http://developer.android.com/guide/topics/ui/actionbar.html#ActionEvents
There is a note, that states the following:
[...] However, the activity gets a chance to handle the event first, so the system first calls onOptionsItemSelected() on the activity, before calling the same callback for the fragment.
You can do as #Surely written, it's great idea, but in that case you will call onOptionsItemSelected on fragment without knowing which fragment it is, and you should override onOptionsItemSelected method in all your fragments.
If you only want to call this method for particular fragments, you should find them by tag, which you used when adding them:
case android.R.id.home:
Fragment frag = getSupportFragmentManager()
.findFragmentByTag(FRAGMENT_TAG);
if(frag != null && frag.isVisible() && frag.onOptionsItemSelected(item)) {
return true;
Tag is specifying like this:
fragmentManager.beginTransaction()
.add(containerId, fragment, FRAGMENT_TAG)