Validating EditTextPreference on Android - android

I have read the many answers on this question but my question is asking where I place the code. I am looking to validate that a number is greater than 100 in the EditTextPreference. This is the code I use to populate the preferences:
public class SettingsFrag extends PreferenceFragment{
//Override onCreate so that the code will run when the activity is started.
#Override
public void onCreate(Bundle savedInstanceState){
//Call to the super class.
super.onCreate(savedInstanceState);
//add the preferences from the XML file.
addPreferencesFromResource(R.xml.preferences);
}
}
Is it in here I add the validation or would I have to create another class?
preferences.xml:
<EditTextPreference
android:key="geofence_range"
android:title="Geofence Size"
android:defaultValue="500"
android:inputType="number"
android:summary="Geofence Size Around User Location"
android:dialogTitle="Enter Size (meters):" />

Add setOnPreferenceChangeListener for EditTextPreference after addPreferencesFromResource to validate data input for User:
EditTextPreference edit_Pref = (EditTextPreference)
getPreferenceScreen().findPreference("geofence_range");
edit_Pref.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
#Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
// put validation here..
if(<validation pass>){
return true;
}else{
return false;
}
}
});

Note: This answer is based on the deprecated android.preference.EditTextPreference.
Huh. Another one of those things that should be so darned easy in Android but isn't. Other answers just silently prevent writing back the result to preferences, which seems a bit shoddy. (Showing toast is less shoddy, but is still shoddy).
You'll need a custom preference to do this. Customize onValidate to suit your needs.
package com.two_play.extensions;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Bundle;
import android.preference.EditTextPreference;
import android.util.AttributeSet;
import android.view.View;
import android.widget.EditText;
public class ValidatingEditTextPreference extends EditTextPreference {
public ValidatingEditTextPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
public ValidatingEditTextPreference(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public ValidatingEditTextPreference(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ValidatingEditTextPreference(Context context) {
super(context);
}
#Override
protected void showDialog(Bundle state) {
super.showDialog(state);
AlertDialog dlg = (AlertDialog)getDialog();
View positiveButton = dlg.getButton(DialogInterface.BUTTON_POSITIVE);
getEditText().setError(null);
positiveButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
onPositiveButtonClicked(v);
}
});
}
private void onPositiveButtonClicked(View v) {
String errorMessage = onValidate(getEditText().getText().toString());
if (errorMessage == null)
{
getEditText().setError(null);
onClick(getDialog(),DialogInterface.BUTTON_POSITIVE);
getDialog().dismiss();
} else {
getEditText().setError(errorMessage);
return; // return WITHOUT dismissing the dialog.
}
}
/***
* Called to validate contents of the edit text.
*
* Return null to indicate success, or return a validation error message to display on the edit text.
*
* #param text The text to validate.
* #return An error message, or null if the value passes validation.
*/
public String onValidate(String text)
{
try {
Double.parseDouble(text);
return null;
} catch (Exception e)
{
return getContext().getString(R.string.error_invalid_number);
}
}
}

It seems more elegant to me to disable the "OK" button instead of allowing the user to press it but then discard their input and show the error. getDialog seems to be gone in androidx.preference, but this seems to work for me instead:
final EditTextPreference p = new EditTextPreference(context);
p.setOnBindEditTextListener(new EditTextPreference.OnBindEditTextListener() {
#Override
public void onBindEditText(#NonNull final EditText editText) {
editText.addTextChangedListener(new TextWatcher() {
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
#Override
public void afterTextChanged(Editable editable) {
String validationError;
try {
// ... insert your validation logic here, throw on failure ...
validationError = null; // All OK!
} catch (Exception e) {
validationError = e.getMessage();
}
editText.setError(validationError);
editText.getRootView().findViewById(android.R.id.button1)
.setEnabled(validationError == null);
}
});
}
});

Here is my Kotlin implementation for androidx.preference.* based on Vladimir Panteleev's answer:
class CustomPreference : EditTextPreference {
// ...
private fun applyValidation() = setOnBindEditTextListener { editText ->
editText.doAfterTextChanged { editable ->
requireNotNull(editable)
// TODO Add validation magic here.
editText.error = if (criteria.isValid()) {
null // Everything is fine.
} else {
if (criteria.getErrorMessage() == null) "Unknown validation error"
else resources.getString(criteria.getErrorMessage())
}
}
}
}
The handy doAfterTextChanged extension is part of the androidx.core:core-ktx library.

Related

How to distinguish for EditText's between pasting into or setting per setText()?

I have an EditText what I populate via
editText.setText(content)
The reason this is an EditText and not a TextView is because I also want to paste stuff (later when user is operating the app) in it or manually type in it, if applicable.
But I have to
reset a flag if editText set via setText()
and nothing if pasted by user
How can I distinguish how a EditText was populated? addTextChangedListener()'s callbacks are triggered in each case.
You can set Listener Class:
public interface GoEditTextListener {
void onUpdate();
}
Custom Edittext
public class GoEditText extends EditText
{
ArrayList<GoEditTextListener> listeners;
public GoEditText(Context context)
{
super(context);
listeners = new ArrayList<>();
}
public GoEditText(Context context, AttributeSet attrs)
{
super(context, attrs);
listeners = new ArrayList<>();
}
public GoEditText(Context context, AttributeSet attrs, int defStyle)
{
super(context, attrs, defStyle);
listeners = new ArrayList<>();
}
public void addListener(GoEditTextListener listener) {
try {
listeners.add(listener);
} catch (NullPointerException e) {
e.printStackTrace();
}
}
/**
* Here you can catch paste, copy and cut events
*/
#Override
public boolean onTextContextMenuItem(int id) {
boolean consumed = super.onTextContextMenuItem(id);
switch (id){
case android.R.id.cut:
onTextCut();
break;
case android.R.id.paste:
onTextPaste();
break;
case android.R.id.copy:
onTextCopy();
}
return consumed;
}
public void onTextCut(){
}
public void onTextCopy(){
}
/**
* adding listener for Paste for example
*/
public void onTextPaste(){
for (GoEditTextListener listener : listeners) {
listener.onUpdate();
}
}
}
xml
<com.yourname.project.GoEditText
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/editText1"/>
Code
private GoEditText editText1;
editText1 = (GoEditText) findViewById(R.id.editText1);
editText1.addListener(new GoEditTextListener() {
#Override
public void onUpdate() {
//here do what you want when text Pasted
}
});
Simply extend EditText, include the flag, and override setText:
public class MyEditText extends EditText {
boolean fromSetText;
#Override
public void setText(String text) {
super.setText(text);
fromSetText = true;
}
}
You can define your own setters/getters and constructors based on your requirements.

Callback with extended EditText

I'm trying to create a custom EditText that provides an onLostFocus event. However, I can't get my head around how I tell the custom class what method to run when the focus is lost.
This is my extended EditText:
public class smtyEditText extends EditText {
public smtyEditText(Context context) {
super(context);
}
public smtyEditText(Context context, AttributeSet attrs) {
super(context, attrs);
}
public smtyEditText(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public void setFocusChangeListener() {
this.setOnFocusChangeListener(new View.OnFocusChangeListener() {
public void onFocusChange(View v, boolean hasFocus) {
if (!hasFocus) {
// notify the relevant activity, probably passing it some parameters like what instance of smtyEditText triggered the event.
}
}
});
}
}
The intention of the setFocusChangeListener function was that from any given activity I could do something like:
public class AddMeal extends ActionBarActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_add_meal);
EditText etMealName = (EditText) findViewById(R.id.txtmealName);
etMealName.setFocusChangeListener(this.fieldLostFocus)
}
// then
public void fieldLostFocus(eventSource) {
// run some kind of validation on the field text.
}
}
Clearly I'm "code paraphrasing" here. I also get that Interfaces, and some other "EventNotifier" class might be needed. These are the resources I've tried to decipher so far:
http://www.javaworld.com/article/2077462/learn-java/java-tip-10--implement-callback-routines-in-java.html
How to Define Callbacks in Android?
http://www.justinmccandless.com/blog/Setting+Up+a+Callback+Function+in+Android
But for whatever reason I can't crystallize what is needed. Do you have any suggestions on how I can achieve this?
You don't need the inheritance... it only adds an unnecessary layer of indirection. Just add the focus change handler in your activity.
public class AddMeal extends ActionBarActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_add_meal);
EditText etMealName = (EditText) findViewById(R.id.txtmealName);
etMealName.setFocusChangeListener(new View.OnFocusChangeListener() {
public void onFocusChange(View v, boolean hasFocus) {
if (!hasFocus) {
// call method to handle loss of focus
}
}
});
}
// then
public void fieldLostFocus(eventSource) {
// run some kind of validation on the field text.
}
}

Create NumberPicker dialog in preference

I am trying to create a NumberPicker dialog in my preference screen. I have already made one following this:https://stackoverflow.com/a/5533295/2442638
However, for my second dialog, I only want one spinner, so I have adapted the code as follows:
import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.TypedArray;
import android.preference.DialogPreference;
import android.util.AttributeSet;
import android.view.View;
import android.widget.NumberPicker;
public class SnoozeTPP extends DialogPreference {
private int Minute = 0;
private NumberPicker np= null;
public static int getMinute(String time) {
String[] pieces = time.split(":");
return (Integer.parseInt(pieces[1]));
}
public SnoozeTPP(Context context, AttributeSet attrs) {
super(context, attrs);
setPositiveButtonText("Set");
setNegativeButtonText("Cancel");
}
#Override
protected View onCreateDialogView() {
np = new NumberPicker(getContext());
return (np);
}
#Override
protected void onBindDialogView(View v) {
super.onBindDialogView(v);
np.setMaxValue(60);
np.setValue(Minute);
}
#Override
protected void onDialogClosed(boolean positiveResult) {
super.onDialogClosed(positiveResult);
if (positiveResult) {
Minute = np.getValue();
String time = 0 + ":" + String.valueOf(Minute);
if (callChangeListener(time)) {
persistString(time);
}
}
}
#Override
protected Object onGetDefaultValue(TypedArray a, int index) {
return (a.getString(index));
}
#Override
protected void onSetInitialValue(boolean restoreValue, Object defaultValue) {
String time = null;
if (restoreValue) {
if (defaultValue == null) {
time = getPersistedString("08:00");
} else {
time = getPersistedString(defaultValue.toString());
}
} else {
time = defaultValue.toString();
}
Minute = getMinute(time);
}
}
There are no errors and the dialog pops up correctly, but the layout of it seems to be "messed up" :-). The blue line stretch across the whole dialog instead of just the width of the numbers.
The question is - how to set the layout correctly? (I am sure there are lots of other mistakes as well!)
Thank you
I solved this by using the CyanogenMod number picker.
Java file:
https://github.com/CyanogenMod/android_packages_apps_Trebuchet/blob/cm-10.2/src/com/cyanogenmod/trebuchet/preference/NumberPickerPreference.java
XML file:
https://github.com/CyanogenMod/android_packages_apps_Trebuchet/blob/cm-10.2/res/layout/number_picker_dialog.xml
Attributes:
https://github.com/CyanogenMod/android_packages_apps_Trebuchet/blob/cm-10.2/res/values/attrs.xml#L158
Here is an example of simple, but working NumberPickerPreference, saving integer value between 1 and 100:
NumberPickerPreference.java:
public class NumberPickerPreference extends DialogPreference {
private NumberPicker mPicker;
private Integer mNumber = 0;
public NumberPickerPreference(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public NumberPickerPreference(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
setPositiveButtonText(android.R.string.ok);
setNegativeButtonText(android.R.string.cancel);
}
#Override
protected View onCreateDialogView() {
mPicker = new NumberPicker(getContext());
mPicker.setMinValue(1);
mPicker.setMaxValue(100);
mPicker.setValue(mNumber);
return mPicker;
}
#Override
protected void onDialogClosed(boolean positiveResult) {
if (positiveResult) {
// needed when user edits the text field and clicks OK
mPicker.clearFocus();
setValue(mPicker.getValue());
}
}
#Override
protected void onSetInitialValue(boolean restoreValue, Object defaultValue) {
setValue(restoreValue ? getPersistedInt(mNumber) : (Integer) defaultValue);
}
public void setValue(int value) {
if (shouldPersist()) {
persistInt(value);
}
if (value != mNumber) {
mNumber = value;
notifyChanged();
}
}
#Override
protected Object onGetDefaultValue(TypedArray a, int index) {
return a.getInt(index, 0);
}
}
This is more a workaround than a solution, but i hope it helps. Adding a dummy textView solved the problem. I got exactly the same problem.
My xml File:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical" >
<TextView
android:id="#+id/textDummyEmpty"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/textDummyEmpty" />
<NumberPicker
android:id="#+id/numberPicker1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal" />
</LinearLayout>
and
android:text="#string/textDummyEmpty"
is an empty String. Maybe its also enough to use just a view instead of a textView.
return a LinearLayout in onCreateDialogView rather than NumberPicker as below:
#Override
protected View onCreateDialogView() {
numberPicker = new NumberPicker(getContext());
numberPicker.setMinValue(1);
numberPicker.setMaxValue(12);
numberPicker.setWrapSelectorWheel(false);
numberPicker.setValue(lastValue);
LinearLayout.LayoutParams pickerParams = new LinearLayout.LayoutParams
(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
pickerParams.gravity = Gravity.CENTER;
numberPicker.setLayoutParams(pickerParams);
LinearLayout layout = new LinearLayout(getContext());
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams
(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
layout.setOrientation(LinearLayout.VERTICAL);
layout.setLayoutParams(params);
layout.addView(numberPicker);
return layout;
//return numberPicker;
}

Custom preferences in Android that use the correct layout

For custom preferences, e.g. a time picker preference, I use the following XML in Android:
<com.my.package.TimePreference android:key="notification_time" android:selectable="true" android:title="#string/notification_time" android:enabled="true" android:summary="#string/setTime" android:defaultValue="00:00" />
Where the class "TimePreference" is:
package com.my.package;
import android.content.Context;
import android.content.res.TypedArray;
import android.preference.DialogPreference;
import android.text.format.DateFormat;
import android.util.AttributeSet;
import android.view.View;
import android.widget.TimePicker;
public class TimePreference extends DialogPreference {
private int lastHour = 0;
private int lastMinute = 0;
private TimePicker picker = null;
private boolean is_24_hours = true;
public static int getHour(String time) {
String[] pieces = time.split(":");
return(Integer.parseInt(pieces[0]));
}
public static int getMinute(String time) {
String[] pieces = time.split(":");
return(Integer.parseInt(pieces[1]));
}
public TimePreference(Context ctxt) {
this(ctxt, null);
}
public TimePreference(Context ctxt, AttributeSet attrs) {
this(ctxt, attrs, 0);
}
public TimePreference(Context ctxt, AttributeSet attrs, int defStyle) {
super(ctxt, attrs, defStyle);
setPositiveButtonText(ctxt.getString(R.string.save));
setNegativeButtonText(ctxt.getString(R.string.cancel));
try {
is_24_hours = DateFormat.is24HourFormat(ctxt);
}
catch (Exception e) {
is_24_hours = true;
}
}
#Override
protected View onCreateDialogView() {
picker = new TimePicker(getContext());
if (is_24_hours) {
picker.setIs24HourView(true);
}
else {
picker.setIs24HourView(false);
}
return(picker);
}
#Override
protected void onBindDialogView(View v) {
super.onBindDialogView(v);
picker.setCurrentHour(lastHour);
picker.setCurrentMinute(lastMinute);
}
#Override
protected void onDialogClosed(boolean positiveResult) {
super.onDialogClosed(positiveResult);
if (positiveResult) {
picker.clearFocus(); // important - otherwise manual input is not saved
lastHour = picker.getCurrentHour();
lastMinute = picker.getCurrentMinute();
String time = String.valueOf(lastHour)+":"+String.valueOf(lastMinute);
if (callChangeListener(time)) {
persistString(time);
}
}
}
#Override
protected Object onGetDefaultValue(TypedArray a, int index) {
return(a.getString(index));
}
#Override
protected void onSetInitialValue(boolean restoreValue, Object defaultValue) {
String time = null;
if (restoreValue) {
if (defaultValue == null) {
time = getPersistedString("00:00");
}
else {
time = getPersistedString(defaultValue.toString());
}
}
else {
time = defaultValue.toString();
}
lastHour = getHour(time);
lastMinute = getMinute(time);
}
}
This has always worked great on Android 2.2 and 2.3 - but now that I've switched to Android 4.0 I can see that this preference does not adjust to the preference screen's layout:
How can I solve this problem? Is there a solution better than setting margin/padding manually?
Finally found the problem:
The constructor super() must be called that takes only two arguments (and no int defStyle argument).
In the code from the question, super() is called with a predefined defStyle value of 0 whcih prevents Android from choosing any nice layout. If you call super() without giving a default style argument, super class DialogPreference's constructor chooses the best style on its own.

Disable the Android keyboard on an array of EditText

I have an array of EditText and I want to disable the standard keyboard Android that appears every time I click on them.
these are the parts code I am using:
InputMethodManager imm = (InputMethodManager)getSystemService(
Context.INPUT_METHOD_SERVICE);
for (i=0;i<dim*dim;i++){
imm.hideSoftInputFromWindow(value[i].getWindowToken(), 0);
value[i].setOnTouchListener(this);
value[i].setOnClickListener(this);
value[i].setOnFocusChangeListener(this);
}
EDIT:
I created a new class, with these lines of code:
import android.content.Context;
import android.util.AttributeSet;
import android.widget.EditText;
public class KeyboardControlEditText extends EditText {
private boolean mShowKeyboard = false;
public void setShowKeyboard(boolean value) {
mShowKeyboard = value;
}
// This constructor has to be overriden
public KeyboardControlEditText(Context context, AttributeSet attrs) {
super(context, attrs);
}
// Now tell the VM whether or not we are a text editor
#Override
public boolean onCheckIsTextEditor() {
return mShowKeyboard;
}
}
and in my main class in OnCreate:
for (i=0;i<dim*dim;i++){
((KeyboardControlEditText) value[i]).setShowKeyboard(false);
value[i].setOnTouchListener(this);
value[i].setOnClickListener(this);
}
You need to create your own EditText class for this. Then, override the default onCheckIsTextEditor and return false.
public class NoKeyboardEditText extends EditText {
// This constructor has to be overriden
public NoKeyboardEditText(Context context, AttributeSet attrs) {
super(context, attrs);
}
// Now tell the VM that we are not a text editor
#Override
public boolean onCheckIsTextEditor() {
return false;
}
}
Make sure you substitute in the correct name for the new EditText. For example, if your package is com.example.widget, you'd want to use <com.example.widget.NoKeyboardEditText ... />.
If you need this to be dynamic, you can get even fancier:
public class KeyboardControlEditText extends EditText {
private boolean mShowKeyboard = false;
public void setShowKeyboard(boolean value) {
mShowKeyboard = value;
}
// This constructor has to be overriden
public KeyboardControlEditText(Context context, AttributeSet attrs) {
super(context, attrs);
}
// Now tell the VM whether or not we are a text editor
#Override
public boolean onCheckIsTextEditor() {
return mShowKeyboard;
}
}
That way, you can call ((KeyboardControlEditText) myEditText).setShowKeyboard(false); to change it at runtime.

Categories

Resources