Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android - How to only allow a certain number of decimal places

Do you know of any method to make sure users can only enter figures with a maximum number of decimals. I'm not sure how to address this problem. In the MS SQL database I'm going to send data from my app I've got columns with this type decimal(8,3) Now considering the data type of the column that's finally going to store the value I want to validate in Android, I've considered these two cases:

  1. If the user enters a number with no decimals, the maximum number of digits must be 8
  2. If the user enters a number with decimals, the maximum number of digits must be 8 (including the digits to the right of the decimal point)

Now I'm sure about the first case, but not so much about the second. Is it right to keep the number of maximum digits fixed(for example, always 8)? Or should I consider allowing a maximum of 8 digits to the left and 3 to the right of the decimal point?

Either way this is what I've been trying in Android:

mQuantityEditText.addTextChangedListener(new TextWatcher() {
            @Override
            public void afterTextChanged(Editable s) {
                String str = mQuantityEditText.getText().toString();
                DecimalFormat format = (DecimalFormat) DecimalFormat
                        .getInstance();
                DecimalFormatSymbols symbols = format.getDecimalFormatSymbols();
                char sep = symbols.getDecimalSeparator();

                int indexOFdec = str.indexOf(sep);

                if (indexOFdec >= 0) {
                    if (str.substring(indexOFdec, str.length() - 1).length() > 3) {                     
                        s.replace(0, s.length(),
                                str.substring(0, str.length() - 1));                        
                    }
                }
            }

            @Override
            public void beforeTextChanged(CharSequence s, int start, int count,
                    int after) {

            }

            @Override
            public void onTextChanged(CharSequence s, int start, int before,
                    int count) {

            }
        });

Even though, the above code handles the maximum number of decimal places. It does not limit the total number of digits allowed in the EditText.

Do you think you could help me improve my code so that it handles both the maximum number of decimal places and the total number of digits allowed in a EditText (considering both numbers to the left and to the right of the decimal point)

EDIT

Well, now I'm trying what João Sousa suggested and here's what I've tried:

1) I defined a class that implements InputFilter

public class NumberInputFilter implements InputFilter {
    private Pattern mPattern;   

    public NumberInputFilter(int precision, int scale) {        
        String pattern="^\\-?(\\d{0," + (precision-scale) + "}|\\d{0," + (precision-scale) + "}\\.\\d{0," + scale + "})$";
        this.mPattern=Pattern.compile(pattern);

    }

    @Override
    public CharSequence filter(CharSequence source, int start, int end, Spanned destination, int destinationStart, int destinationEnd) {
         if (end > start) {
             // adding: filter   
             // build the resulting text
             String destinationString = destination.toString();
             String resultingTxt = destinationString.substring(0, destinationStart) + source.subSequence(start, end) + destinationString.substring(destinationEnd);
             // return null to accept the input or empty to reject it
             return resultingTxt.matches(this.mPattern.toString()) ? null : "";
         }
         // removing: always accept
         return null;
    }

}

2) Tried to use the class like this :

mQuantityEditText.setFilters(new InputFilter[] { new NumberInputFilter(8,3)} );
like image 629
eddy Avatar asked Nov 22 '14 12:11

eddy


2 Answers

I would go for a filter in the edit text itself with the power of regex. First the regex expression:

^\-?(\d{0,5}|\d{0,5}\.\d{0,3})$

Maybe there are multiple ways to improve this expression, but this does trick.

And now just set an input filter in the edittext, like this:

final String regex = "^\-?(\d{0,5}|\d{0,5}\.\d{0,3})$";
((EditText)rootView.findViewById(R.id.editText1)).setFilters(new InputFilter[] {
    new InputFilter() {
        @Override
        public CharSequence filter(CharSequence source, int start, int end, Spanned destination, int destinationStart, int destinationEnd) {
            if (end > start) {
                // adding: filter   
                // build the resulting text
                String destinationString = destination.toString();
                String resultingTxt = destinationString.substring(0, destinationStart) + source.subSequence(start, end) + destinationString.substring(destinationEnd);
                // return null to accept the input or empty to reject it
                return resultingTxt.matches(regex) ? null : "";
            }
            // removing: always accept
            return null;
        }
    }
});

Btw, I just tested this code and what it does is:

  1. The user can enter a maximum of 8 digits;
  2. As soon as the user enters a '.', the maximum decimal digits allowed are 8.

Did I correctly understand the problem you described?

-- EDIT

Ok, I was almost there. From what I understand, decimal(8,3) means at most 8 digits including digits to the left or right of the decimal point, ranging from -99999.999 to 99999.999. At least that's what I understand from this sentence Standard SQL requires that DECIMAL(5,2) be able to store any value with five digits and two decimals, so values that can be stored in the salary column range from -999.99 to 999.99. Even though it's from the MySQL documentation the MSSQL docs seem to do the same.

like image 129
Joao Sousa Avatar answered Sep 28 '22 06:09

Joao Sousa


I have answser for you, me also suffered lot in this kind of situation.:D :P

I have implemented this for maximum of 4 digits to the left and 2 to the right of the decimal point ex: 4444.99

so small changes need to implement what i did: Need to do following changes

1) copy CustomTextWatcher.java to track input of editText.

import java.text.NumberFormat;

import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log;
import android.widget.EditText;

public class CustomTextWatcher implements TextWatcher {


    private NumberFormat nf = NumberFormat.getNumberInstance();
    private EditText et;
    private String tmp = "";
    private int moveCaretTo;
    private static final int INTEGER_CONSTRAINT = 4;
    private static final int FRACTION_CONSTRAINT = 2;
    private static final int MAX_LENGTH = INTEGER_CONSTRAINT
            + FRACTION_CONSTRAINT + 1;

    public CustomTextWatcher(EditText et) {
        this.et = et;
        nf.setMaximumIntegerDigits(INTEGER_CONSTRAINT);
        nf.setMaximumFractionDigits(FRACTION_CONSTRAINT);
        nf.setGroupingUsed(false);
    }

    public int countOccurrences(String str, char c) {
        int count = 0;
        for (int i = 0; i < str.length(); i++) {
            if (str.charAt(i) == c) {
                count++;
            }
        }
        return count;
    }

    @Override
    public void afterTextChanged(Editable s) {
        et.removeTextChangedListener(this); // remove to prevent stackoverflow
        String ss = s.toString();
        int len = ss.length();
        int dots = countOccurrences(ss, '.');
        boolean shouldParse = dots <= 1
                && (dots == 0 ? len != (INTEGER_CONSTRAINT + 1)
                        : len < (MAX_LENGTH + 1));
        if (shouldParse) {
            if (len > 1 && ss.lastIndexOf(".") != len - 1) {
                try {

                    if (ss.contains(".")) {

                        String[] integerFractionStrings = ss.split("\\.");

                        Log.v("Check SS ", ss);

                        Log.v("second string", "Found"
                                + integerFractionStrings.length);

                        if (integerFractionStrings.length > 1) {

                            Log.v("integerFractionStrings",
                                    integerFractionStrings[1]);

                            if (integerFractionStrings[1].length() == 1
                                    && integerFractionStrings[1].charAt(0) == '0') {

                                et.setText(ss);

                                Log.v("second string", "size 1");
                            } else {

                                Log.v("second string", "> 1");

                                Double d = Double.parseDouble(ss);
                                if (d != null) {
                                    et.setText(nf.format(d));
                                }

                            }
                        }
                    } else {

                        Log.v("First string", "No dot");

                        Double d = Double.parseDouble(ss);
                        if (d != null) {
                            et.setText(nf.format(d));
                        }
                    }

                } catch (NumberFormatException e) {
                }
            }
        } else {

            Log.v("second string", "size 1");
            et.setText(tmp);
        }
        et.addTextChangedListener(this); // reset listener

        // tried to fix caret positioning after key type:
        if (et.getText().toString().length() > 0) {
            if (dots == 0 && len >= INTEGER_CONSTRAINT
                    && moveCaretTo > INTEGER_CONSTRAINT) {
                moveCaretTo = INTEGER_CONSTRAINT;
            } else if (dots > 0 && len >= (MAX_LENGTH)
                    && moveCaretTo > (MAX_LENGTH)) {
                moveCaretTo = MAX_LENGTH;
            }
            try {
                et.setSelection(et.getText().toString().length());
                // et.setSelection(moveCaretTo); <- almost had it :))
            } catch (Exception e) {
            }
        }
    }

    @Override
    public void beforeTextChanged(CharSequence s, int start, int count,
            int after) {
        moveCaretTo = et.getSelectionEnd();
        tmp = s.toString();
    }

    @Override
    public void onTextChanged(CharSequence s, int start, int before, int count) {
        int length = et.getText().toString().length();
        if (length > 0) {
            moveCaretTo = start + count - before;
        }
    }
}

2) set this class to check your editText by following.

EditText review_food_Price;

review_food_Price = (EditText) findViewById(R.id.food_Price);

review_food_Price.setRawInputType(InputType.TYPE_CLASS_NUMBER
                | InputType.TYPE_NUMBER_FLAG_DECIMAL);

review_food_Price.addTextChangedListener(new CustomTextWatcher(
                review_food_Price));

Hope you can convert my code according to your need.

like image 23
Nil Avatar answered Sep 28 '22 08:09

Nil