New Preference support library incorrect theme at runtime
S

1

5

I'm trying to use the new Preference v14 Support library. To give the preferences a material style, I use the following style on my Activity:

<style name="PreferenceTheme" parent="@style/Theme.AppCompat.Light.DarkActionBar">
    <item name="preferenceTheme">@style/PreferenceThemeOverlay.v14.Material</item>
</style>

That works fine. My problem is that when I add new Preferences at runtime, they get inflated using an old theme. Here's a screenshot of the result:

WTF

As you can see, the first preference, added via XML, has the new Material style, while the others don't.

Do you have any hint on how to solve the problem?

EDIT Here's an example of code I use to add the Preference at Runtime:

import android.support.v7.preference.ListPreference;

for (...) {
        final ListPreference p = new ListPreference(getActivity());
        p.setTitle(name);
        p.setSummary(langname);
        p.setEntryValues(langEntryValues);
        p.setEntries(langDisplayValues);
        p.setDialogTitle(R.string.select_language);

        category.addPreference(p);
    }

PS: The same behavior occurs with android.support.v7.preference.Preference

Summand answered 8/9, 2015 at 8:6 Comment(3)
What's your device's API level? Also, could you post the code you use to add new preferences?Gaffe
I've tested this under API 22 and 23. Same behavior. See updated answer for code.Summand
I see, looking into the issue right now...Gaffe
Q
17

The problem, you're facing, is related to Context and how its theming works. Your code retrieves a context by passing getActivity() to the constructor, however, this is not the context you want. Here's the solution that applies the correct styles:

final Context ctx = getPreferenceManager().getContext();

for (...) {
    final ListPreference p = new ListPreference(ctx);
    // [...]

    category.addPreference(p);
}

Explanation

Here's PreferenceFragmentCompat's onCreate(...) method:

public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    TypedValue tv = new TypedValue();
    this.getActivity().getTheme().resolveAttribute(attr.preferenceTheme, tv, true);
    int theme = tv.resourceId;
    if(theme <= 0) {
        throw new IllegalStateException("Must specify preferenceTheme in theme");
    } else {
        this.mStyledContext = new ContextThemeWrapper(this.getActivity(), theme);
        this.mPreferenceManager = new PreferenceManager(this.mStyledContext);
        // [...]

        this.onCreatePreferences(savedInstanceState, rootKey);
    }
}

The important lines:

this.getActivity().getTheme().resolveAttribute(attr.preferenceTheme, tv, true);
int theme = tv.resourceId;

Here the preferenceTheme is being retrieved from the Activity's theme. If it exists (i.e. theme is not 0), PFC (PreferenceFragmentCompat) creates a new theme wrapper which will contain the styling infos:

this.mStyledContext = new ContextThemeWrapper(this.getActivity(), theme);

Using this styled context, the PFC can now create the PreferenceManager:

this.mPreferenceManager = new PreferenceManager(this.mStyledContext);

This PFC's root style is now the preferenceTheme which contains all the different sub-styles (preferenceStyle for example).

The problem with your solution is that the Preference class is looking for a preferenceStyle attribute in the contructor-passed context. However, it's only defined in your preferenceTheme, not in the Activity's theme.

Quag answered 8/9, 2015 at 12:30 Comment(2)
I'll add a little more explanation, just wanted to give you a quick fix.Gaffe
No problem, added this interesting case to my support preference-v7 bugfix project.Gaffe

© 2022 - 2024 — McMap. All rights reserved.