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.
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.
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.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.Step 6:
EmoticonEmoticon