wbk727
wbk727

Reputation: 8408

App theme not changing after onSharedPreferenceChanged is called

After creating a preferences activity, I've noticed that my main activity doesn't change themes when my checkbox preference is checked despite calling onSharedPreferenceChanged. Does anyone know what is wrong and how this can be fixed?

styles.xml

<!-- Base application theme. -->
<style name="AppTheme" parent="android:Theme.Material.Light.DarkActionBar">
    <!--<item name="android:windowBackground">@color/colorLight</item>-->
</style>

<style name="MyDarkMaterialTheme" parent="android:Theme.Material">
    <item name="android:windowBackground">@android:color/black</item>
</style>

<style name="MyLightMaterialTheme" parent="android:Theme.Material.Light.DarkActionBar">
    <item name="android:windowBackground">@color/colorLight</item>
</style>

MainActivity class

public class MainActivity extends Activity {

    boolean themeState;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setTheme(R.style.MyDarkMaterialTheme);

        setContentView(R.layout.activity_main);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.menu_main, menu);

        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case R.id.action_settings:
                Intent settingsIntent = new Intent(this, SettingsActivity.class);
                startActivity(settingsIntent);
                return true;

            default:
                return super.onOptionsItemSelected(item);
        }
    }


    @Override
    public void onResume(){
        super.onResume();
        loadPreferences();
        displaySettings();
    }

    private void loadPreferences(){
        SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
        themeState = sharedPreferences.getBoolean("pref_pref1", true);
    }

    public void displaySettings() {
        if (themeState) {
            setTheme(R.style.MyDarkMaterialTheme);
            recreate();
        } else {
            setTheme(R.style.MyLightMaterialTheme);
            recreate();
        }
    }
}

SettingsActivity class

public class SettingsActivity extends Activity {

    boolean themeState;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_settings);

        if (savedInstanceState == null) {
            Fragment preferenceFragment = new SettingsFragment();
            FragmentTransaction ft = getFragmentManager().beginTransaction();
            ft.add(R.id.pref_container, preferenceFragment);
            ft.commit();
        }
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        if (item.getItemId() == android.R.id.home) {
            final Intent intent = getParentActivityIntent();
            if(intent != null){
                intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
            }
            onBackPressed();
            return true;
        }
        return super.onOptionsItemSelected(item);
    }


    @Override
    public void onResume(){
        super.onResume();
        loadPreferences();
        displaySettings();
    }

    private void loadPreferences(){
        SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
        themeState = sharedPreferences.getBoolean("pref_pref1", true);
    }

    public void displaySettings() {
        if (themeState) {
            getApplication().setTheme(R.style.MyDarkMaterialTheme);
            recreate();
        } else {
            getApplication().setTheme(R.style.MyLightMaterialTheme);
            recreate();
        }
    }
}

SettingsFragment class

public class SettingsFragment extends PreferenceFragment {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        //Load the Preferences from the XML file
        addPreferencesFromResource(R.xml.app_preferences);
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        new SharedPreferences.OnSharedPreferenceChangeListener() {
            @Override
            public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
                // Settings activity or fragment should restart with changes applied

            }
        };
    }
}

xml/app_preferences

<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen
    xmlns:android="http://schemas.android.com/apk/res/android">

    <CheckBoxPreference
        android:key="pref_pref1"
        android:title="@string/dark_theme"
        android:defaultValue="false"
        android:layout="@layout/preference_multiline"
        />

</PreferenceScreen>

Csongi77's suggestion

public class SettingsFragment extends PreferenceFragment implements Preference.OnPreferenceChangeListener {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        //Load the Preferences from the XML file
        addPreferencesFromResource(R.xml.app_preferences);

        // Find appropriate preference
        CheckBoxPreference mThemePreference =(CheckBoxPreference)getPreferenceManager().findPreference("pref_pref1");
        // we have to set up listener in order for persisting change to new value
        mThemePreference.setOnPreferenceChangeListener(this);
        SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(mThemePreference.getContext());
        Boolean value=sharedPreferences.getBoolean("pref_pref1",true);
        onPreferenceChange(mThemePreference, value);
    }

    // overriding onPreferenceChange - if we return true, the preference will be persisted
    @Override
    public boolean onPreferenceChange(Preference preference, Object newValue) {
        String preferenceKey = preference.getKey();
        // we have to check the preference type and key, maybe later we have more preferences....
        if(preference instanceof CheckBoxPreference){
            if(preferenceKey.equals("pref_pref1")){
                ((CheckBoxPreference)preference).setChecked((Boolean)newValue);
                // ... do other preference related stuff here - if necessary, for example setSummary, etc...
                getActivity().setTheme(R.style.MyDarkMaterialTheme);
            } else {
                getActivity().setTheme(R.style.MyLightMaterialTheme);
            }
        }
        return true;
    }
}

Logcat

          Process: com.companyname.appname, PID: 4505
          java.lang.NullPointerException: Attempt to invoke interface method 'void com.companyname.appname.SettingsFragment$PreferenceXchangeListener.onXchange(java.lang.Boolean)' on a null object reference
              at com.companyname.appname.SettingsFragment.onPreferenceChange(SettingsFragment.java:57)
              at android.preference.Preference.callChangeListener(Preference.java:928)
              at android.preference.TwoStatePreference.onClick(TwoStatePreference.java:64)
              at android.preference.Preference.performClick(Preference.java:983)
              at android.preference.PreferenceScreen.onItemClick(PreferenceScreen.java:214)
              at android.widget.AdapterView.performItemClick(AdapterView.java:300)
              at android.widget.AbsListView.performItemClick(AbsListView.java:1143)
              at android.widget.AbsListView$PerformClick.run(AbsListView.java:3044)
              at android.widget.AbsListView$3.run(AbsListView.java:3833)
              at android.os.Handler.handleCallback(Handler.java:739)
              at android.os.Handler.dispatchMessage(Handler.java:95)
              at android.os.Looper.loop(Looper.java:135)
              at android.app.ActivityThread.main(ActivityThread.java:5221)
              at java.lang.reflect.Method.invoke(Native Method)
              at java.lang.reflect.Method.invoke(Method.java:372)
              at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:899)
              at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:694)

SettingsActivity class

public class SettingsActivity extends Activity implements SettingsFragment.PreferenceXchangeListener {
    private static final String TAG = SettingsActivity.class.getSimpleName();

    // declaring initial value for applying appropriate Theme
    private Boolean mCurrentValue;

    @Override
    protected void onCreate(Bundle savedInstanceState) {

        // Checking which Theme should be used. IMPORTANT: applying Themes MUST called BEFORE super.onCreate() and setContentView!!!
        Log.d(TAG, "onCreate:::: retrieving preferences");
        SharedPreferences mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
        mCurrentValue = mSharedPreferences.getBoolean("my_preference",false);
        Log.d(TAG, "onCreate:::: my_preference and mCurrentValue=" + mCurrentValue);
        if(mCurrentValue){
            // we have to use simple setTheme() instead getApplication.setTheme()!!!
            setTheme(R.style.DarkTheme);
            Log.d(TAG, "onCreate:::: setTheme:DarkTheme");
        } else {
            setTheme(R.style.LightTheme);
            Log.d(TAG, "onCreate:::: setTheme:LightTheme");
        }

        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_settings);

        Fragment preferenceFragment = new SettingsFragment();
        getFragmentManager().beginTransaction().add(R.id.preference_container, preferenceFragment).commit();
    }

    // callback method for changing preference. It's called only if "my_preference" has changed
    @Override
    public void onXchange(Boolean value) {
        // if value differs from previous Theme, we recreate Activity
        Log.d(TAG, "onXchange:::: \n has called");
        if (value!=mCurrentValue) {
            Log.d(TAG, "onXchange:::: \n new value!=oldValue");
            mCurrentValue=value;
            recreate();
        }
    }
}

SettingsFragment class

public class SettingsFragment extends PreferenceFragment implements Preference.OnPreferenceChangeListener {
    private static final String TAG = SettingsFragment.class.getSimpleName();

    // declaring PreferenceXchangeListener
    private PreferenceXchangeListener mPreferenceXchangeListener;

    public SettingsFragment() {
    }

    // declaring PreferenceXchangeListener in order to communicate with Activities
    public interface PreferenceXchangeListener {
        void onXchange(Boolean value);
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        //Load the Preferences from the XML file
        addPreferencesFromResource(R.xml.app_preferences);

        CheckBoxPreference mCheckBoxPreference = (CheckBoxPreference)findPreference("my_preference");
        mCheckBoxPreference.setOnPreferenceChangeListener(this);
    }

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        // on Attch we assign parent Activity as PreferenceXchangeListener
        try {
            mPreferenceXchangeListener = (PreferenceXchangeListener) context;
        } catch (ClassCastException e) {
            Log.e(TAG, "onAttach::::: PreferenceXchangeListener must be set in parent Activity");
        }
    }

    @Override
    public boolean onPreferenceChange(Preference preference, Object newValue) {
        String preferenceKey=preference.getKey();
        // only my_preference is checked in this case. Later you may add another behaviour to another preference change
        if(preferenceKey.equals("my_preference")){
            ((CheckBoxPreference)preference).setChecked((Boolean)newValue);
            // executing parent Activity's callback with the new value
            mPreferenceXchangeListener.onXchange((Boolean)newValue);
            return true;
        }
        // ... check other preferences here
        return false;
    }
}

Upvotes: 1

Views: 658

Answers (2)

Csongi77
Csongi77

Reputation: 329

Ok, here's the working version:

1) in MainActivity.java's onCreate method check current Theme before calling super.onCreate() and setContentView() and add it in a private global boolean variable (let's call it mTheme). Further info: Change Activity's theme programmatically

2) in MainActivity.java's onStart() method you should check whether the settings has been changed because onCreate won't called when you return from another Activity. If mTheme!=newSettingValue, call recreate(). Important: it's similar to onDestroy method, so previously set values may lost! https://developer.android.com/reference/android/app/Activity#recreate()

3) In your SettingsFragment you have to define an interface (ThemeXchangeListener) with an update(Boolean value) method. You also have to declare ThemeXchangeListener mListener field, too.

4) In SettingsFragment's onAttach(Context context) method assign context to mListener -> mListener=(ThemeXchangeListener)context;

5) In SettingsFragment's onPreferenceChange(Preference pref, Object value) call mListener.update((Boolean)value);

6) In SettingsActivity declare boolean for storing current Theme value(boolean mTheme). In onCreate() load preference and assign value for it. Before super.onCreate() and setContentView() assign appropriate Theme.

7) In SettingsActivity implement ThemeXchangeListener and override update(Boolean value) method. If value!=mTheme, call recreate() method. This will update your Theme at once.

You may check the full working code (with comments) at: https://github.com/csongi77/UpdateThemeOnPreferenceChange

It works fine on my Emulator from API15(IceCreamSandwich). Please let me know whether it works! Best regards, Cs

Upvotes: 1

Csongi77
Csongi77

Reputation: 329

Try this:

public class SettingsFragment extends PreferenceFragment implements Preference.OnPreferenceChangeListener {

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    //Load the Preferences from the XML file
    addPreferencesFromResource(R.xml.app_preferences);

    // Find appropriate preference
    CheckBoxPreference mThemePreference =(CheckBoxPreference)getPreferenceManager().findPreference("pref_pref1");
    // we have to set up listener in order for persisting change to new value
    mThemePreference.setOnPreferenceChangeListener(this);
    SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(mThemePreference.getContext());
    Boolean value=sharedPreferences.getBoolean("pref_pref1",true);
    onPreferenceChange(mThemePreference, value);
    }

// overriding onPreferenceChange - if we return 'true', the preference will be persisted
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
    String preferenceKey = preference.getKey();
    // we have to check the preference type and key, maybe later we have more preferences....
    if(preference instanceof CheckBoxPreference){
        if(preferenceKey.equals("pref_pref1")){
      // Edited this line *******               
        ((CheckBoxPreference)preference).setChecked((Boolean)newValue);   
            // ... do other preference related stuff here - if necessary, for example setSummary, etc...       
        }
    }
return true;
}

}

In a nutshell: implement OnPreferenceChange in your PreferenceFragment. When you override onPreferenceChane return true. In this case the older preference will be overwritten. Hope it helps (if yes, please don't forget to accept my answer)! Best regards, Cs

P.S: don't forget to uninstall app on Emulator

Upvotes: 1

Related Questions