HowTo use support.v7.preference with AppCompat and potential drawbacks - android

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?
Official docs here:
http://developer.android.com/guide/topics/ui/settings.html
http://developer.android.com/reference/android/support/v7/preference/Preference.html

Solution 1: Native PreferenceFragment with AppCompatActivity
In AndroidStudio, choose File > New Project >...> SettingsActivity. This template uses a workaround that retrofits the native PreferenceFragment to work with AppCompatActivity, similar to the support.v4.Fragment or the support.v7.PreferenceFragmentCompat.
Pro: you can now use the native Preference functionality within an
AppCompat app. It's a quick approach when using the AS template, and you can stick to the existing Preference docs and workflows.
Con: the retrofitting isn't very intuitive or clean. Also since it's usually advisable to use support libs where available, I'm not sure how future-proof this approach is.
Solution 2: support.v7.preference.PreferenceFragmentCompat with AppCompatActivity
Pro: maximizes compatibility
Con: a lot of gaps to bridge. Also this might not work with any of the existing preference-extensions-libs out there (eg. ColorPicker or FontPreferences).
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:
dependencies {
...
compile 'com.android.support:appcompat-v7:23.1.1'
compile 'com.android.support:preference-v7:23.1.1'
compile 'com.android.support:support-v4:23.1.1'
}
Theme:
You'll need to define a preferenceTheme in your styles.xml, otherwise running your app will raise an exception.
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light">
<!-- Customize your theme here. -->
<item name="preferenceTheme">#style/PreferenceThemeOverlay</item>
</style>
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 use addPreferencesFromResource(R.xml.preferences) within your PreferenceFragment. Custom preferences are easily done by sub-classing DialogPreference, and then just referenced to within the preferences.xml... bam, works.
Unfortunately, support.v7.preference has had everything related to dealing with Fragment 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 a PreferenceScreen in your preference.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 your PreferenceFragmentCompat. 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 override PreferenceFragmentCompat.onViewCreated() to add something like
// Set the default white background in the view so as to avoid transparency
view.setBackgroundColor(ContextCompat.getColor(getContext(), R.color.background_material_light));
Custom 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 in PreferenceDialogFragmentCompat. 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 using OnPreferenceDisplayDialogCallbacks... one wonders, why there is no functionality to handle sub-class of DialogPreference!
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:
Remove the need to extend and fiddle with Activity and PreferenceFragment in each app/projects. preference.xml is now again the only per-project file to change/maintain.
Handle and display PreferenceScreens (sub-screens) as expected.
Un-split DialogPreference to restore the native behavior.
Handle and display any sub-class of 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.

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:
root = this.getPreferenceScreen();
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:
public boolean onPreferenceStartScreen(PreferenceFragmentCompat preferenceFragmentCompat, PreferenceScreen preferenceScreen) {
preferenceFragmentCompat.setPreferenceScreen(preferenceScreen);
backButton.setVisibility(View.VISIBLE);
return true;
}
Finally, the backButton clickhandler:
setPreferenceScreen(root);
back.setVisibility(View.GONE);
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.

Related

New Android 12+ MaterialSwitch and androidx.preference

So i'm updating my apps to fully support Monet and Material You guidelines, and the official site mentions a new design for the switches. I used it, and that's the result:
I have a preference screen using Androidx preferences library, latest version available at the time of writing, and the only way i found to theme the switches (except the manual theming, which makes no sense) is to use this line in the app's theme:
<item name="switchStyle">#style/Widget.Material3.CompoundButton.MaterialSwitch</item>
And using SwitchPreferenceCompat (it doesn't work in the regular SwitchPreference) this is what i get:
Regardless of the width (which is different, but can be changed) the disabled state is completely different and doesn't match the rest of the app. Why? and most importantly, why do they suggest to use a library which:
Doesn't support Material You out of the box
Doesn't support any new Material3 component
It's hard to properly customize in general
?
I don't want to be too critical, but this is out of my understanding.
EDIT: at the moment, i'm using switchCompat everywhere, to make the app uniform. Looking at the system apps, i can find 4 different type of switches: a custom switch similar to the second screenshot, the old one and the two types in this question. That's hella confusing.
I understand Google stance on this, they don't want to make androidx.* packages dependent to Material library itself, maybe they should provide a separate preference package but this time with fully Material widgets.
In order to have the brand new MaterialSwitch of Material 1.7.0 with preference, I've overridden its widgetLayout with a custom layout by android:widgetLayout="#layout/preference_material_switch" (in fact I applied that programmatically like .widgetLayoutResource = R.layout.preference_material_switch) and put the following on preference_material_switch.xml layout file,
<?xml version="1.0" encoding="utf-8"?>
<!-- Derived from https://github.com/androidx/androidx/blob/8cb282cc/preference/preference/res/layout/preference_widget_switch_compat.xml -->
<com.google.android.material.materialswitch.MaterialSwitch xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/switchWidget"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#null"
android:clickable="false"
android:focusable="false" />
And here is the result,
The same problem, After seeing these replies, I'm thinking to build the settings fragment without androidx.preference.
MKevin3 said:
I hate the provided Android preferences setup and look. So many times they changed the rules and broke what I had.
Not that this helps you probably but I just did my own to avoid all the headaches and I am in full control of the look. If push comes to hove you might consider doing this as well instead of fighting the "Android Way".
sc00ty said:
I gave up trying to use their widgets and fragments. It was so much less of a headache to spend a little time making my own compound widgets for each setting type.

Best practice to bring Material look to Preferences using AppCompat?

What is the best way to bring Material design to the preferences using AppCompat?
I've been reading some questions and the main points seem to be:
The official guidelines suggest avoiding PreferenceActivity if working for API>11 (and I am). Instead one should use PreferenceFragments;
Using PreferenceActivity would be bad also because you can't extend both PreferenceActivity and ActionBarActivity;
You might use a general ActionBarActivity and load PreferenceFragment (API>11) into it. This way preference widgets loaded from resources gets tinted by AppCompat, but that's not true for any widget (see here );
With the suggested solution, anyway, the whole UI look isn't quite Material (see image on Android 4.2), even if inheriting from AppCompat theme.
Another way could be to extend a PreferenceActivity (API>1). As said, you won't extend ActionBarActivity but just inherit AppCompat theme.
Last, you could just make your own Activity extending ActionBarActivity, and deal with widgets, layouts, margins, SharedPreferences yourself.
At the moment I am not aware of a working solution. I've seen some discussion going on in a Chris Banes post introducing Material, but no solution was suggested. I'm asking:
Did some of you succeed in bringing a pure (as possible) Material look to preferences, and how?
Will there be improvements in v7 regarding this issue in the future?
I'm using AndroidDeveloperLB/MaterialPreferenceLibrary in my projects.
Easy to install. ImportEasy to use. Sample

Make ActionBar overlay using ActionBarSherlock

The last few days I've been doing great stuff in development, but I've seem to have hit a wall on something probably stupid simple. It's annoying. I was hoping I could get some help.
What I'm trying to do
I'm trying to create the awesome transparent ActionBar animation effect from Google Music (as described by Cyril Mottier here)
The problem
I can't even get the actionbar to go into overlay / transparent mode.
My set-up
I've got a library-oriented set-up in Eclipse. I've got a library project (let's call it master) that contains all the actual code and activities. Then I've got a project, that just plugs into the master library (let's call it slave). Lastly there's some open source support libraries that master uses (among others is ActionBarSherlock).
The activity I'm trying to apply this effect to is in the master library. It is declared in slave's manifest as com.example.master.DetailActivity, and that works like a charm.
Also: The theme for the activity may come from several places. Either it's set by slave's manifest, or by master at runtime. Themes may come from the master OR the slave project.
What I've tried
Feels like everything. I've started out by creating a custom theme based on Cyril's article. I made sure that for every item I've had a version with and without the android: prefix as per Jake Wharton's instructions.
I've also tried setting it at runtime using requestWindowFeature(Window.FEATURE_ACTION_BAR_OVERLAY);
What I've got
Nothing. The theme applied succesfully (I can see the actionbar's colors change appropriately), but it NEVER goes into overlay mode, nor does it become transparent.
Help would be greatly appreciated, and would probably be helpful to other's trying this effect.
EDIT:
Here are my themes and styles. Codes are kinda messy because I've been experimenting with them to see where I was going wrong. AppTheme and Theme.TranslucentActionBar.ActionBar.Overlay both won't work.
/res/values/
styles.xml
themes.xml
/res/values-v11/
styles.xml
themes.xml
I'm testing on a Nexus 4 with 4.2.2 installed as well as a 2.3.3 emulator.
OK, so apparently, with my set-up, you can't do it using themes.
I ended it up doing it, globally like this.
setTheme(theme.whatever);
requestWindowFeature(Window.FEATURE_ACTION_BAR_OVERLAY);
super.onCreate(savedInstanceState);
setContentView(R.layout.content);
getSupportActionBar().setBackgroundDrawable(getResources().getDrawable(R.drawable.color)); // This is #212121 solid color for the dark action bar.
The lines are spread out over my code, but this is the gist of it. The order is important.
I also found out that sliding menu seemed to be blocking the overlay mode. I had to remove the instance for it to work. I'm still looking for a way to enable both overlay mode and the sliding menu.

How to style the children Views from the parent View?

I am currently trying to get the look of my app right. But I am having problems figuring out how to even set up a way to change themes. For one thing, is there even a way to change styles through code? I checked the method list and I saw nothing. This leads me to my actual question; is there a way that, like CSS, in which you style the parent, and then have it trickle down but also changed depending on the View? I looked at the Android docs, and they did not show any examples of this. Hopefully someone can give me an idea as to how to accomplish this, or if its not possible, to let me know that as well. Thanks in advance.
You should be able to do this using styles and themes. I've implemented this using Jake whartons Sherlock action bar. (I'm not certain if it's necessary) It involves using the comparability library which gives you the ability to use fragments and loaders as well. Look at his democode at http://actionbarsherlock.com/download.html. Look for where themes are mentioned and you will find the information you need. In the demo app you can change the theme in the top right corner and see how it affects the activities look and feel. It also shows many of the features available and the code to write them. I have found this an invaluable resource and it should show you how to theme your app.

Customizing a project library in Android

I'm using this library project in my Android application. But I need to make some customization for it.
For example, if I need a EditText instead of the provided TextView, what is the best practice to customize the library for my needs without writing code in the Project Library?
I made this customization by defining in the library's actionbar.xml layout a EditText instead of TextView, but I don't like this approach.
Do you have any guidelines, tutorials that could help me out?
In your special case I would suggest to use original compatibility's-package actionBar.
But to answer your question: you could always extend classes from the Library, which I think is the best practice if the library should kept untouched. Overriding Methods which you want to change their behavior keeps anything clean. You do the exact same every time you extend android-sdk classes, which you cannot change like you want.
In the case of changing layouts I'm not quite shure. I think I can remember that if the lib has an actionbar.xml and you have an actionbar.xml inside your project, too, yours will win. just like an "overriding layouts" feature

Categories

Resources