RobbB
RobbB

Reputation: 1354

How to have any change in UI reiterate/reload code for any of many listeners?

This is tricky for me so I don't know if I am describing this as well as could be done. But I'll do my best.

I have a unit converter (metric-imperial) as part of my app. I have two numberPickers for picking units. I also have a toggle button which toggles between decimal/fraction output. I want the calculation to update upon any change in the UI such as user scrolling either of the wheels as well as toggling the switch.

I currently have the calculations updating with movement of the two wheels but its very inefficient and repetitive, its also mechanical in its function any seasoned professional would laugh at my code. So I want to improve it but more importantly, since the actual question does solve this, how can I have one block update for any of the three listeners? Right now I have the code in both listener blocks which is weird. And also the toggle switch doesn't do anything till the wheels are moved, not very intuitive.

    var selector = false
    val decimalFraction: ToggleButton = findViewById(R.id.toggleButton)
    decimalFraction.setOnCheckedChangeListener { _, isDecimal -> selector = !isDecimal }

    val fromData = arrayOf("Inches", "Feet", "Millimeters", "Centimeters", "Meters")
    fromUnit.minValue = 0
    fromUnit.maxValue = fromData.size - 1
    fromUnit.displayedValues = fromData
    fromUnit.setOnValueChangedListener { _, _, _ ->
        // INCH TO...
        if (fromUnit.value == 0 && toUnit.value == 0) {
            if (TextUtils.isEmpty(valueInputConversion.text)) { // Do nothing
            } else {
                val inputInch = valueInputConversion.text.toString().toDouble()
                if (selector) {
                    valueOutputConversion.text = "$inputInch" // Inch to inch
                } else {
                    valueOutputConversion.text = 
             DecimalFraction.vulgarFraction(inputInch).first
                }
            }
        } else if (fromUnit.value == 0 && toUnit.value == 1) {
            if (TextUtils.isEmpty(valueInputConversion.text)) { // Do nothing
            } else {
                val inputInch = valueInputConversion.text.toString().toDouble()
                valueOutputConversion.text = "${inputInch * 0.0833333}" // Inch to feet
            }
        } else if (fromUnit.value == 0 && toUnit.value == 2) {
            if (TextUtils.isEmpty(valueInputConversion.text)) { // Do nothing
            } else {
                val inputInch = valueInputConversion.text.toString().toDouble()
                valueOutputConversion.text = "${inputInch * 25.4}" // Inch to millimeters
            }
        } else if (fromUnit.value == 0 && toUnit.value == 3) {
            if (TextUtils.isEmpty(valueInputConversion.text)) { // Do nothing
            } else {
                val inputInch = valueInputConversion.text.toString().toDouble()
                valueOutputConversion.text = "${inputInch * 2.54}" // Inch to centimeters
            }
        } else if (fromUnit.value == 0 && toUnit.value == 4) {
            if (TextUtils.isEmpty(valueInputConversion.text)) { // Do nothing
            } else {
                val inputInch = valueInputConversion.text.toString().toDouble()
                valueOutputConversion.text = "${inputInch * 0.0254}" // Inch to meters
            }
    }


    val toData = arrayOf("Inches", "Feet", "Millimeters", "Centimeters", "Meters")
    toUnit.minValue = 0
    toUnit.maxValue = toData.size - 1
    toUnit.displayedValues = toData
    toUnit.setOnValueChangedListener { _, _, _ ->
        // INCH TO...
        if (toUnit.value == 0 && fromUnit.value == 0) {
            if (TextUtils.isEmpty(valueInputConversion.text)) { // Do nothing
            } else {
                val inputInch = valueInputConversion.text.toString().toDouble()
                if (selector) {
                    valueOutputConversion.text = "$inputInch" // Inch to inch
                } else {
                    valueOutputConversion.text = DecimalFraction.vulgarFraction(inputInch).first
                }
            }
        } else if (toUnit.value == 0 && fromUnit.value == 1) {
            if (TextUtils.isEmpty(valueInputConversion.text)) { // Do nothing
            } else {
                val inputInch = valueInputConversion.text.toString().toDouble()
                valueOutputConversion.text = "${inputInch * 0.0833333}" // Inch to feet
            }
        } else if (toUnit.value == 0 && fromUnit.value == 2) {
            if (TextUtils.isEmpty(valueInputConversion.text)) { // Do nothing
            } else {
                val inputInch = valueInputConversion.text.toString().toDouble()
                valueOutputConversion.text = "${inputInch * 25.4}" // Inch to millimeters
            }
        } else if (toUnit.value == 0 && fromUnit.value == 3) {
            if (TextUtils.isEmpty(valueInputConversion.text)) { // Do nothing
            } else {
                val inputInch = valueInputConversion.text.toString().toDouble()
                valueOutputConversion.text = "${inputInch * 2.54}" // Inch to centimeters
            }
        } else if (toUnit.value == 0 && fromUnit.value == 4) {
            if (TextUtils.isEmpty(valueInputConversion.text)) { // Do nothing
            } else {
                val inputInch = valueInputConversion.text.toString().toDouble()
                valueOutputConversion.text = "${inputInch * 0.0254}" // Inch to meters
            }
    }

EDIT: Forgot to mention that i cut out a lot of code to be less verbose, rest is same for other units to convert but not important here. Also I personally prefer if else over when in Kotlin for readability, don't know if that makes a difference here as it does work.

EDIT #2: Not necessarily looking for someone to solve my code as the question may suggest (unless you want to) but would like to be pointed in the right direction. Kotlin or java works.

Upvotes: 0

Views: 33

Answers (1)

Tenfour04
Tenfour04

Reputation: 93639

Here's what my strategy would be.

Every widget's listener simply calls the same update function. The update function does the conversion using the current values in all the widgets and updates the output text view.

Also, to avoid the two very long if/else chains that cover all permutations of in- and out- unit types, I would just convert everything to one specific unit type, and then convert it to the output unit type.

I'd also create a list of units to their conversion factor to this common intermediate unit type to simplify the conversions.

For example:

fromUnit.setOnValueChangedListener { _, _, _ -> updateOutputText() }
toUnit.setOnValueChangedListener { _, _, _ -> updateOutputText() }
valueInputConversion.addTextChangedListener(object : TextWatcher {
    override fun afterTextChanged(s: Editable?) {
        updateOutputText()
    }
    override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) { }
    override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { }
})

// ...

private val toMeters = listOf(0.0254, 0.3048, 0.001, 0.01, 1.0)

// ...

private fun updateOutputText() {
    if (TextUtils.isEmpty(valueInputConversion.text)) {
        return
    }
    val inputValue = valueInputConversion.text.toDoubleOrNull()
    if (inputValue == null) {
        // maybe show error?
        return
    }
    val meters = inputValue * toMeters[fromUnit.value]
    val outputUnits = meters / toMeters[toUnit.value]
    outputTextView.text = outputUnits.toString() // Might want to use String.format or DecimalFormat here
}

Upvotes: 1

Related Questions