I was trying to implement preferences for an AppCompat app, using support.v7.preference. It took me a couple of days to fiddle through it, since support.v7.preference has some significant differences to the native preferences... which isn't too bad once you know, but unfortunately there's little documentation out there. I thought I'd share my findings so others don't have to go through the same pain.
So... question:
How do you best implement Preferences for AppCompat apps (with PreferenceFragment and AppCompatAcitivity being incompatible)?
Here are a couple of related questions:
- Preference sub-screen not opening when using support.v7.preference
- How to move back from Preferences subscreen to main screen in PreferenceFragmentCompat?
- PreferenceFragmentCompat requires preferenceTheme to be set
- How do I create custom preferences using android.support.v7.preference library?
I have an alternative solution to this, that i'd love feedback on.
I made a custom layout for my preferencefragment, with a "Back" button in the upper left corner.
First, in the "onCreatePreference" i store away the root PreferenceScreen:
Then, I add the OnPreferenceStartScreenCallback as described above and in other threads to make the fragment go to subscreen, but in my "onPreferenceStartScreen" i also set the back button to visible like this:
Finally, the backButton clickhandler:
This seem to work fine for me. Obviously the back stack won't work, but i can live with that since there is a Back button.
Not perfect, but given the abysmal API i think i'm happy.
I would love to hear if someone thinks there are any problems with this approach.
Solution 1: Native
PreferenceFragment
withAppCompatActivity
In AndroidStudio, choose File > New Project >...> SettingsActivity. This template uses a workaround that retrofits the native
PreferenceFragment
to work withAppCompatActivity
, similar to thesupport.v4.Fragment
or thesupport.v7.PreferenceFragmentCompat
.AppCompat
app. It's a quick approach when using the AS template, and you can stick to the existing Preference docs and workflows.Solution 2:
support.v7.preference.PreferenceFragmentCompat
withAppCompatActivity
ColorPicker
orFontPreferences
).Should you choose not to use Solution 1 (I'm still not sure which of the two is more future proof), there are a couple of drawbacks when using
support.v7.preference
.Important drawbacks of using Solution 2 are mentioned below.
Dependencies:
Theme: You'll need to define a
preferenceTheme
in your styles.xml, otherwise running your app will raise an exception.You might wanna split this into different styles for 7+/14+/21+. A lot of people complain about this being buggy at the time of this writing. There is a very comprehensive answer available here.
Behavior changes: using the native preferences is extremely straight forward: all you need to do is define/maintain your
preferences.xml
and useaddPreferencesFromResource(R.xml.preferences)
within yourPreferenceFragment
. Custom preferences are easily done by sub-classingDialogPreference
, and then just referenced to within thepreferences.xml
... bam, works.Unfortunately,
support.v7.preference
has had everything related to dealing withFragment
stripped out, making it loose a lot of it's built-in functionality. Instead of just maintaining an XML, you now have to sub-class and override a lot of stuff, all of which is unfortunately undocumented.PreferenceScreens:
PreferenceScreens
are no longer managed by the framework. Defining aPreferenceScreen
in yourpreference.xml
(as described in the docs) will display the entry, but clicking on it does nothing. It's now up to you to deal with displaying and navigating sub-screens. Boring.There is one approach (described here), adding a
PreferenceFragmentCompat.OnPreferenceStartScreenCallback
to yourPreferenceFragmentCompat
. While this approach is quickly implemented, it simply swaps the content of the existing preference fragment. Downside is: there is no back navigation, you're always 'at the top', which isn't very intuitive for the user.In another approach (described here), you'll also have to manage the back stack in order to achieve back navigation as expected. This uses
preferenceScreen.getKey()
as a root for each newly created/displayed fragment.When doing so, you might also stumble over the
PreferenceFragments
being transparent by default and adding up oddly on top of each other. People tend to overridePreferenceFragmentCompat.onViewCreated()
to add something likeCustom DialogPreference: Making your own preferences has also gone from trivial to boring.
DialogPreference
now has anything that deals with the actual dialog, removed. That bit now lives inPreferenceDialogFragmentCompat
. So you'll have to sub-class both, then deal with creating the dialog and displaying it yourself (explained here).Looking at the source of
PreferenceFragmentCompat.onDisplayPreferenceDialog()
shows that it knows how to deal with exactly 2 dialog preferences (EditTextPreference
,ListPreference
), everything else you'll have to implement yourself usingOnPreferenceDisplayDialogCallback
s... one wonders, why there is no functionality to handle sub-class ofDialogPreference
!Here is some code that implements most of these workarounds and boxes them in a lib module:
https://github.com/mstummer/extended-preferences-compat.git
Main intentions were:
Activity
andPreferenceFragment
in each app/projects.preference.xml
is now again the only per-project file to change/maintain.PreferenceScreens
(sub-screens) as expected.DialogPreference
to restore the native behavior.DialogPreference
.Don't think it's clean enough to be just used out of the box, but it might give you some hints when dealing with similar issues. Give it a spin and let me know if you've got any suggestions.