PreferenceManager.setDefaultValues does not work for custom Preferences - android

I'm working on a little app with a lot of modifiable preferences, most of them being SeekBarPreferences.
It happens that, since I'm quite not happy with Android default SeekBarPreferences, I'm using the very good MaterialSeekBarPreference library which unfortunately have not been updated for 2 years.
Here is an example of code used by this library:
<com.pavelsikun.seekbarpreference.SeekBarPreference
android:key="#string/param_maxEvent"
android:title="Blahlblahblahblahblah"
android:summary="Blahlblahblahblahblah too"
android:defaultValue="2"
custom:msbp_minValue="0"
custom:msbp_maxValue="5"
custom:msbp_measurementUnit="events"
custom:msbp_interval="1"
custom:msbp_dialogEnabled="false"/>
As you can see, you can use the android:defaultValue xml attribute, and it works perfectly with the UI.
Since I need to load all these default values at app initialization, I use the PreferenceManager.setDefaultValues method:
public class App extends Application {
#Override public void onCreate() {
super.onCreate();
PreferenceManager.setDefaultValues(this, R.xml.preferences, true);
}
}
This works fine with all default preferences (SwitchPreference, ListPreference, Preference), but unfortunately not with these custom SeekBarPreference.
Loading the preferences activity does not set up thoses default values either.
Is there any workaround for this problem ? Else, if I was up to edit the library, what should I change ?

I don't use the method PreferenceManager.getDefaultSharedPreferences(this, R.xml.preferences, true);. Instead, I use preference.setDefaultValue(object); in the Fragment.

Related

DialogPreference is not saving the preference when I expect it to?

I have written a bare bones standard DialogPreference which is working fine, except that it is not saving the preference to default shared preferences when I expected it to.
1) open the app, and main activity shows value of foo from default shared preferences = 1
2) go to settings
3) click on foo setting which opens my DialogPreference and shows value = 1
4) enter value 3
5) close my DialogPreference using Ok button
***** default shared preferences foo should now be 3
6) click on foo setting which opens my DialogPreference and shows value = 1
***** so my DialogPreference didn't save the preference to default shared preferences?
7) cancel dialog
8) go back to main activity which shows value of foo from default shared preferences = 3
***** so my DialogPreference did save the preference to default shared preferences
9) go to settings
10) click on foo setting which opens my DialogPreference and shows value of 3
Why isn't the value of default shared preferences foo = 3 at step (6)?
It seems that the preference is only being saved to default shared preferences when the flow returns to the main activity from the settings list, which is counter intuitive to saving the preference in the onDialogClosed method of DialogPreference.
MyDialogPreference
public class MyDialogPreference extends DialogPreference
{
private static final String DEFAULT_VALUE = "0";
private String value = DEFAULT_VALUE;
private EditText editText;
public MyDialogPreference(Context context, AttributeSet attrs)
{
super(context, attrs);
setDialogLayoutResource(R.layout.constrained_integer_preference);
}
#Override
public void onBindDialogView(View view)
{
super.onBindDialogView(view);
editText = (EditText) view.findViewById(R.id.edit);
editText.setText("" + value);
}
#Override
protected void onDialogClosed(boolean positiveResult)
{
if (positiveResult)
{
persistString(editText.getText().toString());
}
super.onDialogClosed(positiveResult);
}
#Override
protected Object onGetDefaultValue(TypedArray typedArray, int index)
{
return typedArray.getString(index);
}
#Override
protected void onSetInitialValue(boolean restorePersistedValue, Object defaultValue)
{
if (restorePersistedValue)
{
value = getPersistedString(DEFAULT_VALUE);
}
else
{
value = (String) defaultValue;
if (shouldPersist())
{
persistString(value);
}
}
}
}
EDIT: So it appears that the preference I am handling with my DialogPreference has no key, which is causing all the problems. But I have specified the key in the preferences.xml file for this DialogPreference. I have tried everything to force the key to be recognised but nothing is working.
Can anyone tell me how I get a DialogPreference to receive the android:key from the preferences.xml file to work?
preferences.xml
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto">
<org.mycompany.myproject.MyDialogPreference
android:defaultValue="11"
android:dialogLayout="#layout/my_preference"
android:inputType="number"
android:key="MY_KEY"
android:selectAllOnFocus="true"
android:singleLine="true"
android:summary="summary"
android:title="My Preference" />
</PreferenceScreen>
You'd have to implement the OnPreferenceChangeListener and/or call to notifyChanged().
Unless you'd provide the code of that DialogPreference, it's difficult to reproduce the issue.
At some point I always feel like I'm hacking Android, and this is definitely a hack.
Initially I thought the problem I was fighting was that the framework ignores my android:key, because getKey() returns an empty string, but that can't be true because it gets the persistent value when it starts the PreferenceScreen, and saves my changed values to shared preferences when I close the DialogPreference.
So it seems the problem I am fighting is that the framework reads the preferences persistent values in to internal members, and then uses the internal members until the flow returns out of the preferences framework, without refreshing them after a DialogPreference has closed.
But I have finally found a way of getting the PreferenceScreen to refresh the preferences persistent values it holds in it's internal members. Although it's not really a refresh, it's a hack.
So what I do is basically throw away the PreferenceScreen and create a new one. I do this by adding the following code to my SettingsFragment.onCreate method directly before addPreferencesFromResource(R.xml.preferences).
SharedPreferences.OnSharedPreferenceChangeListener prefListener = (prefs, key) ->
{
setPreferenceScreen(null);
addPreferencesFromResource(R.xml.preferences);
};
PreferenceManager.getDefaultSharedPreferences(getActivity().getApplicationContext()).registerOnSharedPreferenceChangeListener(prefListener);
This is probably bad. I have tested it, repeatedly, though not thouroghly, and have not witnessed any adverse effects so far.
So with this hack, I can now repeatedly open a DialogPreference from the PreferenceScreen, save a new value, then go back in to the DialogPreference with the previously updated value.
I don't believe my hack is the intended way of achieving this outcome, but after days of searching the source code and google for answers, I have not found anything else.
I am leaving this answer here for anyone else that faces the same problem and is brave enough to try my hack. I'll update the answer if (and probably when) I find any problems with the hack.
Better yet, if anyone else can provide a preferred solution, pointing out what I have done wrong that caused the problem, please do.
EDIT: After working for so long, that hack eventually broke, and consistently.
But while removing the hack, I approached the problem with a fresh mind, and using the fact that I determined the dialog is getting the preference key, I used this fix for the problem, which is working perfectly.
I added this line of code to the start of onBindDialogView
value = getSharedPreferences().getString(getKey(), "-1");
Which makes the calls to onGetDefaultValue and onSetInitialValue redundant, but they just don't work as intended, at least not for me.
EDIT:
omg, I hate this!
I did not notice that during an earlier refactor the line of code that updates the DialogPreference internal value in onDialogClosed was removed.
It's usually something simple, and with everything else I was checking, I missed that small change.
I only just noticed it during a code review on the repo, and now I feel silly. So no additional code was required in the end.

Android - custom row layout in my ListPreference

In my Android application, I have implemented class SubtitleColorListPreference which extends from ListPreference. I need this, because I need to set my own layout for each item in list. Everything works fine and it looks like this:
The important code is in method onPrepareDialogBuilder(AlertDialog.Builder builder), where I set my own ListAdapter.
#Override
protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
// some other code is here ...
ListAdapter listAdapter = new SubtitleColorAdapter(getContext(), R.layout.subtitle_color_preference_item, colorNameHolders, index, this);
builder.setAdapter(listAdapter, this);
}
Now, I need to use PreferenceFragmentCompat instead of PreferenceFragment, so my SubtitleColorListPreference has to extend android.support.v7.preference.ListPreference. And here is the problem - there are no onPrepareDialogBuilder method in this ListPreference. Also, I did not find any similar method. I tried to find some examples how to create custom android.support.v7.preference.ListPreference, but with no success.
Does anybody know how can I solve this?
Google thinks that you don't need a custom view in ListPreference. It seems, the only way is to use the deprecated PreferenceFragment and android.preference.ListPreference or stay with obsolete SDK 27. It's clear that they don't understand what they're doing.

How can I reuse code when creating multiple preferences.xml files on Android?

I want to display different preference options in my app depending on the device SDK and screen size, but certain preferences will be displayed on all devices. I could accomplish this by creating a full preferences.xml file for each possible device, like this:
xml/preferences.xml:
<PreferenceScreen>
<!-- Preference 1 (all devices) -->
<!-- Preference 2 (all devices) -->
</PreferenceScreen>
xml-v21/preferences.xml:
<PreferenceScreen>
<!-- Preference 1 (all devices) -->
<!-- Preference 2 (all devices) -->
<!-- Preference 3 (SDK 21 only) -->
</PreferenceScreen>
But this will get unwieldy very quickly given the number of possible combinations of screen sizes and SDKs. What I'd really like to do would be to use the same basic list of preferences on all devices and dynamically mix in additional preferences that are specific to certain screen sizes and SDKs. I've gone through the Android Providing Resources guide, but it seems that using alternative resources in the manner described there would still require me to create a separate resource directory for every screen-size-and-SDK combination and would require a lot of code duplication. Is there a nice, elegant solution to this problem that I'm missing?
Have you read the Settings guide? My app also has a lot of changes dynamically, both in 1) which headers / fragments to show, and 2) which prefs each fragment contains. For both issues you can use different resource versions, as you describe, or you can implement the differences in code.
For issue #1, you can either call loadHeadersFromResource directly, and have different headers resource files, or you can have code that does something similar. For example, my PreferencesActivity uses a separate PreferenceFragment subclass for each prefs section, and makes a decision at run time about which fragments (headers) to show:
#Override
public void onBuildHeaders(List<Header> targets) {
// Build a list of PreferenceFragment class objects to show now
List<Class<? extends PreferenceFragment>> fragmentClasses = ...;
// Create a Header for each fragment to return to Android
for (Class<? extends PreferenceFragment> fragmentClass: fragmentClasses) {
try {
PreferenceFragment fragment = fragmentClass.newInstance();
Header header = new Header();
header.fragment = fragmentClass.getName();
header.titleRes = fragment.getTitleId();
targets.add(header);
this.headers = targets;
} catch (Exception e) {
}
}
}
For issue #2, you can start with preferences from a common resource file, and then add the conditional ones in code:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Load common prefs from an XML resource
addPreferencesFromResource(R.xml.preferences);
// Add conditional prefs in code
PreferenceScreen prefScreen = getPreferenceScreen();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Preference pref = ...; // create condition pref
prefScreen.addPreference(pref);
}
}
I actually add all prefs in code, common and conditional. However, I didn't see a way to create a PreferenceScreen from scratch, so I actually have an empty XML file that I load from resources, and then add all preferences in code. It works really well.

Android preferences without an xml - Using fragments

Hi I want to create preferences in my application but I cannot use resources at all due to some dependency issues.
I am able to do this using the below code:
public class DTMainActivity extends PreferenceActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setPreferenceScreen(defaultPref());
setDependencies();
}
// The first time application is launched this should be read
private PreferenceScreen defaultPref() {
PreferenceScreen root = getPreferenceManager().createPreferenceScreen(this);
SwitchPreference dLogTracingEnablePref = new SwitchPreference(this);
dLogTracingEnablePref.setTitle(R_Class.R_String.dLogTracingEnablePrefString);
dLogTracingEnablePref.setDisableDependentsState(false);
dLogTracingEnablePref.setChecked(true);
dLogTracingEnablePref.setKey(R_Class.R_String.dLogTracingEnablePrefKey);
root.addPreference(dLogTracingEnablePref);
}
I would want to do this using the new fragment based approach, without using the deprecated APIs like getPreferenceManager etc.. I can create all the other UI layout elements like linearlayout etc.. without any resources, but when it comes to preferences and PreferenceFragment class, all that is available is addPreferencesFromResource() which would need an XML. Can any one help me here please?
I managed to made it using a PreferenceFragment, without addPreferencesFromResource(),
Instead I just created the PreferenceScreen like you just did and used
try using the bindPreferenceSummaryToValue, consider "p" being a PreferenceScreen with Preferences already added into it, (and also that has been created and configured previously)
PreferenceScreen p = createPreferences();//a method that creates a PreferenceScreen and add some preferences into it
this.setPreferenceScreen(p);
bindPreferenceSummaryToValue(p.findPreference("preference_key"));
I responded to someone with a similar problem here .. perhaps you can check it out

Android, subclass Preference class

I am tryig to write an Android Honeycomb application and I am having trouble subclassing Preference: http://developer.android.com/reference/android/preference/Preference.html
I want to make a similar layout with title and summary but also a progress bar.
I have created the layout and added the custom preference class but I can't seem to get hold of the instance of it to set the values of the items in it.
It seems that the preference key doesn't work for the custom class.
Here is my preference definition compared to the standard preference class:
<Preference
android:key="int_free_storage"
android:title="Free Space"
android:summary="free storage value here"/>
<com.hamid.storageether.SpacePreference
android:key="int_space_test"
android:title="Test"
android:summary="This is my custom preference"/>
My my preference subclass then sets my XML layout as it's layout resource in its constructor
setLayoutResource(R.layout.space_pref_layout);
it also overrides the setTitle and setSummary methods....
In my main PreferenceActivity I try to get hold of my Preference by it's key but no luck it seems, since the preference never gets updated:
// These Two work
Preference intTotal = (Preference)findPreference("int_total_storage");
Preference intFree = (Preference)findPreference("int_free_storage");
intTotal.setSummary("Standard Preference Summary 1");
intFree.setSummary("Standard Preference Summary 2");
// My subclass doesn't - It just displays the default text defined in the layout xml.
SpacePreference intTest = (SpacePreference)findPreference("int_test_space");
intTest.setTitle("Testtttyyy");
intTest.setSummary("Test Summary");
Could someone please point me towards where I may be going wrong?
Is this code copied straight from the program or retyped? If copied, then your key is "int_space_test" in XML and "int_test_space" in code. It should be throwing a null pointer exception on the next line where you use intTest if that's the case.

Categories

Resources