Android: Simple Calculator in Kotlin

It has been a long time since I wrote the tutorial: Android: Simple Calculator. However, still, that tutorial attracts more readers to my blog. Therefore, I have decided to write a new article to develop the same application using Kotlin. The previous article on developing a Calculator application using Java is still valid and feel free to compare this article with the previous one.


Android: Simple Calculator in Kotlin

Step 1:
Create a new Android application: Kotlin Calculator with an Empty activity. In the very first dialog, make sure that you have enabled the Kotlin support.

Android: Simple Calculator in Kotlin

Step 2:
Add the exp4j dependency to the project by adding implementation 'net.objecthunter:exp4j:0.4.8' to the dependencies in build.gradle. This library is used in Step 4, to evaluate the input in the String format. After the modification, the file should look like the following screenshot.


Android: Simple Calculator in Kotlin

Step 3:
Create a new file button.xml in the res/drawable folder with the following content. This drawable will be later used to decorate the buttons in our calculator.
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_pressed="true">
        <shape>
            <gradient android:angle="90" android:endColor="#FFFFFF" android:startColor="#9EB8FF" android:type="linear"/>
            <padding android:bottom="0dp" android:left="0dp" android:right="0dp" android:top="0dp"/>
            <size android:width="60dp" android:height="60dp"/>
            <stroke android:width="1dp" android:color="#ff3da6ef"/>
        </shape>
    </item>
    <item>
        <shape>
            <gradient android:angle="90" android:endColor="#FFFFFF" android:startColor="#ffd9d9d9" android:type="linear"/>
            <padding android:bottom="0dp" android:left="0dp" android:right="0dp" android:top="0dp"/>
            <size android:width="60dp" android:height="60dp"/>
            <stroke android:width="0.5dp" android:color="#ffcecece"/>
        </shape>
    </item>
</selector>


Step 4:
Modify the MainActivity.kt as shown below. This code is nothing other than the Kotlin implementation of the MainActivity.java from my previous article. However, at this time, we do not have User Interface (UI) controls in the activity_main.xml. Therefore, there can be some errors with the findViewById function call.
package com.javahelps.kotlincalculator

import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.view.View
import android.widget.Button
import android.widget.TextView
import net.objecthunter.exp4j.ExpressionBuilder


class MainActivity : AppCompatActivity() {

    // TextView used to display the input and output
    lateinit var txtInput: TextView

    // Represent whether the lastly pressed key is numeric or not
    var lastNumeric: Boolean = false

    // Represent that current state is in error or not
    var stateError: Boolean = false

    // If true, do not allow to add another DOT
    var lastDot: Boolean = false

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

    /**
     * Append the Button.text to the TextView
     */
    fun onDigit(view: View) {
        if (stateError) {
            // If current state is Error, replace the error message
            txtInput.text = (view as Button).text
            stateError = false
        } else {
            // If not, already there is a valid expression so append to it
            txtInput.append((view as Button).text)
        }
        // Set the flag
        lastNumeric = true
    }

    /**
     * Append . to the TextView
     */
    fun onDecimalPoint(view: View) {
        if (lastNumeric && !stateError && !lastDot) {
            txtInput.append(".")
            lastNumeric = false
            lastDot = true
        }
    }

    /**
     * Append +,-,*,/ operators to the TextView
     */
    fun onOperator(view: View) {
        if (lastNumeric && !stateError) {
            txtInput.append((view as Button).text)
            lastNumeric = false
            lastDot = false    // Reset the DOT flag
        }
    }


    /**
     * Clear the TextView
     */
    fun onClear(view: View) {
        this.txtInput.text = ""
        lastNumeric = false
        stateError = false
        lastDot = false
    }

    /**
     * Calculate the output using Exp4j
     */
    fun onEqual(view: View) {
        // If the current state is error, nothing to do.
        // If the last input is a number only, solution can be found.
        if (lastNumeric && !stateError) {
            // Read the expression
            val txt = txtInput.text.toString()
            // Create an Expression (A class from exp4j library)
            val expression = ExpressionBuilder(txt).build()
            try {
                // Calculate the result and display
                val result = expression.evaluate()
                txtInput.text = result.toString()
                lastDot = true // Result contains a dot
            } catch (ex: ArithmeticException) {
                // Display an error message
                txtInput.text = "Error"
                stateError = true
                lastNumeric = false
            }
        }
    }
}
Let's analyze this code function by function. First of all, note that we have defined some variables inside the class. Among them, txtInput is the refernce to the TextView which is assigned in the onCreate function. lastNumeric is a flag indicating that the recent button pressed was a numeric button. You can confirm it by checking the onDigit function. stateError variable is another flag indicating that there was error in processing the last input and currently the TextView is showing an ERROR message. Similar, lastDot variable is used to indicate that the last input was a ".". These variables are used in appropriate places to avoid invalid input. For example, if the TextView shows an ERROR message, we should not let the user to press an operator (say +) because then the output will be "ERROR+" which will lead to another error. Pay attention to the if conditions used in all the functions. They prevent such invalid expressions by checking the flag variables.

How to calculate the output is defined in the onEqual function. We have ensured that the TextView will always show a valid input expression (For example, it may be 20*5+3). It is highly complex to extract the operands and operators from this input. This is where the Exp4j library becomes a lifesaver. Exp4j library can process an expression in String format and return the result. Therefore, in the onEquals method, we feed the String value of the txtInput.text to the ExpressionBuilder and evaluate the result.

Step 5:
Now modify the activity_main.xml as given below:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:paddingTop="8dp"
        tools:context=".MainActivity">

    <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:ems="10"
            android:textSize="48sp"
            android:background="#efefef"
            android:id="@+id/txtInput"
            android:gravity="right|center_vertical"
            android:maxLength="12"
            android:layout_marginLeft="8dp"
            android:layout_marginEnd="8dp"
            android:layout_marginStart="8dp"
            app:layout_constraintTop_toTopOf="parent"
            android:layout_marginRight="8dp"/>

    <TableLayout
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_marginTop="8dp"
            app:layout_goneMarginTop="8dp"
            android:layout_marginLeft="8dp"
            android:layout_marginBottom="8dp"
            android:layout_marginRight="8dp"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/txtInput"
            android:gravity="fill">

        <TableRow
                android:layout_width="match_parent"
                android:layout_height="0dp"
                android:layout_weight="1"
                android:gravity="center">

            <Button
                    android:text="7"
                    android:background="@drawable/button"
                    android:layout_width="0dp"
                    android:layout_weight="1"
                    android:layout_height="match_parent"
                    android:onClick="onDigit"
                    android:id="@+id/btnSeven"/>

            <Button
                    android:text="8"
                    android:background="@drawable/button"
                    android:layout_width="0dp"
                    android:layout_weight="1"
                    android:layout_height="match_parent"
                    android:onClick="onDigit"
                    android:id="@+id/btnEight"/>

            <Button
                    android:text="9"
                    android:background="@drawable/button"
                    android:layout_width="0dp"
                    android:layout_weight="1"
                    android:layout_height="match_parent"
                    android:onClick="onDigit"
                    android:id="@+id/btnNine"/>

            <Button
                    android:text="/"
                    android:background="@drawable/button"
                    android:layout_width="0dp"
                    android:layout_weight="1"
                    android:layout_height="match_parent"
                    android:onClick="onOperator"
                    android:id="@+id/btnDivide"/>
        </TableRow>

        <TableRow
                android:layout_width="match_parent"
                android:layout_height="0dp"
                android:layout_weight="1"
                android:gravity="center">

            <Button
                    android:text="4"
                    android:background="@drawable/button"
                    android:layout_width="0dp"
                    android:layout_weight="1"
                    android:layout_height="match_parent"
                    android:onClick="onDigit"
                    android:id="@+id/btnFour"/>

            <Button
                    android:text="5"
                    android:background="@drawable/button"
                    android:layout_width="0dp"
                    android:layout_weight="1"
                    android:layout_height="match_parent"
                    android:onClick="onDigit"
                    android:id="@+id/btnFive"/>

            <Button
                    android:text="6"
                    android:background="@drawable/button"
                    android:layout_width="0dp"
                    android:layout_weight="1"
                    android:layout_height="match_parent"
                    android:onClick="onDigit"
                    android:id="@+id/btnSix"/>

            <Button
                    android:text="*"
                    android:background="@drawable/button"
                    android:layout_width="0dp"
                    android:layout_weight="1"
                    android:layout_height="match_parent"
                    android:onClick="onOperator"
                    android:id="@+id/btnMultiply"/>
        </TableRow>

        <TableRow
                android:layout_width="match_parent"
                android:layout_height="0dp"
                android:layout_weight="1"
                android:gravity="center">

            <Button
                    android:text="1"
                    android:background="@drawable/button"
                    android:layout_width="0dp"
                    android:layout_weight="1"
                    android:layout_height="match_parent"
                    android:onClick="onDigit"
                    android:id="@+id/btnOne"/>

            <Button
                    android:text="2"
                    android:background="@drawable/button"
                    android:layout_width="0dp"
                    android:layout_weight="1"
                    android:layout_height="match_parent"
                    android:onClick="onDigit"
                    android:id="@+id/btnTwo"/>

            <Button
                    android:text="3"
                    android:background="@drawable/button"
                    android:layout_width="0dp"
                    android:layout_weight="1"
                    android:layout_height="match_parent"
                    android:onClick="onDigit"
                    android:id="@+id/btnThree"/>

            <Button
                    android:text="-"
                    android:background="@drawable/button"
                    android:layout_width="0dp"
                    android:layout_weight="1"
                    android:layout_height="match_parent"
                    android:onClick="onOperator"
                    android:id="@+id/btnSubtract"/>
        </TableRow>

        <TableRow
                android:layout_width="match_parent"
                android:layout_height="0dp"
                android:layout_weight="1"
                android:gravity="center">

            <Button
                    android:text="."
                    android:background="@drawable/button"
                    android:layout_width="0dp"
                    android:layout_weight="1"
                    android:layout_height="match_parent"
                    android:onClick="onDecimalPoint"
                    android:id="@+id/btnDecimal"/>

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

            <Button
                    android:text="CLR"
                    android:background="@drawable/button"
                    android:layout_width="0dp"
                    android:layout_weight="1"
                    android:layout_height="match_parent"
                    android:onClick="onClear"
                    android:id="@+id/btnClear"/>

            <Button
                    android:text="+"
                    android:background="@drawable/button"
                    android:layout_width="0dp"
                    android:layout_weight="1"
                    android:layout_height="match_parent"
                    android:onClick="onOperator"
                    android:id="@+id/btnAdd"/>
        </TableRow>

        <TableRow
                android:layout_width="match_parent"
                android:layout_height="0dp"
                android:layout_weight="1">

            <Button
                    android:text="="
                    android:background="@drawable/button"
                    android:layout_width="0dp"
                    android:layout_weight="1"
                    android:layout_height="match_parent"
                    android:onClick="onEqual"
                    android:id="@+id/btnEqual"/>
        </TableRow>
    </TableLayout>
</android.support.constraint.ConstraintLayout>
Above code defines all the UI controls and bind them with the MainActivity.kt. Note the android:onClick attributes of buttons refering the functions defined in the MainActivity.kt.
Android: Simple Calculator in Kotlin
Step 6:
Save all the changes and run the project.
Android: Simple Calculator in Kotlin

Find the project @ Git Hub
Previous
Next Post »

Contact Form

Name

Email *

Message *