In my android application I have a PreferenceScreen parent that has 3 CheckBoxPreferences as children.
When I click the parent preferenceScreen, and the 3 checkboxes are displayed, I select one of them, and in the Preference.OnPreferenceChangeListener asociated with the checkboxes I set the parent's preferenceScreen summary with:
Parent.setSummary("string depending on the selection")
The thing is that when I return to the parent, it's summary is not updated, even if internally the value has correspondingly changed to the value setted.
Has anyone any idea regarding this behavior?
Use
Parent.setSummary("string depending on the selection");
((BaseAdapter)getPreferenceScreen().getRootAdapter()).notifyDataSetChanged();
works like a charm and can be used regardless the place you change the summary.
This is the correct way
Preference pref = findPreference(getString(R.string.key_of_pref));
PreferenceScreen parent = (PreferenceScreen) sf.findPreference(getString(R.string.key_of_preference_screen));
pref.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
#Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
boolean newValueBool = (Boolean) newValue;
parent.setSummary(newValueBool ? "Summary is true" : "Summary is false");
((BaseAdapter) getPreferenceScreen().getRootAdapter()).notifyDataSetChanged();
// true to update the state of the Preference with the new value
// in case you want to disallow the change return false
return true;
}
});
I discovered that it seems to work by following up setSummary() with getListView().invalidate()
You can use BaseAdapter.notifyDataSetChanged() on the parent PreferenceScreen to update the UI. See here for example code: Update existing Preference-item in a PreferenceActivity upon returning from a (sub)PreferenceScreen
If you're using support preference than go for:
findPreference("your_preference").setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
public boolean onPreferenceChange(Preference preference, Object o) {
getListView().getAdapter().notifyDataSetChanged();
return true;
}
});
The new insistence on fragments instead of activities seems to make this harder. The invalidate route didn't seem to work nor approaches using the underlying View. Thanks to halxinate's answer I have now managed to work this through. For people who are as new as I am to all this here are a few more details:
When creating the settings fragment, save a reference in your main activity, e.g.:
#Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
switch (item.getItemId()) {
case R.id.action_preferences:
if (getFragmentManager().getBackStackEntryCount() < 1) {
FragmentTransaction trans = getFragmentManager()
.beginTransaction();
// Save a reference to the settings fragment
settingsFrag = new SettingsFragment();
trans.replace(R.id.container, settingsFrag);
trans.addToBackStack(null);
trans.commit();
}
return true;
default:
return super.onOptionsItemSelected(item);
}
}
Then when you want to update the summaries of the outer PreferenceScreen from the OnSharedPreferenceChangeListener use this sort of thing. Note that you need to have defined a key in your preference fragment xml for the outer PreferenceScreen (in this case android:key="prefs_root"):
public static void setOuterSummaries(SettingsFragment sf) {
// Set the outer preferences summaries
if (sf == null)
return;
//Code to update the summaries....
// Force the parent screen summaries to update
prefScr = (PreferenceScreen) sf.findPreference("prefs_root");
if (prefScr != null)
((BaseAdapter) prefScr.getRootAdapter()).notifyDataSetChanged();
}
OnSharedPreferenceChangeListener listener = new SharedPreferences.OnSharedPreferenceChangeListener() {
public void onSharedPreferenceChanged(SharedPreferences prefs,
String key) {
Log.e("L", "Change");
setOuterSummaries(settingsFrag);
}
};
Note that you could save a reference to the BaseAdapter instead of to the settings fragment but this approach seems safer when you think of generalizing it to a multi-fragment situation or code which dynamically creates the fragment content.
Related
I am facing two problems in my app.
In my app i am using preferenceActivity that contains simple settings for the app (checkboxpreference and listpreference), i have three activities mainActivity,secondActivity and settingsActivity which is prefrenceActivity, in secondActivity i am showing a form that lets user choose a value from a spinner view , what i want to do is user can select a default value of that spinner from settingsActivity so user dont have to manually select the value of spinner everytime.
Now my first problem is: whenever i reopen my settingsActivity(preferenceActivity) the summery of the listPreference is resets to default it only shows selected value while settingsActivity is open, when i go back to mainActivity and i again open the settingsActivity the summery of listPreferece shows default value(if i open list of values,it shows last selected value checked).
Second problem is: Whenever i close the app and open again the preference value gets destroyed means the secondActivity dosent show user selected default value instead it shows the first value of spinner.
here is my code
settingsActivity:
public class settingsActivity extends PreferenceActivity
{
MainActivity mainActivity = new MainActivity();
ListPreference listpref;
#Override
public void onCreate(Bundle savedInstenceState)
{
super.onCreate(savedInstenceState);
addPreferencesFromResource(R.xml.settings);
getActionBar().setDisplayHomeAsUpEnabled(true);
listpref = (ListPreference)findPreference("prefDefaultCurrency");
listpref.setOnPreferenceChangeListener(new OnPreferenceChangeListener()
{
#Override
public boolean onPreferenceChange(Preference preference, Object value) {
// TODO Auto-generated method stub
listpref.setSummary(value.toString());
mainActivity.pref_default_currency_index = listpref.findIndexOfValue(value.toString());
return true;
}
});
CharSequence curenttext = listpref.getEntry();
mainActivity.pref_default_currency_index = listpref.findIndexOfValue(curenttext.toString());
}
pref_default_currency_index is a static int variable declared in mainActivity,
i retrive this variable to set the value of spinner in secondActivity, when user clicks a button to open an alertdialoug that contains spinner.
Please help me, thanks in advance.
I got the solution to both of my problems.. i was making it too difficult when it as very easy!!
My first problem was to get back the option user selected from listPreference from PreferenceActivity, i realized that preferenceActivity automatically saves information to SharedPrererences so what i have to do is just retrieve that information in onCreate() method and save it in a local variable.
SharedPreferences sharedPrefs= PreferenceManager.getDefaultSharedPreferences(this);
pref_default_currency_index = Integer.parseInt(sharedPrefs.getString("prefDefaultCurrency","0"));
Second problem was that whenever i open settings activity (preferenceActivity) the summery of listPreference was not showing last chosen value, to solve that problem i just have to set the summery of listPreference in setOnPreferenceChangeListener() method
listpref = (ListPreference)findPreference("prefDefaultCurrency");
listpref.setOnPreferenceChangeListener(new OnPreferenceChangeListener()
{
#Override
public boolean onPreferenceChange(Preference preference, Object value) {
// TODO Auto-generated method stub
listpref.setSummary(value.toString());
return true;
}
});
thats it, it was easy!
I've implemented my preferences like shown in the official guidelines.
I have a PreferenceActivity which creates the PreferenceFragment like this:
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
Bundle extras = getIntent().getExtras();
if (extras != null)
{
Bundle bundle = new Bundle();
_widgetID = extras.getInt(GlobalSettings.EXTRA_WIDGET_ID);
bundle.putInt(GlobalSettings.EXTRA_WIDGET_ID, _widgetID);
WidgetSettingsFragment fragment = new WidgetSettingsFragment();
fragment.setArguments(bundle);
getFragmentManager().beginTransaction().replace(android.R.id.content,
fragment).commit();
}
}
The PreferenceFragment loads the preferences from the resources and they contain a preference subscreen like this:
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<!-- opens a subscreen of settings -->
<PreferenceScreen
android:key="button_voicemail_category_key"
android:title="#string/voicemail"
android:persistent="false">
<ListPreference
android:key="button_voicemail_provider_key"
android:title="#string/voicemail_provider" ... />
<!-- opens another nested subscreen -->
<PreferenceScreen
android:key="button_voicemail_setting_key"
android:title="#string/voicemail_settings"
android:persistent="false">
...
</PreferenceScreen>
<RingtonePreference
android:key="button_voicemail_ringtone_key"
android:title="#string/voicemail_ringtone_title"
android:ringtoneType="notification" ... />
...
</PreferenceScreen>
...
</PreferenceScreen>
This works well so far, but now I'd like to have an up-Button in the actionBar when the preferences subscreen is shown. Any idea how to accomplish that?
I have tried to set setDisplayHomeAsUpEnabled(true) in my activity but then the up-Button is only shown in the main preferences (where it should not) and not in the subscreen.
I'm wondering that even in the official docs the subscreen is shown without an active up-Button:
Link to the docs: Settings
Any help is really welcome
I finally got it to work :D. It's quite hacky but it works.
The problem is, that using subscreens in xml-layouts results in some 'code magic'.
A new activity/dialog is started for the subscreen and you don't have direct access to it.
To get access to the actionbar and the OnClickListener of the home/up-button you need to get a reference to your PreferenceScreen and get its parent Dialog in order to access the actionbar and its home/up button.
This is how it is done inside my PreferenceFragment:
#Override
public void onCreate(Bundle savedInstanceState)
{
...
final PreferenceScreen preferenceScreen = (PreferenceScreen) findPreference(getString(R.string.keyPrefScreenDynamicWidgetDetails));
preferenceScreen.setOnPreferenceClickListener(new OnPreferenceClickListener()
{
public boolean onPreferenceClick(Preference preference)
{
preferenceScreen.getDialog().getActionBar().setDisplayHomeAsUpEnabled(true);
final Dialog dialog = preferenceScreen.getDialog();
View homeBtn = dialog.findViewById(android.R.id.home);
if (homeBtn != null)
{
OnClickListener dismissDialogClickListener = new OnClickListener()
{
#Override
public void onClick(View v)
{
dialog.dismiss();
}
};
// Prepare yourselves for some hacky programming
ViewParent homeBtnContainer = homeBtn.getParent();
// The home button is an ImageView inside a FrameLayout
if (homeBtnContainer instanceof FrameLayout) {
ViewGroup containerParent = (ViewGroup) homeBtnContainer.getParent();
if (containerParent instanceof LinearLayout) {
// This view also contains the title text, set the whole view as clickable
((LinearLayout) containerParent).setOnClickListener(dismissDialogClickListener);
} else {
// Just set it on the home button
((FrameLayout) homeBtnContainer).setOnClickListener(dismissDialogClickListener);
}
} else {
// The 'If all else fails' default case
homeBtn.setOnClickListener(dismissDialogClickListener);
}
}
return true;
}
});
...
}
Following link gave me the final hints and code to solve my problem:
Action Bar Home Button not functional with nested PreferenceScreen
I do this per the Android docs in the "Supporting older versions with preference headers" section http://developer.android.com/guide/topics/ui/settings.html#BackCompatHeaders. Using the legacy PreferenceActivity, you specify a Preference in the xml that launches an intent to the same preference activity class. The activity checks the intent action and determines if it is nested or not (to show the up button) and which preference xml to inflate in the screen.
Of course, I intend to support older devices as well. I have found that the PreferenceFragment is only useful for large tablets that use preference headers.
To reuse preferences between phones and tablets I came up with this solution https://stackoverflow.com/a/20806812/1139784
To enable the up action do the following:
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setDisplayUseLogoEnabled(true);
this will give you the icon.
then add
#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);
}
you can alter this to go where you need to. As another option you can use the navigateUpTo(Intent intent) and the onSupportNavigateUpTo(Intent intent) methods and specify the intent you want to return to.
In my app, when options are checked in the overflow menu, then the orientation is changed, they become unchecked. Any way to fix this?
I know onSavedInstance should be able to help me here, but I don't know how to implement it in my case.
Here's an Example of how my overflow checkboxes are handled in the main activity
#Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
#Override
public boolean onMenuItemSelected(int featureId, MenuItem item) {
switch (item.getItemId()) {
case R.id.subtract_option:
if (item.isChecked()){
item.setChecked(false);
subtractMode = false;
} else{
item.setChecked(true);
subtractMode = true;
}
return true;
default:
return super.onMenuItemSelected(featureId, item);
So how would I implement the onSavedInstanceState in this case?
It has to do with how Android handles orientation changes. It actually kills your activity and restarts it. There's two ways to fix it.
1)Implement onSaveInstanceState, write all your state to an object (including the values of checkboxes) and implement onRestoreInstanceState to reverse the process.
2)Add android:configChanges="orientation". This will stop the overriding behavior. It will also break the automatic reloading of layouts if you have separate landscape andd portrait layouts.
I recommend route 2 if your don't have separate layouts for the orientations. If you do and your app is simple, I suggest route 1. If you need that and your app is complex you're screwed and in for a world of pain.
override onSaveInstanceState method.
#Override
public void onSaveInstanceState( Bundle outState )
{
super.onSaveInstanceState( outState );
outState.putInt( "position", this.position );
}
and on onCreateView
if ( savedInstanceState != null )
{
int temp;
temp = savedInstanceState.getInt( "position", -1 );
Log.i( LOG_TAG, "temp....." + temp );
// do whatever you want
}
Make appropriate changes as per your need.
Visit How to save state during orientation change in Android if the state is made of my classes?
he says to use onRetainNonConfigurationInstance() method to save your data on orientation changes.
Several tries to ask this question in #android-dev (irc) and hours of searching, but I still don't have a solution to this problem.
I'm currently working on the search-function in my android music player. I'm using the amazing ActionBarSherlock to provide support for older android versions.
My Problem is the following:
When the user clicks the search menu/action button, the actionView of the clicked action should be expanded, and a new Fragment (the searchFragment) should be shown instead of the currently active one.
However when i'm attempting to do this, the actionView doesn't expand.
I've tried to expand the actionView, without adding the SearchFragment, and in that case the actionView DOES expand. However the combination seems impossible.
Here's my code:
#Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item != null) {
if (item.getItemId() == R.id.collectionactivity_search_menu_button) {
item.expandActionView();
mTabsAdapter.replace(new SearchFragment(), false);
return true;
}
}
return false;
}
/**
* Replaces the view pager fragment at specified position.
*/
public void replace(int position, Fragment newFragment, boolean isBackAction) {
// Get currently active fragment.
ArrayList<Fragment> fragmentsStack = mFragments.get(position);
Fragment currentFragment = fragmentsStack.get(fragmentsStack.size() - 1);
if (currentFragment == null) {
return;
}
// Replace the fragment using a transaction.
this.startUpdate(mViewPager);
FragmentTransaction ft = mFragmentManager.beginTransaction();
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
ft.attach(newFragment).remove(currentFragment).commit();
if (isBackAction == true)
fragmentsStack.remove(currentFragment);
else
fragmentsStack.add(newFragment);
this.notifyDataSetChanged();
this.finishUpdate(mViewPager);
}
The mTabsAdapter.replace(...) method replaces the currently shown Fragment with the one in the first parameter. In Addition the fragment is being added to a custom backStack.
Replacing the Fragment before or after expanding the View didn't make any difference.
Hopefully somebody is able to help me :)
thanks in advance!
Have you tried setting your actionviews android:showAsAction to collapseActionView? that way you don't have to manage the expand/close action.
If that does not work you can handle it in another way,you set an expand listener and replace your fragment once your action view starts expanding
item.setOnActionExpandListener(new OnActionExpandListener() {
#Override
public boolean onMenuItemActionCollapse(MenuItem item) {
// Do something when collapsed
return true; // Return true to collapse action view
}
#Override
public boolean onMenuItemActionExpand(MenuItem item) {
mTabsAdapter.replace(new SearchFragment(), false);
return true; // Return true to expand action view
}
});
remember to return true to let the actionview expand
I found out what the problem was caused by.
My mTabsAdapter.replace(..) method was calling notifyDataSetChanged();. So everytime I replaced the fragment, onPrepareOptionsMenu was being called, resulting in the search action button being removed and added again, thus resulting in the actionView being collapsed.
The solution to this is to fix my onPrepareOptionsMenu, so the actionView will be expanded again, whenever onPrepareOptionsMenu is called and the actionView was expanded before.
I'm developing for android 3+
In my action bar i have a drop-down list(see how to hide/unhide the actionbar list on android 3? for the dropdown i intend). The problem is i need to do a certain action when the user selects something, but Android calls onNavigationItemSelected() as soons as it draws the view, so no selection actually happened.
How can i detect if the user actually pressed something and it is not a fake call from android ?
public class ListDittaListener implements OnNavigationListener{
private BaseActivity activity;
private ListDittaListener()
{
}
public ListDittaListener(BaseActivity activity)
{
this.activity = activity;
}
#Override
public boolean onNavigationItemSelected(int itemPosition, long itemId)
{
MyApp appState = ((MyApp)this.activity.getApplicationContext());
appState.setDittaSelezionata( (int) itemId);
SharedPreferences settings = this.activity.getSharedPreferences(MyApp.PREFS_NAME, 0);
SharedPreferences.Editor editor = settings.edit();
editor.putInt("ditta_id_selezionata", (int) itemId);
////////restart activity this.activity.recreate();
return false;
}
}
You can easily just ignore the first call to onNavigationItemSelected if you like:
public class Whatever implements OnNavigationListener {
private boolean synthetic = true;
#Override
public boolean onNavigationItemSelected(int itemPosition, long itemId) {
if (synthetic) {
synthetic = false;
return true;
}
// do whatever you really wanted here
}
}
Method onNavigationItemSelected(int itemPosition, long itemId) will be called anyway by the action bar.
What you may want to do is to tell action bar what itemPosition it should pass to the method on the first call. (In other words, to tell action bar what navigation item should be set after activity is created). Here is the code:
mActionBarMenuSpinnerAdapter = ...;
mActionBar = getActionBar();
mActionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST);
mActionBar.setListNavigationCallbacks(mActionBarMenuSpinnerAdapter, this);
mActionBar.setSelectedNavigationItem(###your_default_navigation_item_here###);
After doing this you can solve your problem by applying changes in the onNavigationItemSelected(int itemPosition, long itemId) if only itemPosition is different.
The android system will call onNavigationItemSelected(0, 0) after the activity is setup. (Which means later than onResume()).
As other guys mentioned, you'd better not do any hack like ignore first call, otherwise the android system won't call onNavigationItemSelected() again when you select the first index. (The system thought the first item is already selected)
My solution is call actionbar.setSelectedNavigationItem(the real item# you want) after you setup the actionbar. Then the system will call onNavigationItemSelected() twice. First onNavigationItemSelected(0, 0) and then the onNavigationItemSelected(the real item#).
Well I cannot see anything wrong in your current code.
How did you create your dropdown elements. And what element is "select" by Android after the view is created. And what are your doing in your onCreate method where the ActionBar is initialized.
I did it as instructed here and it worked for me:
http://developer.android.com/guide/topics/ui/actionbar.html#Dropdown
I have viewpager with fragments and I need set custom action bar for every fragment in pager
In desired page I have navigation list, fragment fires onNavigationItemSelected automatically when I swipe pages, want to avoid this behavior and run tasks only if I selected nav item manually.
public class MyFragment extends Fragment implements ActionBar.OnNavigationListener {
private boolead fireReady = false;
#Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
// every time make it false, this method invoked on swipe action
fireReady = false;
if (isVisibleToUser) {
// setup actionbar, you also can setup action bar in activity
String[] array = getActivity().getResources().getStringArray(R.array.users_order);
ArrayAdapter<String> adapter = new ArrayAdapter<String>(getActivity(), android.R.layout.simple_spinner_item, array);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
getActivity().getActionBar().setNavigationMode(ActionBar.NAVIGATION_MODE_LIST);
getActivity().getActionBar().setListNavigationCallbacks(adapter, this);
}
}
#Override
public boolean onNavigationItemSelected(int itemPosition, long itemId) {
if (fireReady) {
// task fire only when you directly press navigation item
UsersTask task = new UsersTask(getActivity());
task.setTaskListener(this);
task.execute(usersUrls[itemPosition]);
} else {
// make it true first time when page displayed
fireReady = true;
}
return false;
}
}
}