Background:
I'm using PreferencesFragment to manage preferences in my android app and i use options menu to access those preferences.
MainActivity class overriden methods:
#Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch(item.getItemId()){
case R.id.menu_prefs:
PrefsFragment prefs = new PrefsFragment();
getFragmentManager()
.beginTransaction()
.replace(R.id.main_container, prefs)
.addToBackStack(null)
.commit();
break;
}
return true;
}
res/menu/menu.xml:
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
<item
android:id="#+id/menu_prefs"
android:icon="#android:drawable/ic_menu_preferences"
android:title="#string/menu_prefs_title"
android:showAsAction="ifRoom|withText">
</item>
</menu>
Problem:
While on main screen, I click preferences icon in action bar, PrefsFragment starts.
While on PrefsFragment, I click preferences icon in action bar PrefsFragment starts again (this is where problem arise).
I hit the back button and it reverts fragment transaction and goes back to PrefsFragment. (it should go back to main screen).
Question:
Is there any smart way to disable double showing PrefsFragment?
Desired outcome:
Clicking prefs icon in action bar if on main screen shows PrefsFragement, clicking prefs icon in action bar while PrefsFragemnt is visible does nothing, clicking back button while on PrefsFragment always goes back to main screen.
Ok, after some more digging I did this:
I'm disabling action bar/options menu if PrefsFragemnt is visible ( I could add different menu here but disabling it works fine in this case ).
PrefsFragment.java
import android.os.Bundle;
import android.preference.PreferenceFragment;
public class PrefsFragment extends PreferenceFragment {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.prefs);
}
#Override
public void onStop() {
super.onStop();
getActivity().invalidateOptionsMenu();
}
}
MainActivity.java (overriden methods):
#Override
public boolean onCreateOptionsMenu(Menu menu) {
PrefsFragment prefs = (PrefsFragment)getFragmentManager().findFragmentByTag("PREFS");
if (prefs != null){
if (prefs.isVisible()) {
return true;
}
}
getMenuInflater().inflate(R.menu.menu, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch(item.getItemId()){
case R.id.menu_prefs:
PrefsFragment prefs = new PrefsFragment();
FragmentManager manager = getFragmentManager();
manager
.beginTransaction()
.replace(R.id.main_container, prefs, "PREFS")
.addToBackStack(null)
.commit();
manager.executePendingTransactions();
invalidateOptionsMenu();
break;
}
return true;
}
The most important part is calling manager.executePendingTransactions() and invalidateOptionsMenu() on button click and calling invalidateOptionsMenu() when stopping fragment. This will cause for menu to be refreshed evry time we go to prefs and after getting out of them.
If you have better solutions I would love to see them, as this one feels a little bit hackish to me.
Can you try to accomplish it using FragmentTransaction.detach?
http://developer.android.com/reference/android/app/FragmentTransaction.html#detach%28android.app.Fragment%29
Related
My problem is as follows:
I have one activity with many fragments. Main activity has "three dots" menu.
When I call a fragment, it has it's own menu (which is showing).
This fragment is a form with EditText fields, and checkboxes.
When a user changes something in those fields, and then press the "save" icon in the toolbar, the values get collected, and sent to the web service, and updated. BUT only the first time.
After going back (back arrow in toolbar), and returning to a fragment (new fragment gets created), after editing the fields, and pressing the "save" button, the form doesn't get saved/updated.
I debugged it, and found out that the "save" button which gets called for the second time, is actually the "save" button of the previous fragment.
The values in the editText fields gets collected inside a HashMap. And this HashMap gets updated and filled with new data inside new fragment. BUT when it gets to calling onOptionsItemSelected(), OLD DATA from the previous (or THE FIRST fragment) gets in the focus, and those data gets saved/updated.
So, my question is this: Is there a way to detach the menu items from the fragment, so when another (new) fragment is called, and gets created it attaches its own menu option to itself (not from the first fragment ever created)?
I tested it with System.identityHashCode(fragment), and when a user edit fields in "second" fragment, the hash code is OK, BUT when the "save" gets called hash code gets changed to the hash code of the first fragment.
This is the part of code from FormFragment:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
#Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
menu.clear();
inflater.inflate(R.menu.menu_main, menu);
int itemId = 0;
for (ActionList menuItem : allData.getActionList()) {
menu.add(1, itemId, itemId, menuItem.getPrompt());
itemId++;
}
inflater.inflate(R.menu.menu_activity, menu);
super.onCreateOptionsMenu(menu, inflater);
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == R.id.action_favourite) {
if (valuesArray != null) {
((CommInterface) getActivity()).getUpdateList(valuesArray, taskUrl, dataItemId);
return true;
} else {
Toast.makeText(getActivity(), "Not updated!", Toast.LENGTH_SHORT).show();
return true;
}
}
for (ActionList menuName : allData.getActionList()) {
if (item.getTitle().equals(menuName.getPrompt())) {
((CommInterface) getActivity()).formMenuItemSelected(menuName.getActionName(), position);
return true;
}
}
return super.onOptionsItemSelected(item);
}
And the part of code from MainActivity:
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_choose_instance:
....
case android.R.id.home:
if (height > width) {
getSupportFragmentManager().popBackStack();
toolbarTitles.remove(toolbarTitlesCurrentNumber);
toolbarTitlesCurrentNumber--;
toolbar.setTitle(toolbarTitles.get(toolbarTitlesCurrentNumber));
if (toolbarTitlesCurrentNumber == 0) {
getSupportActionBar().setDisplayHomeAsUpEnabled(false);
getSupportActionBar().setHomeButtonEnabled(false);
}
} else if (...) {
....
}
default:
return super.onOptionsItemSelected(item);
}
}
Create the menu items under fragments not under activity,
see the previous discussion
Android Options Menu in Fragment
The answer to this problem is: use the getChildFragmentManager() in TabbedPagerAdapter.
I was using the Activity's getSupportFragmentManager() which is wrong when you are using NESTED fragments like in TabbedFragment which has a ViewPager, and a ViewPager has (child) Fragments inside.
I'm tryng study the Preference system of Android.
When from my navdrawer I call settings fragment, it doesn't show nothing and menu remain selected, also if I select another voice.
This is the screen at startup of my APP:
Where all is perfect. After I click on Impostazioni (settings, in English), Home (or the "Elenca" item) reamin checked, also with "Impostazioni"
Another one problem is that Settings frame is blank (no error in Log but it's all blank).
This is XML preferences.xml stored under res/xml
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<PreferenceCategory
android:title="#string/pref_sms_storage_title"
android:key="pref_key_storage_settings">
<CheckBoxPreference
android:key="pref_sync"
android:title="#string/pref_sync"
android:summary="#string/pref_sync_summ"
android:defaultValue="true" />
</PreferenceCategory>
</PreferenceScreen>
This is my Settings Fragment:
import android.os.Bundle;
import android.preference.PreferenceFragment;
import android.support.v4.app.Fragment;
import com.xxxxxx.R;
public class SettingsFragment extends Fragment{
#Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
new InnerSettingsFragment();
}
public static class InnerSettingsFragment extends PreferenceFragment {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Load the preferences from an XML resource
addPreferencesFromResource(R.xml.preferences);
}
}
}
And finally this is my BaseApp that mantains the navdrawer logic:
navigationView.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener() {
// This method will trigger on item Click of navigation menu
#Override
public boolean onNavigationItemSelected(MenuItem menuItem) {
//Checking if the item is in checked state or not, if not make it in checked state
if (menuItem.isChecked()) menuItem.setChecked(false);
else menuItem.setChecked(true);
//Closing drawer on item click
drawerLayout.closeDrawers();
//Check to see which item was being clicked and perform appropriate action
switch (menuItem.getItemId()) {
case R.id.home:
DashboardFragment dashboardFragment = new DashboardFragment();
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
fragmentTransaction.replace(R.id.frame, dashboardFragment,"DASHBOARD_FRAGMENT");
fragmentTransaction.commit();
return true;
case R.id.list_event:
ListEventFragment fragmentListEvent = new ListEventFragment();
fragmentTransaction = getSupportFragmentManager().beginTransaction();
fragmentTransaction.replace(R.id.frame, fragmentListEvent);
fragmentTransaction.commit();
return true;
case R.id.settings:
SettingsFragment fragmentSettings = new SettingsFragment();
fragmentTransaction = getSupportFragmentManager().beginTransaction();
fragmentTransaction.replace(R.id.frame, fragmentSettings);
fragmentTransaction.commit();
return true;
default:
return true;
}
}
});
You shouldn't make your PreferenceFragment an inner class, add your outer class in its place, and still expect things to work as usual. It won't because the outer onCreate() wouldn't delegate to the inner onCreate() by itself. So, just make your InnerSettingsFragment a regular top-level class and you'll start seeing your preferences get loaded from the XML resource.
public class SettingsFragment extends PreferenceFragment {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Load the preferences from an XML resource
addPreferencesFromResource(R.xml.preferences);
}
}
In a Bluetooth-related app (with minSdkVersion="18") I have a single MainActivity.java, displaying one of the following 3 UI Fragments:
MainFragment.java (the top screen)
SettingsFragment.java (settings screen, entered through menu)
ScanningFragment.java (lists nearby Bluetooth devices)
To display an "Up button" and handle the "Back button" I have the following code in place:
public class MainActivity extends Activity
implements BleWrapperUiCallbacks {
// set in onResume() of each fragment
private Fragment mActiveFragment = null;
ยด #Override
public void onBackPressed() {
if (!getFragmentManager().popBackStackImmediate())
super.onBackPressed();
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_ACTION_BAR);
setContentView(R.layout.activity_root);
getActionBar().setDisplayHomeAsUpEnabled(true);
if (savedInstanceState == null) {
Fragment fragment = new MainFragment();
getFragmentManager().beginTransaction()
.replace(R.id.root, fragment, "main")
.commit();
}
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
getFragmentManager().popBackStackImmediate();
break;
case R.id.action_settings:
Fragment fragment = new SettingsFragment();
getFragmentManager().beginTransaction()
.addToBackStack(null)
.replace(R.id.root, fragment, "settings")
.commit();
break;
}
return super.onOptionsItemSelected(item);
}
This works well, but has a cosmetic problem, that the "Up button" is still displayed when the MainFragment.java is being displayed - as you can see on the left side of the above screenshot.
I have tried calling
getActionBar().setHomeButtonEnabled(false);
when that fragment is being active, but that only disables the "Up button" - without really hiding it.
With help of Little Child (thanks!) here my solution using the FragmentManager.OnBackStackChangedListener:
public class MainActivity extends Activity
implements OnBackStackChangedListener,
BleWrapperUiCallbacks {
#Override
public void onCreate(Bundle savedInstanceState) {
...
getFragmentManager().addOnBackStackChangedListener(this);
}
#Override
public void onBackStackChanged() {
getActionBar().setDisplayHomeAsUpEnabled(
getFragmentManager().getBackStackEntryCount() > 0);
}
You are in luck because in API level 18 there is this:
getActionBar().setHomeAsUpIndicator(R.drawable.ic_yourindicator);
for support library, it is:
getSupportActionBar().setHomeAsUpIndicator(R.drawable.ic_yourindicator);
So now depending upon what fragment you are in, you can change the icon of "up" button.
Also, try this:
getActionBar().setDisplayHomeAsUpEnabled(false);
Set whether home should be displayed as an "up" affordance.
2021 Working Solution:
(requireActivity() as AppCompatActivity).supportActionBar?.setHomeAsUpIndicator(null)
I am an Android programming newbie trying to move from Activities to Fragments.
I have a simple Bluetooth app with 3 Activities:
MainActivity (with "Settings" in menu)
SettingsActivity with few checkboxes and seekbars
ScanningActivity showing Bluetooth devices in a list
This is a screenshot of the MainActivity, with an ellipsis menu in top-right corner:
I am trying to change my app to have a single MainActivity, which displays one of 3 Fragments (MainFragment, SettingsFragment or ScanningFragment):
public class MainActivity extends Activity implements
MainListener, /* my 3 custom interfaces */
SettingsListener,
ScanningListener,
BleWrapperUiCallbacks {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main); // empty FrameLayout
Fragment fragment = new MainFragment();
getFragmentManager().beginTransaction()
.replace(R.id.root, fragment, "main")
.commit();
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_settings:
Fragment fragment = new SettingsFragment();
getFragmentManager().beginTransaction()
.addToBackStack(null)
.replace(R.id.root, fragment, "settings")
.commit();
break;
}
return super.onOptionsItemSelected(item);
}
My problem is that currently the "Settings" menu is always displayed - while I only need to display it when the MainFragment is shown (but not when SettingsFragment or ScanningFragment are shown).
How to solve this best?
And do I have to introduce an enum variable, which I can query to find which of the 3 Fragments is currently displayed or is there a nicer way?
Use the onCreateOptionsMenu() in the fragment you wish to show the menu, not in your activity. In the others fragments you don't want to display the menu, use setHasOptionMenu(false).
To get your current fragment, use the findFragmentByTag() from your FragmentManager
You can Override onCreateOptions menu in the fragment that should have the menu and only inflate it there.
In the fragment:
#Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){
super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.your_fragment_menu);
}
my problem is when i click on a action bar item onOptionsItemSelected is called but not working.
I only want the back button to work.
Here is my code, this is in the SherlockFragment file:
actionbar = getSherlockActivity().getSupportActionBar();
actionbar.setDisplayHomeAsUpEnabled(true);
#Override
public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
inflater.inflate(R.menu.menu_detail, menu);
super.onCreateOptionsMenu(menu, inflater);
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.detailitem1:
break;
case android.R.id.home:
getActivity().onBackPressed();
break;
default:
return super.onOptionsItemSelected(item);
}
return false;
}
The menu item is com.actionbarsherlock.view.MenuItem.
I added setHasOptionsMenu(true); in onCreate and tried onCreateView too.
The onBackPressed is this, its in the SherlockFragmentActivity:
#Override
public void onBackPressed() {
Saved = SP.getBoolean("saved", false);
if ((getSupportFragmentManager().findFragmentByTag("detail") == null)
| mTwoPane) {
ready = true;
} else
ready = false;
if (!Saved && ready) {
new AlertDialog.Builder(this)
.....
}else if (Saved && ready){
super.onBackPressed();
}else {
someListFragment Fragment = new someListFragment ();
Fragment .setArguments(getIntent().getExtras());
Fragment .setHasOptionsMenu(true);
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.fragment_container, Fragment, "listview")
.commit();
}
}
In the onbackpressed the ready is true when we are in two pane mode, so when using tablet, or if we are in the first fragment(so the second is null).
If i press the back button everything works fine, the fragments are replaced.
But when i click on the back button in the action bar it just goes back to a previous activity.
Even if i chage the getActivity().onBackPressed(); onBackPressed's else statement i doesnt work.
Does anyone know whats the solution, what i did wrong?
Sorry i was a bit lazy, i forget that i had a onOptionsItemSelected in my FragmantActivity, and the back button is android.R.id.home everwhere, so i put an if statement there and voila.