Is this a clusterf*ck? It feels like a clusterf*ck

I dabble a bit in scripting so I wanted to give Android a go and went with an online beginner course for it.
The first “real” app after all the mandatory beginner stuff was a calculator and a very limited one too.
The rules are it’s going to do basic calculations on two numbers and nothing more, it will be a bit hard to maintain and there will be a couple of bugs too.

I figured fine, but the way we implemented the thing just feels like a clusterfuck. Is all of this logic necessary for basically doing one arithmetic operation on two variables? I feel like there is a more elegant solution to this.

Here is the UI (not pasting in or hard coding strings, colors and such):

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/tvInput"
        android:layout_width="match_parent"
        android:layout_height="250dp"
        android:background="#efefef"
        android:gravity="end|bottom"
        android:padding="10dp"
        android:text="@string/calc_text"
        android:textSize="48sp" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:layout_weight="1">

        <Button
            android:id="@+id/btnSeven"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_margin="2dp"
            android:layout_weight="1"
            android:onClick="onDigit"
            android:text="@string/_7" />

        <Button
            android:id="@+id/btnEight"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_margin="2dp"
            android:layout_weight="1"
            android:onClick="onDigit"
            android:text="@string/_8" />

        <Button
            android:id="@+id/btnNine"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_margin="2dp"
            android:layout_weight="1"
            android:onClick="onDigit"
            android:text="@string/_9" />

        <Button
            android:id="@+id/btnDivide"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_margin="2dp"
            android:layout_weight="1"
            android:onClick="onOperator"
            android:text="@string/Divide" />

    </LinearLayout>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:layout_weight="1">

        <Button
            android:id="@+id/btnFour"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_margin="2dp"
            android:layout_weight="1"
            android:onClick="onDigit"
            android:text="@string/_4" />

        <Button
            android:id="@+id/btnFive"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_margin="2dp"
            android:layout_weight="1"
            android:onClick="onDigit"
            android:text="@string/_5" />

        <Button
            android:id="@+id/btnSix"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_margin="2dp"
            android:layout_weight="1"
            android:onClick="onDigit"
            android:text="@string/_6" />

        <Button
            android:id="@+id/btnMultiply"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_margin="2dp"
            android:layout_weight="1"
            android:onClick="onOperator"
            android:text="@string/Multiply" />

    </LinearLayout>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:layout_weight="1">

        <Button
            android:id="@+id/btnOne"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_margin="2dp"
            android:layout_weight="1"
            android:onClick="onDigit"
            android:text="@string/_1" />

        <Button
            android:id="@+id/btnTwo"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_margin="2dp"
            android:layout_weight="1"
            android:onClick="onDigit"
            android:text="@string/_2" />

        <Button
            android:id="@+id/btnThree"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_margin="2dp"
            android:layout_weight="1"
            android:onClick="onDigit"
            android:text="@string/_3" />

        <Button
            android:id="@+id/btnSubtract"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_margin="2dp"
            android:layout_weight="1"
            android:onClick="onOperator"
            android:text="@string/Subtract" />

    </LinearLayout>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:layout_weight="1">

        <Button
            android:id="@+id/btnDot"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_margin="2dp"
            android:layout_weight="1"
            android:onClick="onDecimalPoint"
            android:text="." />

        <Button
            android:id="@+id/btnZero"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_margin="2dp"
            android:layout_weight="1"
            android:onClick="onDigit"
            android:text="0" />

        <Button
            android:id="@+id/btnClr"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_margin="2dp"
            android:layout_weight="1"
            android:onClick="onClear"
            android:text="@string/clr" />

        <Button
            android:id="@+id/btnAdd"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_margin="2dp"
            android:layout_weight="1"
            android:onClick="onOperator"
            android:text="@string/Add" />

    </LinearLayout>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:layout_weight="1">
        <Button
            android:id="@+id/Equal"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_margin="2dp"
            android:layout_weight="1"
            android:onClick="onEqual"
            android:text="@string/Equal" />

    </LinearLayout>

</LinearLayout>

and then the implementation:

package com.example.calculator

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.View
import android.widget.Button
import android.widget.TextView
import java.lang.ArithmeticException

class MainActivity : AppCompatActivity() {
    private var tvInput: TextView? = null
    private var lastNumeric: Boolean = false
    private var lastDot: Boolean = false

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        tvInput = findViewById(R.id.tvInput)
    }

    fun onDigit(view: View) {
        tvInput?.append((view as Button).text)
        lastNumeric = true
        lastDot = false
    }

    fun onClear(view: View) {
        tvInput?.text = ""
    }

    fun onDecimalPoint(view: View) {
        if (lastNumeric && !lastDot) {
            tvInput?.append(".")
            lastNumeric = false
            lastDot = true
        }
    }

    fun onOperator(view: View) {
        tvInput?.text?.let {
            if (lastNumeric && !isOperatorAdded(it.toString())) {
                tvInput?.append((view as Button).text)
                lastNumeric = false
                lastDot = false
            }
        }
    }

    fun onEqual(view: View) {
        if (lastNumeric) {
            var tvValue = tvInput?.text.toString()
            var prefix = ""
            try {
                if (tvValue.startsWith("-")) {
                    prefix = "-"
                    tvValue = tvValue.substring(1)
                }

                if (tvValue.contains("-")) {
                    var splitValue = tvValue.split("-")
                    var one = splitValue[0]
                    var two = splitValue[1]

                    if (prefix.isNotEmpty())
                        one = prefix + one

                    tvInput?.text = removeZeroAfterDot((one.toDouble() - two.toDouble()).toString())
                } else if (tvValue.contains("+")) {
                    var splitValue = tvValue.split("+")
                    var one = splitValue[0]
                    var two = splitValue[1]

                    if (prefix.isNotEmpty())
                        one = prefix + one

                    tvInput?.text = removeZeroAfterDot((one.toDouble() + two.toDouble()).toString())
                } else if (tvValue.contains("*")) {
                    var splitValue = tvValue.split("*")
                    var one = splitValue[0]
                    var two = splitValue[1]

                    if (prefix.isNotEmpty())
                        one = prefix + one

                    tvInput?.text = removeZeroAfterDot((one.toDouble() * two.toDouble()).toString())
                } else if (tvValue.contains("/")) {
                    var splitValue = tvValue.split("/")
                    var one = splitValue[0]
                    var two = splitValue[1]
                    if (prefix.isNotEmpty())
                        one = prefix + one
                    tvInput?.text = removeZeroAfterDot((one.toDouble() / two.toDouble()).toString())
                }
            } catch (e: ArithmeticException) {
                e.printStackTrace()
            }
        }
    }

    private fun removeZeroAfterDot(result: String): String {
        var value = result
        if (result.contains(".0"))
            value = result.substring(0, result.length - 2)
        return value
    }

    private fun isOperatorAdded(value: String): Boolean {
        return if (value.startsWith("-")) {
            false
        } else {
            value.contains("/") || value.contains("*") || value.contains("+") || value.contains("-")
        }
    }
}

Definitely could have been done better but it is very explanatory for beginners and persons that do not understand computer “logic”

My approach is always bruteforce to get it working within the parameters, then you go back and start refining and “shaking your head” at why you did things in a specific way.

1 Like

I suppose yeah. My expectation was (having some limited JS experience) that you can bind some sort of (integer) value to a button, than create “event listener” for all buttons (sort of target them with a class or something), not sure if that is correct Android terminology, and call a function that will pull the value from the button and then do something to variables. This just gave me “android is tedious” vibe.

But I like it. It’s nice what you can make a mobile device do. Really nice.

What you described is absolutely doable. I would go one step further and make a listener for a button press and have the button class perform the action internally. IE if the button that was pressed was a digit, then you have it return its value to the buffer. When the button is an operator button, the button operates on the buffer. If the button is clear, it clears the buffer. If the button is a format button, then it formats the buffer.

This would give you reusable code and move all of your heavy lifting logic out of the main.

1 Like

Awesome. I think I’ll stick to the course and when I’m done I’ll go back and try to refactor every exercise.
We do have some interesting apps lined up:

  • Quiz app
  • Drawing app
  • Weather app
  • 7 minute workout clone
  • Trello clone

Thanks for the pointers!

1 Like

When you start writing in strongly-typed languages you begin having to more succinctly and correctly express your logic.

The way events work in java (and in just about every programming language fundamentally) is by using a worker thread that runs an event loop that checks for events. When you hook an event with a listener, the functor is called when the event is triggered.

I highly recommend writing your own event loops if you want to understand this better. The term ‘bind’ is highly abstract and I’m not even sure most people understands what it actually means is happening. It’s also a word used to refer to other kinds of operations in other languages – such as in c++ (see std::bind) where it refers to binding runtime-values to the arguments of a functor to allow for an argument-less call which can work well in a template function or generic algorithm where multiple functors with different signatures need to be called.

In Java binding can refer to a name-to-object binding.

1 Like