onSharedPreferenceChanged called multiple times.... why? - android

I have a preference Activity, at first when I chance a preference the onPreferenceChange is triggered once as expected.
However, after some time (going to different activities and such) the onPreferenceChange is called twice.
I see in the debugger that the WeakHashMap for the mListeners is 1 in the beginning and then becomes greater than 1, but not sure why?
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getPrefs();
int preferencesResource = 0; // R.xml.preferences;
preferencesResource = getResources().getIdentifier("pref", "xml",
getPackageName());
addPreferencesFromResource(preferencesResource);
listener = new SharedPreferences.OnSharedPreferenceChangeListener() {
#Override
public void onSharedPreferenceChanged(SharedPreferences arg0,
String arg1) {
// Why is this called once then sometimes twice!!
Log.i("PreferencesActivity", "OnPreferenceChanged()");
}
};
prefs.registerOnSharedPreferenceChangeListener(listener);
}
protected void onDestroy() {
super.onDestroy();
listener = null;
prefs.unregisterOnSharedPreferenceChangeListener(listener);
prefs = null;
}
public Preferences getPrefs() {
if (prefs == null) prefs = new Preferences(this);
return prefs;
}

You've placed unregisterOnSharedPreferenceChangeListener() in onDestroy() and it's not called on all activity restarts.
Look at the activity lifecycle diagram. Conclusion is that proper way to do this is to place registerOnSharedPreferenceChangeListener() and unregisterOnSharedPreferenceChangeListener() in onResume() and onPause() respectively.

This isn't for a LiveWallpaper by any chance is it? It sounds to me like you have two instances of the same class running(I.E. The LiveWallpaper itself as well as the preview because you're in settings). If it seems like they happen instantly right on top of each other and there is no delay than most likely you have the same listener running twice.

Related

Updating a SharedPreferences from another activity

I have two activities. In the first activity, I have a SharedPreferences with a TextView, which displays the amount of data in the SharedPreferences. These codes are written in the onCreate()
SharedPreferences coin = getSharedPreferences("saved", MODE_PRIVATE);
SharedPreferences.Editors editor2 = coin.edit();
editor2.putInt("t", 0).apply();
editor.commit();
TextView name = (TextView) findViewById(R.id.textview);
Integer a = coin.getInt("t", 0);
name.setText(a.toString());
In the second activity, I wanna add 10 points to the data and update the SharedPreferences. These codes are not inside onCreate().
SharedPreferences coin = getSharedPreferences("saved", MODE_PRIVATE);
SharedPreferences.Editors editor2 = coin.edit();
int a = coin.getInt("t", 0);
int b = a + 10;
editor2.putInt("t", Integer.valueOf(b));
The problem is, after adding 10 units there is no update in the TextView so I don't even know if the 10 units have been added correctly to SharedPreferences or not.
Please help
If I understand, you would like reflect the final value of t in SharedPreference in your first activity after its updated in second activity?
To do that, create a function updateNameTV() which would contain logic to update the TextView with the value of t from SharedPreference
and call that function in onResume() of the FIRST activity like:
#Override
protected void onResume() {
// TODO Auto-generated method stub
updateNameTV();
super.onResume();
}
So when Activity 2 is launched, Activity 1 goes in back of the stack and onPause() gets called.. when we return to Activity 1 this activity is pop back and onStart() followed by onResume() is called.
These are the lifecycle events more here
So the plan is, when onResume() is called we update the TextView value with the latest SharedPreference
And we do that before calling super.onResume() such that update is performed before handing over the control to next lifecycle event. Hope this gives more clarity.
You have to register OnSharedPreferenceChangeListener in your first activity in order to listen to the changes and then update your TextView with the updated value.
Implement OnSharedPreferenceChangeListener in your FirstActivity:
public class FirstActivity extends AppCompatActivity implements
SharedPreferences.OnSharedPreferenceChangeListener {
...
#Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String s) {
// This method will be called when there is a change in SharedPreference
// Update your textview here
if(s.equals("t")) {
Integer a = sharedPreferences.getInt("t", 0);
name.setText(a.toString());
}
}
}
In your FirstActivity's onStart() and onStop() method, manage the listener:
#Override
protected void onStart() {
super.onStart();
getSharedPreferences("saved", MODE_PRIVATE).registerOnSharedPreferenceChangeListener(this);
}
#Override
protected void onStop() {
getSharedPreferences("saved", MODE_PRIVATE).unregisterOnSharedPreferenceChangeListener(this);
super.onStop();
}
But remember, once you leave your FirstActivity, the listener gets unregistered and you will no longer receive updates. To reflect the updates even after the activity go background, you can just update your textview in your onStart() so that when the FirstActivity starts, the textview gets updated with the latest value from SharedPreferences:
#Override
protected void onStart() {
super.onStart();
SharedPreferences coin = getSharedPreferences("saved", MODE_PRIVATE);
coin.registerOnSharedPreferenceChangeListener(this);
// Also Update the textview here
Integer a = coin.getInt("t", 0);
name.setText(a.toString());
}
Now, any update to the preference from anywhere will also change the textview text as soon as the FirstActivity comes into foreground or launches.

Listener behavior in relation to Activity lifecycle

I have an Activity with a private OnSharedPreferenceChangeListener, the listener's work is defined on the onCreate() method of the Activity. The listener is registered to the sharedPreferences of the application.
The change itself is triggered by a Service in response to an sms received intent.
Will the listener receive the callback when the Activity itself has died? are there cases where it will not?
The listener is defined (roughly):
private OnSharedPreferenceChangeListener _sharedPreferenceListener;
public void onCreate(Bundle bundle){
...
_prefs = PreferenceManager.getDefaultSharedPreferences(this);
_prefs.registerOnSharedPreferenceChangeListener(_sharedPreferenceListener);
...
_sharedPreferenceListener = new SharedPreferences.OnSharedPreferenceChangeListener(){ /*doing some work here*/};
...
}
please igonre the logic here if correct or not, assume that the code works, my main concern is how the listener reacts to changes in the lifecycle of the activity.
Thanks,
actually, since the listener doesn't know anything about the activity (and as such you can use it anywhere , not just in an activity), you will get notified no matter where you use it.
Also, since you can't know for sure what it does with the context , you should use the application context instead in this case (so that you won't have memory leaks, though I doubt it needs a reference to the activity).
Of course, if the listener itself is referenced by weak reference, and the activity doesn't have any reference to itself on any other class, the listener can be GC-ed too. You can see in the code of Android (or at least of API 19) that in the class "android.app.SharedPreferencesImpl" (example link here) , you have a WeakHashMap of listeners, so it might mean that the activity that hosts the listener can be GC-ed and so the listener will stop from being called. Here is the relavant code of Android:
private final WeakHashMap<OnSharedPreferenceChangeListener, Object> mListeners =
new WeakHashMap<OnSharedPreferenceChangeListener, Object>();
...
public void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {
synchronized(this) {
mListeners.put(listener, mContent);
}
}
So, as I've written, best if you just put the application context in case you wish to keep listening to this event.
Or, in case you do wish to stop listening to this event, just unregister it when the activity is being destroyed.
to prove it, you can simply run your app...
here's my proof app:
MainActivity.java
public class MainActivity extends Activity {
#Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
preferences.registerOnSharedPreferenceChangeListener(new SharedPreferences.OnSharedPreferenceChangeListener() {
#Override
public void onSharedPreferenceChanged(final SharedPreferences sharedPreferences, final String key) {
android.util.Log.d("AppLog", "changed!");
}
});
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
startActivity(new Intent(MainActivity.this, Activity2.class));
}
}, 1000);
finish();
}
}
Activity2.java
public class Activity2 extends Activity {
#Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_activity2);
//if you call here System.gc(); , you have a good chance that the listener won't be called
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
//this may or may not cause the listener to write to the log
final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(Activity2.this);
preferences.edit().putBoolean("test", true).commit();
}
}, 1000);
}
}
Will the listener receive the callback when the Activity itself has died?
-> No, it won't. Because when your activity dies, the _prefs and _sharedPreferenceListener fields will be destroyed.
You could check this question for more details on OnSharedPreferenceChangeListener :
SharedPreferences.onSharedPreferenceChangeListener not being called consistently
You must un-register the listener in onDestroy() of activity, else Activity object will stay in memory.

edit text saving status in android

i did a lot of trials before coming here, i need that user find the same values entered in
an activity when he returns to , i did this code but it is not working :
public class ActivityUn extends Activity {
#Override
public void onSaveInstanceState(Bundle savedInstanceState) {
savedInstanceState.putString("arm_1", rm_1ts);
savedInstanceState.putString("arm_2", rm_2ts);
super.onSaveInstanceState(savedInstanceState);
}
public void ajouter(View v) {
db.open();
long id = db.insertMENAGE(rm_1ts,rm_2ts); }
EditText rm_1;
EditText rm_2;
String rm_1ts = "";
String rm_2ts = "";
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_un);
rm_1 = (EditText)findViewById(R.id.rm_1);
rm_2 = (EditText)findViewById(R.id.rm_2);
if (savedInstanceState != null) {
// Restore value of members from saved state
rm_1ts = savedInstanceState.getString("arm_1");
rm_2ts = savedInstanceState.getString("arm_2");
rm_1.setText(rm_1ts);
rm_2.setText(rm_2ts);
}
Button bton = (Button)findViewById(R.id.ajoutUn);
bton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
rm_1ts = rm_1.getText().toString();
rm_2ts = rm_2.getText().toString();
ajouter(v);
}
});
The problem is that onSaveInstanceState is only called when your Activity is killed via System to free memory or a 3rd party app. But when you press a back button in an Activity it simply is equivalent to finish(). In this case onSaveInstanceState is not called. So if you want to store data every time your Activity is created just store the values in a SharedPreference. You can store the values either in onStop or onPause method depending on your needs.
Here you can learn more about SharedPrefence and other types of storage options.
For the sake of simplicity, you could store the value in the SharedPreferences.
getSharedPreferences().edit().putString("myEditText", yourValue).commit();
That's it.
Try restoring the state in onStart or onResume instead of onCreate. onCreate is only called if the activity needs to be recreated after it is destroyed. Often the activity will be retained in the background in a stopped state, and won't call onCreate when it comes back.
Anywhere in your file, put:
#Override
public void onResume() {
super.onResume();
//Your code here
}

How to save/restore(update) ref to the dialog during screen rotation?(I need ref in onCreate method of activity.)

protected Dialog onCreateDialog(int id) {
...
AlertDialog.Builder adb = new AlertDialog.Builder(this);
...
mydialog = adb.create();
...
}
But onCreateDialog runs after onCreate.
If you want to be backwards compatible, you do it as follows.
class MyActivity extends Activity {
protected static final class MyNonConfig {
// fill with public variables keeping references and other state info, set to null
}
private boolean isConfigChange;
private MyNonConfig nonConf;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.my_layout);
nonConf = (MyNonConfig)getLastNonConfigurationInstance();
if (nonConf == null) { nonConf = new NonConfig(); }
// handle the information of the nonConf objects/variables
...
}
#Override
protected void onStop() {
super.onStop();
isConfigChange = false;
}
#Override
public Object onRetainNonConfigurationInstance() {
isConfigChange = true;
return nonConf;
}
#Override
protected void onDestroy() {
super.onDestroy();
// handle objects in nonConf, potentially based on isConfigChange flag
...
}
The nonConf objects will survive all configuration changes (but not real stops of your app). Also, the isConfigChange flag tells you reliably if your activity is going to be re-created immediately or not. Thus, you can cancel/detach tasks or handle other resources adequately based on this information.
Edit: Please note that if onDestroy() is called then you can rely on the isConfigChange flag. Also, if Android is processing a configuration change then onDestroy() will be called. However, if Android is about to end your activity then the call to onDestroy() is optional because Android considers your activity to be killable right after it calls onPause() (pre-Honeycomb) or onStop() (Honeycomb and beyond). That's not a problem though, because if your activity is going to be killed, the state of your numerous objects isn't interesting to anyone anymore. However, if you want to be friendly, this is one more aspect to consider regarding the decision what to put into onPause() and onStop().
Hope this helps.

Simple persisting doesn't work

private static final String KEY = "qaz";
private String aString;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = (TextView) findViewById(R.id.txt);
final String saved;
if (savedInstanceState != null) {
saved = savedInstanceState.getString(KEY);
report("[" + saved + "]");
} else {
saved = null;
report("[NULL]");
}
if (aString==null && saved!=null) {
aString = saved;
report("A");
}
}
#Override
public void onSaveInstanceState(Bundle savedInstanceState) {
report("s");
savedInstanceState.putString(KEY, aString);
super.onSaveInstanceState(savedInstanceState);
}
#Override
protected void onPause() {
report("p");
super.onPause();
}
#Override
public void onBackPressed() {
report("b");
super.onBackPressed();
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.activity_main, menu);
return true;
}
private void report(String s) {
aString += " " + s;
Log.v("report", aString);
textView.setText(aString);
}
onSaveInstance is intended to persist a value from one instance of your activity to the next, for example when the activity is destroyed and recreated on an orientation change. Just remember that when your activity is recreated, it is an entirely new instance of the class you've defined to extend the Activity class and therefore, all local fields (variables) will be re-initialised.
As you've already discovered, SharedPreferences are a good way to persist a value beyond the Activity lifecycle and of course, there are others. The values saved in onSaveInstance and retrieved in onCreate are intended for you to be able to initialise a new instance of your activity to the same state it was before it was destroyed.
Imagine for example that you have a messaging application and your user has already entered several lines of text. They then accidentally flip the orientation (we've all done that right?) then flip it back. How frustrated would they be to see that their text had disappeared?! So a good developer would save the current content of the TextView in onSaveInstance and retrieve it in onCreate. If any value is retrieved, it is passed to the setText() method so that the user can continue from where they were.
Take a look here. Learning the Activity life cycle, how to control it and how to pass values between Activities (either 2 different Activities or 2 instances of the same Activity) is one of the keys to unlocking the Android kingdom.
http://developer.android.com/reference/android/app/Activity.html#ActivityLifecycle
The key point is to understand when each callback is called and, even more importantly, what may or may not be called by Android. For example, when your app is in the background,there are NO guarantees that your activity will be called again if Android decides to kill your app.
Good luck.

Categories

Resources