Reputation: 8408
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
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
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