Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

New Preference support library incorrect theme at runtime

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

like image 700
Spotlight Avatar asked Sep 08 '15 08:09

Spotlight


1 Answers

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.

like image 179
Gergely Kőrössy Avatar answered Nov 03 '22 00:11

Gergely Kőrössy