Home > front end >  Regular expression for 3 decimal with min and max value
Regular expression for 3 decimal with min and max value

Time:12-15

Please i need help to optimize the input filter on my customEditText

Requirement:

min float = 0.001
max float = 1000.000
decimal number = 3

I want to avoid 0 from the user set but he can enter something like 0.01 , 0.001 , 1.555 and 1000.000

This is what i've tested so far:

I used this function to set the min and max

    class EditTextInputFilter(min: Float, max: Float) : InputFilter {
        private val min: Float = min.coerceAtMost(max)
        private val max: Float = min.coerceAtLeast(max)

        override fun filter(source: CharSequence, i: Int, i2: Int, spanned: Spanned, i3: Int, i4: Int): CharSequence? {
            try {
                val input = (spanned.toString()   source.toString()).toFloatOrZero()
                if (isInRange(min, max, input)) {
                    return null
                }
            } catch (nfe: NumberFormatException) {
                Logger.error(nfe.localizedMessage!!)
            }
            return ""
        }

        private fun isInRange(min: Float, max: Float, value: Float): Boolean {
            return value in min..max
        }
    }

I used this Decimal filter to remove comment and un-useful dots

    class DecimalFilter(private val decimalDigits: Int) : InputFilter {
            override fun filter(source: CharSequence, i: Int, i2: Int, spanned: Spanned, i3: Int, i4: Int): CharSequence? {
                var dotPos = -1
                val len = spanned.length
                for (decimalsI in 0 until len) {
                    val c = spanned[decimalsI]
                    if (c == '.' || c == ',') {
                        dotPos = decimalsI
                        break
                    }
                }
                if (dotPos >= 0) {
                    // protects against many dots
                    if (source == "." || source == ",") return ""
                    // if the text is entered before the dot
                    if (i4 <= dotPos) return null
                    if (len - dotPos > decimalDigits) return ""
                }
                return null
            }
        }

    
      class CustomEditText : TextInputEditText {

        private var decimals = 3
        private var min = 0.001f
        private var max = 1000f

        private lateinit var oldFilters: MutableList<InputFilter>

        constructor(context: Context?) : super(context!!) {
            init(null)
        }

        constructor(context: Context?, attrs: AttributeSet?) : super(context!!, attrs) {
            init(attrs)
        }

        constructor(context: Context?, attrs: AttributeSet?, defStyle: Int) : super(context!!, attrs, defStyle) {
            init(attrs)
        }

        private fun init(attrs: AttributeSet?) {
            isInEditMode
            if (attrs != null) {
                val a = context.theme.obtainStyledAttributes(attrs, R.styleable.CustomEditText, 0, 0)
                decimals = a.getInt(R.styleable.CustomEditText_decimals, decimals)
                min = a.getFloat(R.styleable.CustomEditText_min, min)
                max = a.getFloat(R.styleable.CustomEditText_max, max)
            }
            oldFilters = filters.toMutableList()
            addFilters()
        }

        private fun addFilters() {
            val inputFilters = ArrayList<InputFilter>(oldFilters.size   1)
            inputFilters.addAll(oldFilters)
            inputFilters.add(EditTextInputFilter(min, max))
            inputFilters.add(DecimalFilter(decimals))
            filters = inputFilters.toTypedArray()
        }

        fun setMin(min: Float) {
            this.min = min
            addFilters()
        }

        fun setMax(max: Float) {
            this.max = max
            addFilters()
        }

        fun setDecimals(decimals: Int) {
            this.decimals = decimals
            addFilters()
        }
    }

My problem now is, i can not enter 0

CodePudding user response:

The problem is that you're validating the field as the user types and since the range of allowed values is 0.001..1000.000 you cannot type the number 0 (even if the intention is to enter 0.1). This is not a coding but a specification problem.

The way it's specified is:

  • don't accept values outside 0.001..1000.000
  • validate as the user types

It's impossible to meet these requirements. I see a couple of solutions:

  • validate once the user submits (not as the user types)

  • validate without blocking the user input (by using setError in a TextWatcher) List item

  • allow 0 values in your filter (range 0..1000.000) but add another validation when the user submits to eliminate the 0

  • allow 0 values in your filter (range 0..1000.000) and disable the submit button (or whatever you have to process the input) if the value isn't in range

From a ux perspective I'd vote for the last option and maybe also indicate to the user what values are allowed so they know why they can't continue

CodePudding user response:

@EmanuelMoecklin After reading your proposal answer i reviewed my code with this code below and it works perfectly and also i can re-use it for other input field.

I use my EditTextInputFilter class to set the min and max. The DecimalFilter class to set the number of decimal

class EditTextInputFilter(min: Float, max: Float) : InputFilter {
    private val min: Float = min.coerceAtMost(max)
    private val max: Float = min.coerceAtLeast(max)

    override fun filter(source: CharSequence, i: Int, i2: Int, spanned: Spanned, i3: Int, i4: Int): CharSequence? {
        try {
            val input = (spanned.toString()   source.toString()).toFloatOrZero()
            if (isInRange(min, max, input)) {
                return null
            }
        } catch (nfe: NumberFormatException) {
            Logger.error(nfe.localizedMessage!!)
        }
        return ""
    }

    private fun isInRange(min: Float, max: Float, value: Float): Boolean {
        return value in min..max
    }
 }

class DecimalFilter(private val decimalDigits: Int) : InputFilter {
     override fun filter(source: CharSequence, i: Int, i2: Int, spanned: Spanned, i3: Int, i4: Int): CharSequence? {
    var dotPos = -1
    val len = spanned.length
    for (decimalsI in 0 until len) {
        val c = spanned[decimalsI]
        if (c == '.' || c == ',') {
            dotPos = decimalsI
            break
        }
    }
    if (dotPos >= 0) {
        // protects against many dots
        if (source == "." || source == ",") return ""
        // if the text is entered before the dot
        if (i4 <= dotPos) return null
        if (len - dotPos > decimalDigits) return ""
    }
    return null
  }
}

I've created another validate function :

private fun validateInput(input: String): Boolean {
  val double = input.toDoubleOrNull()
  return double != null && double in 0.001f...1000f
}

Than i set the input filter like this on my input field:

binding.valueTiet.filters = arrayOf(EditTextInputFilter(0f, 1000f), DecimalFilter(3))
binding.valueTiet.doAfterTextChanged {
    if (validateInput(it)) {
        binding.valueTietTil.error = ""
        Logger.info("Value accepted")
    } else {
        binding.valueTietTil.error = "Allowed range [0.001-1000]"
    }
}
  • Related