Android - How to set a preference from within another Activity - android

I have a PreferenceActivity class that handles the getting and setting of preferences within my application. But I have a "main" Activity class that makes a call to a web service right at start-up, and based on the return value from the web service, needs to update one of the preferences.
I can't seem to get this to work without throwing a NullPointerException error.
Here is the code from the main Activity:
protected void onPostCreate(Bundle savedInstanceState)
{
if(getWebServiceResult.equals("FALSE"))
{
//do stuff
}
else
{
myPreferences prefs = new myPreferences();
prefs.updateMyChoice("TRUE");
}
}
And here is the code from the PreferenceActivity class that is throwing the error:
public void updateMyChoice(String newText)
{
if(subscriber_opt_in == null) //this is coming up null
{
subscriber_opt_in = (EditTextPreference) findPreference("opt_in"); //error here
}
subscriber_opt_in.setText(newText);
subscriber_opt_in.setSummary(newText);
}
I need to know how to properly update this preference. If there's a way to do it within the main Activity class, that's even better, but if I have to do it through the PreferenceActivity class, I just need to understand how to do it.
Thanks!

You should use SharedPreference class:
http://developer.android.com/reference/android/content/Context.html#getSharedPreferences%28java.lang.String,%20int%29
Code examples and use here:
http://developer.android.com/guide/topics/data/data-storage.html#pref

Related

Can't resolve findPreference() from MainActivity?

I'm getting a compiler error cannot resolve method findPreference when I try to intialize an OnSharedPreferencesChanged listener in my MainActivity. According to the answer here:
findPreference() should be called from a class implementing PreferenceActivity interface
but I don't understand what the code to do this would be. How can I get rid of the compiler error and successfully set listeners for preference changes?
MainActivity.java
public class MainActivity extends FragmentActivity implements SharedPreferences.OnSharedPreferenceChangeListener {
private SharedPreferences.OnSharedPreferenceChangeListener listener;
#Override
protected void onCreate(Bundle savedInstanceState) {
...
//Test preference menu
listener = new SharedPreferences.OnSharedPreferenceChangeListener() {
public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
if (key.equals("pref_wood")) {
Preference woodPref = findPreference(key); //COMPILER ERROR HERE
MainActivity.getGLSurfaceView().setTexture("");
// Set summary to be the user-description for the selected value
woodPref.setSummary(sharedPreferences.getString(key, ""));
}
}
}
}
}
findPreference is a method which is part of both PreferenceFragment and PreferenceActivity - these are the Fragments/Activities that actually show your Preference screen (the activity is deprecated and you should be using the PreferenceFragment).
You're trying to use it in your MainActivity. This doesn't work because the Preference objects don't actually exist on this screen (they exist in another activity that usually have a PreferenceFragment as part of it). If you need to get access to a preference value of a preference in an activity that is not your preference screen, use SharedPreferences, something like:
SharedPreferences sharedPref = getActivity().getPreferences(Context.MODE_PRIVATE);
int defaultValue = getResources().getBoolean(R.bool.saved_high_score_default);
boolean wood = sharedPref.getBoolean(pref_wood, defaultValue);
You can check out the documentation for further examples.
If your MainActivity is supposed to be a screen that shows settings, then you should probably rename it and include a preference fragment inside of it.
I believe you're also going to run into trouble with setSummary because the Preference is not part of this activity, it's part of the activity where you actually modify the preferences. setSummary is used to update the actual UI of the Preference so that if you, for example, select one of three values when using a list preference, it shows the value you just selected on the screen.

Getting resources from a static context

I've read a few questions about this, but I wasn't happy with the answers, so I decided do ask about my particular example.
I'm developing and Android App that has a Settings screen with a few configurable integer parameters. All these parameters have a maximum and minimum value. Therefore, everytime the user sets a new value for those parameters, I want to validate them. If the new value is out of the defined bounds, I want to show a Toast informing the user of what went wrong.
On the other hand, because in some situations in my App the user can "spam" a button that may show a Toast, in order to avoid having Toast showing repetedly for a while, I created an Application class with a static Toast that is shown everytime I want to show a toast:
public class MyApplication extends Application {
private static Toast toast;
public static void showToast(Context context, String string){
//(...)
}
}
Back to the Settings page, here's how I implemented it:
public class SettingsActivity extends PreferenceActivity {
private Context context;
static SharedPreferences sharedPreferences;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
context = this;
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
getFragmentManager().beginTransaction().replace(android.R.id.content, new SettingsFragment()).commit();
}
public static class SettingsFragment extends PreferenceFragment implements SharedPreferences.OnSharedPreferenceChangeListener {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.preferences);
setListeners();
}
public void setListeners() {
setListenerA();
//(other listeners to other settings)
}
private void setListenerA() {
findPreference(KEY_PREF_A).setOnPreferenceChangeListener(
new Preference.OnPreferenceChangeListener() {
#Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
boolean isEmpty = newValue.toString().isEmpty();
//(other validations)
boolean isValid = !isEmpty; //&& (other validations)
if(!isValid){
if(isEmpty){
MyApplication.showToast(context, MyApplication.getResources().getString(R.string.toastPreferenceNullValue));
} else if(isAnotherReasonToFail1){
// another Toast
} // else if(other reasons to fail)
}
return isValid;
}
}
);
}
}
}
And here are my problems: MyApplication.getResources() is a non-static method and cannot be called from the static context of class SettingsFragment. Also context is not static (as it should not be) and can't also be referenced there.
I need to show that Toast because otherwise the user wouldn't have a clue why his settings weren't being applied. On the other hand, I need the error message to be stored in the strings.xml file, not only because that's how you do it, but also for future multi-language purposes.
I am not familiar with how Fragments work, and I made the Settings screen like this after reading a few articles (like this one) and some questions here. There might be a different way to make a Settings screen that allows me to do what I want, I just don't know any.
Can someone suggest an approach that fits my problem?
Thanks
EDIT: emerssso solved the resources part. Now the problem is only how to call the Toast without having a context.
Fragment has a getResources() method that is equivalent to calling Application::getResources(). The only caveat is that you have to make sure that the fragment is attached to an activity (i.e. getActivity() != null) or you risk throwing an exception.
See: https://developer.android.com/reference/android/app/Fragment.html#getResources()
More generally, getActivity() can be used to get a valid context whenever the fragment is attached to the activity, as Activity is an implementation of Context.
If you want to have a context reference even after a fragment has detached, you can store a reference to getActivity().getApplicationContext() safely in the fragment for later use, but this is probably not ideal.

Android Return from background : how to manage code?

When I'm back from the background, onResume() method is called. Okay. So if I want to do some special code when I'm back I can put it there.
What about if I've many activities ? Do I've to duplicate that code everywhere ? Should I used the Application class and call a method in each onResume() methods ?
Plus, I would need to access the UI (changes, dialogs, ...). Maybe the application class isn't that a good idea ... ?
Concretely, I want to refresh my cache every XX time and I want to do that check when the application (or activity) is back from the background but I don't know exactly how to do and where to do that.
How do you do that guys ? Thanks for help.
If it is similar code you could use a static helper functions class and pass this to the functions as the context so that you can do Activity specific functions.
If you just want to know when an APP is returning from background state (when you return to home or another app and back to your app again) maybe this can help you.
if you find a case when this do not apply, please let me know.
public class FatherClass extends Activity {
private static int activities = 0;
public void onCreate(Bundle savedInstanceState, String clase) {
super.onCreate(savedInstanceState);
}
protected void onRestart(){
super.onRestart();
if(activities == 0){
Log.i("APP","BACK FROM BACKGROUND");
}
}
protected void onStop(){
super.onStop();
activities = activities - 1;
}
protected void onStart(){
super.onStart();
activities = activities + 1;
}
}
All of your classes MUST extend from this one, or increment and decrement the value of this class.

onSharedPreferenceChanged not fired if change occurs in separate activity?

I've implemented onSharedPreferenceChanged in my main activity.
If I change the preferences in the main activity, my event fires.
If I change the preferences through my preferences screen (PreferenceActivity) my event does NOT fire when preferences are changed (because it's a separate activity and separate reference to sharedPreferences?)
Does anybody have a recommendation of how I should go about overcoming this situation?
Thanks!
EDIT1: I tried adding the event handler right in my preference activity but it never fires. The following method gets called during onCreate of my preference activity. When I change values, it never prints the message (msg() is a wrapper for Log.d).
private void registerChangeListener () {
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this);
sp.registerOnSharedPreferenceChangeListener(new OnSharedPreferenceChangeListener () {
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
msg (" ***** Shared Preference Update ***** ");
Intent i = new Intent();
i.putExtra("KEY", key);
i.setAction("com.gtosoft.dash.settingschanged");
sendBroadcast(i);
// TODO: fire off the event
}
});
}
The OnSharedPreferenceChangeListener gets garbage collected in your case if you use an anonymous class.
To solve that problem use the following code in PreferenceActivity to register and unregister a change listener:
public class MyActivity extends PreferenceActivity implements
OnSharedPreferenceChangeListener {
#Override
protected void onResume() {
super.onResume();
// Set up a listener whenever a key changes
getPreferenceScreen().getSharedPreferences()
.registerOnSharedPreferenceChangeListener(this);
}
#Override
protected void onPause() {
super.onPause();
// Unregister the listener whenever a key changes
getPreferenceScreen().getSharedPreferences()
.unregisterOnSharedPreferenceChangeListener(this);
}
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,String key)
{
// do stuff
}
Furthermore be aware that the listener only gets called if the actual value changes. Setting the same value again will not fire the listener.
see also SharedPreferences.onSharedPreferenceChangeListener not being called consistently
This happen because garbage collector. its works only one time. then the reference is collected as garbage. so create instance field for listener.
private OnSharedPreferenceChangeListener listner;
listner = new SharedPreferences.OnSharedPreferenceChangeListener() {
#Override
public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
//implementation goes here
}
};
prefs.registerOnSharedPreferenceChangeListener(listner);
I arrived here, like many others, because my listener won't be fired when I changed my boolean from true to false, or viceversa.
After much reading, and refactoring, switching contexts/inner classes/privates/static/ and the like, I realized my (stupid) error:
The onSharedPreferenceChanged is only called if something changes. Only. Ever.
During my tests, I was so dumb to click on the same button all the time, thus assigning the same boolean value to the preference all the time, so it did not ever change.
Hope this helps somebody!!
One other way of avoiding the problem is to make your activity the listener class. Since there is only one override method with a distinctive name you can do this:
public class MainActivity extends AppCompatActivity implements SharedPreferences.OnSharedPreferenceChangeListener
{
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
sharedPreferences.registerOnSharedPreferenceChangeListener(this);
...
}
#Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key)
{
...
}
}
Note the original question spoke of a MainActivity listening to setting changes in a PreferenceActivity. The asker then added an "EDIT1" and changed the question to listening in the PreferenceActivity itself. That is easier than the former and seems to be what all the answers assume. But what if you still want the former scenario?
Well, it will work too, but do not use OnResume() and OnPause() to register and unregister the listener. Doing so will cause the listener to be ineffectual because the user leaves the MainActivity when they use the PreferenceActivity (which makes sense when you think about it). So it will work, but then your MainActivity will still be listening in the background even when the user is not using it. Kind of a waste of resources isn't it? So there is another solution that seems to work, simply add a method to OnResume() to re-read all preferences. That way when a user finishes editing preferences in a PreferenceActivity, the MainActivity will pick them up when the user returns to it and you don't need a listener at all.
Someone please let me know if they see a problem with this approach.
Why don't you just add a onSharedPreferenceChanged in the rest of the activities where the preferences could change?
The garbage collector erases that... you should consider using an Application context instead...or just add the code when app launchs... and then add the the listener with application context...
Consider keeping PreferencesChangeListener inside Android App class instance. Although it's NOT a clean solution storing reference inside App should stop GC from garbage collecting your listener and you should still be able to receive DB change updates. Remember that preference manager does not store a strong reference to the listener! (WeakHashMap)
/**
* Main application class
*/
class MyApp : Application(), KoinComponent {
var preferenceManager: SharedPreferences? = null
var prefChangeListener: MySharedPrefChangeListener? = null
override fun onCreate() {
super.onCreate()
preferenceManager = PreferenceManager.getDefaultSharedPreferences(this)
prefChangeListener = MySharedPrefChangeListener()
preferenceManager?.registerOnSharedPreferenceChangeListener(prefChangeListener)
}
}
and
class MySharedPrefChangeListener : SharedPreferences.OnSharedPreferenceChangeListener {
/**
* Called when a shared preference is changed, added, or removed.
*/
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
if (sharedPreferences == null)
return
if (sharedPreferences.contains(key)) {
// action to perform
}
}
}
While reading Word readable data shared by first app,we should
Replace
getSharedPreferences("PREF_NAME", Context.MODE_PRIVATE);
with
getSharedPreferences("PREF_NAME", Context.MODE_MULTI_PROCESS);
in second app to get updated value in second app.

SharedPreferences.onSharedPreferenceChangeListener not being called consistently

I'm registering a preference change listener like this (in the onCreate() of my main activity):
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
prefs.registerOnSharedPreferenceChangeListener(
new SharedPreferences.OnSharedPreferenceChangeListener() {
public void onSharedPreferenceChanged(
SharedPreferences prefs, String key) {
System.out.println(key);
}
});
The trouble is, the listener is not always called. It works for the first few times a preference is changed, and then it is no longer called until I uninstall and reinstall the app. No amount of restarting the application seems to fix it.
I found a mailing list thread reporting the same problem, but no one really answered him. What am I doing wrong?
This is a sneaky one. SharedPreferences keeps listeners in a WeakHashMap. This means that you cannot use an anonymous inner class as a listener, as it will become the target of garbage collection as soon as you leave the current scope. It will work at first, but eventually, will get garbage collected, removed from the WeakHashMap and stop working.
Keep a reference to the listener in a field of your class and you will be OK, provided your class instance is not destroyed.
i.e. instead of:
prefs.registerOnSharedPreferenceChangeListener(
new SharedPreferences.OnSharedPreferenceChangeListener() {
public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
// Implementation
}
});
do this:
// Use instance field for listener
// It will not be gc'd as long as this instance is kept referenced
listener = new SharedPreferences.OnSharedPreferenceChangeListener() {
public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
// Implementation
}
};
prefs.registerOnSharedPreferenceChangeListener(listener);
The reason unregistering in the onDestroy method fixes the problem is because to do that you had to save the listener in a field, therefore preventing the issue. It's the saving the listener in a field that fixes the problem, not the unregistering in onDestroy.
UPDATE: The Android docs have been updated with warnings about this behavior. So, oddball behavior remains. But now it's documented.
this accepted answer is ok, as for me it is creating new instance each time the activity resumes
so how about keeping the reference to the listener within the activity
OnSharedPreferenceChangeListener listener = new OnSharedPreferenceChangeListener(){
public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
// your stuff
}
};
and in your onResume and onPause
#Override
public void onResume() {
super.onResume();
getPreferenceScreen().getSharedPreferences().registerOnSharedPreferenceChangeListener(listener);
}
#Override
public void onPause() {
super.onPause();
getPreferenceScreen().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(listener);
}
this will very similar to what you are doing except we are maintaining a hard reference.
The accepted answer creates a SharedPreferenceChangeListener every time onResume is called. #Samuel solves it by making SharedPreferenceListener a member of the Activity class. But there's a third and a more straightforward solution that Google also uses in this codelab. Make your activity class implement the OnSharedPreferenceChangeListener interface and override onSharedPreferenceChanged in the Activity, effectively making the Activity itself a SharedPreferenceListener.
public class MainActivity extends Activity implements SharedPreferences.OnSharedPreferenceChangeListener {
#Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String s) {
}
#Override
protected void onStart() {
super.onStart();
PreferenceManager.getDefaultSharedPreferences(this)
.registerOnSharedPreferenceChangeListener(this);
}
#Override
protected void onStop() {
super.onStop();
PreferenceManager.getDefaultSharedPreferences(this)
.unregisterOnSharedPreferenceChangeListener(this);
}
}
As this is the most detailed page for the topic I want to add my 50ct.
I had the problem that OnSharedPreferenceChangeListener wasn't called. My SharedPreferences are retrieved at the start of the main Activity by:
prefs = PreferenceManager.getDefaultSharedPreferences(this);
My PreferenceActivity code is short and does nothing except showing the preferences:
public class Preferences extends PreferenceActivity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// load the XML preferences file
addPreferencesFromResource(R.xml.preferences);
}
}
Every time the menu button is pressed I create the PreferenceActivity from the main Activity:
#Override
public boolean onPrepareOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
//start Preference activity to show preferences on screen
startActivity(new Intent(this, Preferences.class));
//hook into sharedPreferences. THIS NEEDS TO BE DONE AFTER CREATING THE ACTIVITY!!!
prefs.registerOnSharedPreferenceChangeListener(this);
return false;
}
Note that registering the OnSharedPreferenceChangeListener needs to be done AFTER creating the PreferenceActivity in this case, else the Handler in the main Activity won't be called!!! It took me some sweet time to realize that...
Kotlin Code for register SharedPreferenceChangeListener it detect when change will happening on the saved key :
PreferenceManager.getDefaultSharedPreferences(this)
.registerOnSharedPreferenceChangeListener { sharedPreferences, key ->
if(key=="language") {
//Do Something
}
}
you can put this code in onStart() , or somewhere else..
*Consider that you must use
if(key=="YourKey")
or your codes inside "//Do Something " block will be run wrongly for every change that will happening in any other key in sharedPreferences
So, I don't know if this would really help anyone though, it solved my issue.
Even though I had implemented the OnSharedPreferenceChangeListener as stated by the accepted answer. Still, I had an inconsistency with the listener being called.
I came here to understand that the Android just sends it for garbage collection after some time. So, I looked over at my code.
To my shame, I had not declared the listener GLOBALLY but instead inside the onCreateView. And that was because I listened to the Android Studio telling me to convert the listener to a local variable.
It make sense that the listeners are kept in WeakHashMap.Because most of the time, developers prefer to writing the code like this.
PreferenceManager.getDefaultSharedPreferences(getApplicationContext()).registerOnSharedPreferenceChangeListener(
new OnSharedPreferenceChangeListener() {
#Override
public void onSharedPreferenceChanged(
SharedPreferences sharedPreferences, String key) {
Log.i(LOGTAG, "testOnSharedPreferenceChangedWrong key =" + key);
}
});
This may seem not bad. But if the OnSharedPreferenceChangeListeners' container was not WeakHashMap, it would be very bad.If the above code was written in an Activity . Since you are using non-static (anonymous) inner class which will implicitly holds the reference of the enclosing instance. This will cause memory leak.
What's more, If you keep the listener as a field, you could use registerOnSharedPreferenceChangeListener at the start and call unregisterOnSharedPreferenceChangeListener in the end. But you can not access a local variable in a method out of it's scope. So you just have the opportunity to register but no chance to unregister the listener. Thus using WeakHashMap will resolve the problem. This is the way I recommend.
If you make the listener instance as a static field, It will avoid the memory leak caused by non-static inner class. But as the listeners could be multiple, It should be instance-related. This will reduce the cost of handling the onSharedPreferenceChanged callback.
While reading Word readable data shared by first app,we should
Replace
getSharedPreferences("PREF_NAME", Context.MODE_PRIVATE);
with
getSharedPreferences("PREF_NAME", Context.MODE_MULTI_PROCESS);
in second app to get updated value in second app.
But still it is not working...

Categories

Resources