Android: onPreferenceChange with validation - android

Quick overview:
I have built validation that works with onPreferenceChanged on a sample project designed just to test settings and how they're saved and such. The functionality is all there and works as I desire it to. I was hoping to be able to move this functionality over into the default generated SettingsActivity class since it appears to provide solid functionality and much needed headers for the expansion of the program.
The code itself I would like to implement is what's been provided below. Very briefly I have a check which decides if a specific verification or not needs to be implemented and if so it attaches the specific one, otherwise it attaches a generic one. I believe this to be a hacky method and would like an alternative if possible.
// Type of verification checking to attach
public void attachOnPreferenceChangedListener(Preference dsp_pref) {
if (dsp_pref.getKey().equals("et_targetPref")) {
attachTargetVerifier(dsp_pref);
} else { // Any non verifier specific is given a generic listen with summary updater
attachGenericVerifier(dsp_pref);
}
}
private void attachGenericVerifier(Preference dsp_pref) {
dsp_pref.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
#Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
updatePrefSummary(preference);
return true;
}
});
}
private void attachTargetVerifier(Preference dsp_pref) {
dsp_pref.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
#Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
Pattern dsl_pattern = Pattern.compile(URL_REGEX,Pattern.CASE_INSENSITIVE);
Matcher dsl_matcher = dsl_pattern.matcher(newValue.toString());
if (!dsl_matcher.matches()) {
Toast.makeText(getActivity(), "Invalid URL. Example: http://example.com/target", Toast.LENGTH_LONG).show();
} else if (dsl_matcher.group(1).toLowerCase().endsWith("/target")) {
Toast.makeText(getActivity(), "Preference Saved", Toast.LENGTH_LONG).show();
updatePrefSummary(preference);
return true;
} else if(dsl_matcher.group(2) == null) {
Toast.makeText(getActivity(), "URL must start with 'http://'", Toast.LENGTH_LONG).show();
} else {
}
return false;
}
});
}
What the default SettingsActivity provides is just a simple and straightforward OnPreferenceChange override, which probably means validation checking needs to be integrated here unless a more elegant method exists, in which case I'm all ears:
private static Preference.OnPreferenceChangeListener sBindPreferenceSummaryToValueListener = new Preference.OnPreferenceChangeListener() {
#Override
public boolean onPreferenceChange(Preference preference, Object value) {
String stringValue = value.toString();
if (preference instanceof ListPreference) {
// For list preferences, look up the correct display value in
// the preference's 'entries' list.
ListPreference listPreference = (ListPreference) preference;
int index = listPreference.findIndexOfValue(stringValue);
// Set the summary to reflect the new value.
preference.setSummary(
index >= 0
? listPreference.getEntries()[index]
: null);
} else if (preference instanceof RingtonePreference) {
// For ringtone preferences, look up the correct display value
// using RingtoneManager.
if (TextUtils.isEmpty(stringValue)) {
// Empty values correspond to 'silent' (no ringtone).
preference.setSummary(R.string.pref_ringtone_silent);
} else {
Ringtone ringtone = RingtoneManager.getRingtone(
preference.getContext(), Uri.parse(stringValue));
if (ringtone == null) {
// Clear the summary if there was a lookup error.
preference.setSummary(null);
} else {
// Set the summary to reflect the new ringtone display
// name.
String name = ringtone.getTitle(preference.getContext());
preference.setSummary(name);
}
}
} else {
// For all other preferences, set the summary to the value's
// simple string representation.
preference.setSummary(stringValue);
}
return true;
}
};
Appreciate any advice on how this can be done without it becoming unmanageable

The solution, for those interested, while not complete and 100% is to place the small validation block at the top, if validation fails a return false is carried out:
private static Preference.OnPreferenceChangeListener sBindPreferenceSummaryToValueListener = new Preference.OnPreferenceChangeListener() {
#Override
public boolean onPreferenceChange(Preference preference, Object value) {
String stringValue = value.toString();
///////////////////////////////////////////////////////////////////
// Preference Validation Section
///////////////////////////////////////////////////////////////////
// URL Check: http://whatev.er/targetstore
if (preference.getKey().equals("connect_url")) {
Pattern dsl_pattern = Pattern.compile(URL_REGEX,Pattern.CASE_INSENSITIVE);
Matcher dsl_matcher = dsl_pattern.matcher(stringValue);
if (dsl_matcher.matches() && // Match!
dsl_matcher.group(1).toLowerCase().endsWith("/targetstore")) { // Confirmed validity
Toast.makeText(dsl_context, "Preference Saved", Toast.LENGTH_LONG).show();
} else { // No match - don't save data.
Toast.makeText(dsl_context, "Invalid URL. Example: http://example.com/targetstore", Toast.LENGTH_LONG).show();
return false; // Stops result from saving
}
}
if (preference instanceof ListPreference) {
//List Pref setSummary
} else if (preference instanceof RingtonePreference) {
//Ringtone Pref setSummary
} else {
// For all other preferences, set the summary to the value's
// simple string representation.
preference.setSummary(stringValue);
}
return true;
}
};
There is still an issue whenever the settings are loaded, a "Preference Saved" prompt will be shown. Some check verifying whether data is being read and not set might help solve this, I have no knowledge of such a check however.

Related

how to show an error for EditTextPreference

I am trying to show the user an error when they enter a value for an EditTextPreference incorrectly.
Currently I have a onPreferenceChangeListener which returns true or false:
editTextPreference.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
#Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
if (newValue.toString().length() < 1 || Integer.parseInt(newValue.toString()) < 1) {
return false;
} else {
return true;
}
}
});
I found this question Design Android EditText to show error message as described by google, but it is for a EditText not an EditTextPreference. Now I am trying to find a similar solution that I can use for EditTextPreference.
Thank you for your help!

Reverting an android preference's value when a statement in another thread fails

I have settings in my app that I allow the user to manipulate using a PreferenceScreen with Preferences. However, I want to store the settings on a server so that the settings can persist over multiple devices. I have the following code that lets me do this:
private void updateSettingOnPrefChange(final Preference pref, final Setting setting) {
pref.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
#Override
public boolean onPreferenceChange(Preference preference, final Object newValue) {
try {
setting.update(newValue, new Callback<Boolean>() {
#Override
public void call(Boolean succeeded) {
if (!succeeded) {
Toast.makeText(getActivity(), "Setting failed to update. Please try again.", Toast.LENGTH_LONG).show();
//here I need to revert the value of the Preference without again calling the onChangeListener
}
}
}, getActivity());
} catch (Exception ex) {
if (BuildConfig.DEBUG)
ex.printStackTrace();
Toast.makeText(getActivity(), "Setting failed to update. Please try again.", Toast.LENGTH_LONG).show();
return false;
}
return true;
}
});
}
As you can see in the code the request is being run on a different thread using a custom Callback class to clean up based on the result of the call. The issue is the code will have already return true on the main thread.
How can I revert the value of the Preference in the callback function (preferably without also calling the onPrefChangeListener function so I don't get infinite recursion)?
Use the OnPreferenceClickListener instead of OnPreferenceChangeListener to listen for user taps on the setting field and then make your rpc request accordingly. If you have to update the value (in the case of a server failure) you can change the setting without firing the click listener and having the infinite loop.
https://developer.android.com/reference/android/preference/Preference.html#setOnPreferenceClickListener(android.preference.Preference.OnPreferenceClickListener)

Run Service only when on Wifi if user wants to

I made an app whos purpose is to download and set wallpaper in set intervals.
User can choose to do that only when connected to wifi or not.
Relevant code:
mWallpaperButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (mSwitchWifi.isChecked()) {
mConnectivity = mConnectionDetector.isConnectedToWifi();
} else {
mConnectivity = mConnectionDetector.isConnectedToInternet();
}
if (mConnectivity) {
my code here
}
The code above works fine for setting the wallpaper the first time.
My problem is, I need the Service to check if the user wants to update wallpaper only over WIFI before doing so. At the moment, wallpaper is updated regardless of mSwitchWifi state. (which is bad, because it can use mobile data and user sometimes doesn't want that.)
I tried running similar Switch code in Service but I can't because it must be called in a UI Thread.
I also tried couple of workarounds and Intent.putExtra but I get exception:
NullPointerException: Attempt to invoke virtual method on a null object reference
Any idea how to check network state in service?
My service code atm:
public static class Service extends IntentService {
public Service() {
super("wallpaperchanger-download");
}
#Override
protected void onHandleIntent(Intent intent) {
if (url == null) {
SharedPreferences sharedPreferences = getSharedPreferences(PREFS_NAME, MODE_PRIVATE);
String getUrl = sharedPreferences.getString(pref_urlKey, null);
if (getUrl != null) {
url = getUrl;
}
}
wm = WallpaperManager.getInstance(this);
try {
InputStream input = new URL(url).openStream();
Log.v(TAG, url);
wm.setStream(input);
input.close();
} catch (Exception e) {
e.printStackTrace();
}
loading = false;
Log.v(TAG, "Service Running Url " + url);
}
}
If you question is how to access the user selection inside a service/runnable/thread then you can use shared preferences to achieve this. So in your case when the user selects the choice for the first time you want to do something like this:
if(mSwitchWifi.isChecked()) { // i guess this is no wifi
SharedPreferences sharedPreferences = getSharedPreferences(PREFS_NAME, MODE_PRIVATE);
Editor editor = sharedPeredences.edit()
editor.putBoolean("isWifi", false)
} else { // guessing this is wifi
SharedPreferences sharedPreferences = getSharedPreferences(PREFS_NAME, MODE_PRIVATE);
Editor editor = sharedPeredences.edit()
editor.putBoolean("isWifi", true)
}
This is this code to check if it is true or false:
mWallpaperButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Boolean isWifi = isWifi()
if (!isWifi) { // i guess this is if not wifi
mConnectivity = mConnectionDetector.isConnectedToWifi();
} else if (isWifi) { // guessing this is wifi
mConnectivity = mConnectionDetector.isConnectedToInternet();
}
}
}
public Boolean isWifi() { // you can call this inside your service
SharedPreferences sharedPreferences = getSharedPreferences(PREFS_NAME, MODE_PRIVATE);
Boolean wifiState = sharedPreferences.getBoolean("isWifi", true)
return wifiState;
}
This is just a very rough implementation to give an idea of how you can do it, you can improve this many ways. For example you could put the if statement thats inside the onClickListener in the isWifi() function and just call isWifi() inside your runnable/thread...
you can set list preferences to auto update functions based on the network ....
You can create separate class to check the connectivity and from that class you can select the preferences like auto update only on wifi or when connected to network or do not auto update ....

Android adding OnPreferenceChangeListener to MultiSelectListPreference(MSLP) stops MSLP from working

My minimum SDK is Android 4.0. When I try to add an OnPreferenceChangeListener to MultiSelectListPreference, it stops MSLP from storing the changed values. It works fine without the listener, and even with my code commented out it seems to fail.
private void init () {
MultiSelectListPreference multiSelectListPref = (MultiSelectListPreference) findPreference("repeat_days");
if (multiSelectListPref != null) {
/* Works fine if this is commented out
multiSelectListPref.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
#Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
//formatSummary((MultiSelectListPreference) preference, newValue);
return false;
}
});*/
}
}
I need to know when the user changes the information. I have seen this response
MultiSelectListPreference not storing values? but I can not seem to get even that to work.
Any help is appreciated, Thanks in advance!
You can view google's code here (https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/preference/MultiSelectListPreference.java). The pertinent method is:
#Override
protected void onDialogClosed(boolean positiveResult) {
super.onDialogClosed(positiveResult);
if (positiveResult && mPreferenceChanged) {
final Set<String> values = mNewValues;
if (callChangeListener(values)) {
setValues(values);
}
}
mPreferenceChanged = false;
}
So when you return false in your onPreferenceChange() method, you are explicitly telling it not to save your updated values. Return true and this should do what you expect.
The other SO posts suggest there have been various bugs in this preference implementation at various SDK levels. YMMV.

Random boolean generator for android

I am trying to create a random (50/50) chance of a case A or case B happen in android and I need it to be as simple and as resource efficient as possible. I've looked through stackoverflow but all I find for random boolean is in C++?
Would appreciate if someone could give me a suggestion as to how to do this, whether with boolean or with strings/integers (since I was told that booleans is a primitive).
I tried the following
public static boolean getRandomBoolean() {
return Math.random() < 0.5; }
boolean atrg = getRandomBoolean();
if (atrg = true)
{ Toast.makeText(cxt, "TRUE", Toast.LENGTH_SHORT).show(); }
else if (atrg = false)
{ Toast.makeText(cxt, "FALSE", Toast.LENGTH_SHORT).show(); }
But in nearly every case, I tested (>20x), its TRUE?. This is likely a stupid question but is getRandomBoolean a boolean or an int/double? Sorry, I'm very new to android, as you probably guessed.
Your random generator is fine, but your toast displaying the result is not.
The problem is in the if-statement where you use a single equals sign (=) which is an assignment. The result of this assignment will be true and thus it will never show the "FALSE" toast.
Try this instead.
boolean atrg = getRandomBoolean();
if (atrg) {
Toast.makeText(cxt, "TRUE", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(cxt, "FALSE", Toast.LENGTH_SHORT).show();
}
This is not how you check boolean in if. = is the assignment operator and == is used for comparison. You should check like:
if(atrg == true)
or in case of boolean it is simply:
if(atrg)
Your statement:
if(atrg = true)
assigns atrg with true and you never get a false case.
Just use Math.random(), it will return a value between 0 and 1.. Example below:
public static boolean getRandomBoolean() {
return Math.random() < 0.5;
}
public static void main(String[] args) {
System.out.println(getRandomBoolean());
}

Categories

Resources