Developing a scientific calculator using Jetpack and Kotlin

The scientific calculator app we are building in this tutorial comes with an intuitive and attractive user interface.Additionally,it provides arithmetic functions such as divison and multiplication.

March 30, 20243 min read

Scientific calculators have a number of uses.For instance,they are majorly used to simplify arithmetic operations in supermarkets,shops,schools and even hospitals.This tutorial outlines step by step development of a scientific calculator using Jetpack and Kotlin in Android Studio.

Setting up project in Android Studio

  1. To create a scientific calculator from scratch ensure the latest version of Android studio is installed in your computer.Create a new project by choosing File,New,New Project then choose Empty Activity.Write Scientific Calculator in the name field and click Finish button.

ImageImage

Adding ViewModel Dependencies

2.Ensure you add the required View model dependencies in build.gradle(.kts) in the app level module which comprises of:


  implementation("androidx.lifecycle:lifecycle-viewmodel:$lifecycle_version")
    // LiveData
    implementation("androidx.lifecycle:lifecycle-livedata:$lifecycle_version")
    // Lifecycles only (without ViewModel or LiveData)
    implementation("androidx.lifecycle:lifecycle-runtime:$lifecycle_version")

    // Saved state module for ViewModel
    implementation("androidx.lifecycle:lifecycle-viewmodel-savedstate:$lifecycle_version")

    // Annotation processor
    annotationProcessor("androidx.lifecycle:lifecycle-compiler:$lifecycle_version")
    // alternately - if using Java8, use the following instead of lifecycle-compiler
    implementation("androidx.lifecycle:lifecycle-common-java8:$lifecycle_version")

    // optional - helpers for implementing LifecycleOwner in a Service
    implementation("androidx.lifecycle:lifecycle-service:$lifecycle_version")

    // optional - ProcessLifecycleOwner provides a lifecycle for the whole application process
    implementation("androidx.lifecycle:lifecycle-process:$lifecycle_version")

    // optional - ReactiveStreams support for LiveData
    implementation("androidx.lifecycle:lifecycle-reactivestreams:$lifecycle_version")

    // optional - Test helpers for LiveData
    testImplementation("androidx.arch.core:core-testing:$arch_version")

    // optional - Test helpers for Lifecycle runtime
    testImplementation("androidx.lifecycle:lifecycle-runtime-testing:$lifecycle_version")

Try Kodaschool for free

Click below to sign up and get access to free web, android and iOs challenges.

Sign Up

Calculator Components

3.Delete the code for Hello World logic.Next,add a class called CalculatorButton.kt then create a CalculatorButton function with a string symbol,modifiers to control UI changes and onClick lambda to respond to click events.Additionally,add Box composable and pass the modifier as a parameter and style it to achieve circular shape with 90dp .clip(RoundedCornerShape(90.dp)).Inside Box composable add Text composable with symbol, fontsize, color and Theme which is set to dark or light color.

package com.example.scientificcalculator
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.TextStyle

@Composable

fun calculatorButton(
    symbol: String,
    modifier: Modifier = Modifier,
    color: Color = Color.White,
    textStyle: TextStyle = TextStyle(),
    onClick: () -> Unit
){

    Box(
        contentAlignment = Alignment.Center,
        modifier = Modifier
            .clip(RoundedCornerShape(100.dp))
            .background(color)
            .clickable {
                onClick()
            }
            .then(modifier)
    ) {
        Text(
            text = symbol,
            style = textStyle,
            fontSize = 36.sp,
            color = Color.White
        )
    }


}

4.Next is creating click events that will aid in clearing all content,deleting a single item,arithmetic options such as multiplication,division,addition and subtraction(x,/,+ and -).Moreover,add a click event that triggers when = sign is pressed and another for the Decimal point.Define these in a new Sealed Class called CalculatorActions.kt.

package com.example.scientificcalculator

sealed class CalculatorActions {



data class Number(val number: Int): CalculatorActions()

object Clear: CalculatorActions()

object Delete: CalculatorActions()

data class Operation(val): CalculatorActions()

object Calculate: CalculatorActions()

object Decimal: CalculatorActions()



}
Image

Sealed Classes

5.Create a sealed class that will have all the operations including multiplication,division,addition, subtraction and operation.Pass the CalculatorOperations class to data class Operation in CalculatorActions.

package com.example.scientificcalculator

import java.text.DateFormatSymbols

sealed class CalculatorOperation (val symbols: String){
    object Add: CalculatorOperation("+")
    object Subtract: CalculatorOperation("-")
    object Multiply: CalculatorOperation("x")
    object Divide: CalculatorOperation("/")

}

Passing class into data class

  package com.example.scientificcalculator

   sealed  class CalculatorAction {

    data class Number(val number: Int): CalculatorAction()
    object Clear: CalculatorAction()
    object Delete: CalculatorAction()
    data class Operation(val operations: CalculatorOperation): CalculatorAction()
    object Calculate: CalculatorAction()
    object Decimal: CalculatorAction()

}

Tracking States

6.Add a data class that will allow tracking of states when user enter values in the calculator.Name the data Class CalculatorState.kt

Image

package com.example.scientificcalculator
data class CalculatorState(
    var no1: String ="",
    var no2: String ="",
    var operations: CalculatorOperation?=null,

)

7.Create a viewModel that will ensure that states are not lost even if the screen is rotated.

package com.example.scientificcalculator
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel

class CalculatorViewModel:ViewModel() {
    var state by mutableStateOf(CalculatorState())
    private set
}

8.Define the UI by styling the Items in the Layout in MainActivity and ensure that any custom colors are correctly defined in color.kt file in ui.theme folder

 Color Definition

package com.example.scientificcalculator.ui.theme

import androidx.compose.ui.graphics.Color

val Purple80 = Color(0xFFD0BCFF)
val PurpleGrey80 = Color(0xFFCCC2DC)
val Pink80 = Color(0xFFEFB8C8)
val Purple40 = Color(0xFF6650a4)
val PurpleGrey40 = Color(0xFF625b71)
val Pink40 = Color(0xFF7D5260)
val MediumGray = Color(0xFF2E2E2E)
val LightGray = Color(0xFF818181)
val Orange = Color

MainActivity code with styles for Calculator Layout

package com.example.scientificcalculator

import android.os.Bundle

import androidx.activity.ComponentActivity

import androidx.activity.compose.setContent

import androidx.compose.foundation.background

import androidx.compose.foundation.layout.Arrangement

import androidx.compose.foundation.layout.Box

import androidx.compose.foundation.layout.Column

import androidx.compose.foundation.layout.Row

import androidx.compose.foundation.layout.aspectRatio

import androidx.compose.foundation.layout.fillMaxSize

import androidx.compose.foundation.layout.fillMaxWidth

import androidx.compose.foundation.layout.padding

import androidx.compose.material3.MaterialTheme

import androidx.compose.material3.Surface

import androidx.compose.material3.Text

import androidx.compose.runtime.Composable

import androidx.compose.ui.Alignment

import androidx.compose.ui.Modifier

import androidx.compose.ui.graphics.Color

import androidx.compose.ui.graphics.Color.Companion.LightGray

import androidx.compose.ui.text.font.FontWeight

import androidx.compose.ui.text.style.TextAlign

import androidx.compose.ui.tooling.preview.Preview

import androidx.compose.ui.unit.dp

import androidx.compose.ui.unit.sp

import androidx.lifecycle.viewmodel.compose.viewModel

import com.example.scientificcalculator.ui.theme.MediumGray

import com.example.scientificcalculator.ui.theme.Orange

import com.example.scientificcalculator.ui.theme.ScientificCalculatorTheme



class MainActivity : ComponentActivity() {

override fun onCreate(savedInstanceState: Bundle?) {

super.onCreate(savedInstanceState)

setContent {

ScientificCalculatorTheme {val viewModel = viewModel<CalculatorViewModel>()

val state = viewModel.state

val buttonSpacing = 8.dp

Box(

modifier = Modifier

.fillMaxSize()

.background(Color.DarkGray)

.padding(16.dp)

) {

Column(

modifier = Modifier

.fillMaxWidth()

.align(Alignment.BottomCenter),

verticalArrangement = Arrangement.spacedBy(buttonSpacing),

) {

Text(

text = state.number1 + (state.operation?.symbol ?: "") + state.number2,

textAlign = TextAlign.End,

modifier = Modifier

.fillMaxWidth()

.padding(vertical = 32.dp),

fontWeight = FontWeight.Light,

fontSize = 80.sp,

color = Color.White,

maxLines = 2

)

Row(

modifier = Modifier

.fillMaxWidth(),

horizontalArrangement = Arrangement.spacedBy(buttonSpacing)

) {

CalculatorButton(

symbol = "AC",

color = LightGray,

modifier = Modifier

.aspectRatio(2f)

.weight(2f)

) {

viewModel.onAction(CalculatorAction.Clear)

}

CalculatorButton(

symbol = "Del",

color = LightGray,

modifier = Modifier

.aspectRatio(1f)

.weight(1f)

) {

viewModel.onAction(CalculatorAction.Delete)

}

CalculatorButton(

symbol = "/",

color = Orange,

modifier = Modifier

.aspectRatio(1f)

.weight(1f)

) {

viewModel.onAction(CalculatorAction.Operation(CalculatorOperation.Divide))

}

}

Row(

modifier = Modifier

.fillMaxWidth(),

horizontalArrangement = Arrangement.spacedBy(buttonSpacing)

) {

CalculatorButton(

symbol = "7",

color = MediumGray,

modifier = Modifier

.aspectRatio(1f)

.weight(1f)

) {

viewModel.onAction(CalculatorAction.Number(7))

}

CalculatorButton(

symbol = "8",

color = MediumGray,

modifier = Modifier

.aspectRatio(1f)

.weight(1f)

) {

viewModel.onAction(CalculatorAction.Number(8))

}

CalculatorButton(

symbol = "9",

color = MediumGray,

modifier = Modifier

.aspectRatio(1f)

.weight(1f)

) {

viewModel.onAction(CalculatorAction.Number(9))

}

CalculatorButton(

symbol = "x",

color = Orange,

modifier = Modifier

.aspectRatio(1f)

.weight(1f)

) {

viewModel.onAction(CalculatorAction.Operation(CalculatorOperation.Multiply))

}

}

Row(

modifier = Modifier

.fillMaxWidth(),

horizontalArrangement = Arrangement.spacedBy(buttonSpacing)

) {

CalculatorButton(

symbol = "4",

color = MediumGray,

modifier = Modifier

.aspectRatio(1f)

.weight(1f)

) {

viewModel.onAction(CalculatorAction.Number(4))

}

CalculatorButton(

symbol = "5",

color = MediumGray,

modifier = Modifier

.aspectRatio(1f)

.weight(1f)

) {

viewModel.onAction(CalculatorAction.Number(5))

}

CalculatorButton(

symbol = "6",

color = MediumGray,

modifier = Modifier

.aspectRatio(1f)

.weight(1f)

) {

viewModel.onAction(CalculatorAction.Number(6))

}

CalculatorButton(

symbol = "-",

color = Orange,

modifier = Modifier

.aspectRatio(1f)

.weight(1f)

) {

viewModel.onAction(CalculatorAction.Operation(CalculatorOperation.Subtract))

}

}

Row(

modifier = Modifier

.fillMaxWidth(),

horizontalArrangement = Arrangement.spacedBy(buttonSpacing)

) {

CalculatorButton(

symbol = "1",

color = MediumGray,

modifier = Modifier

.aspectRatio(1f)

.weight(1f)

) {

viewModel.onAction(CalculatorAction.Number(1))

}

CalculatorButton(

symbol = "2",

color = MediumGray,

modifier = Modifier

.aspectRatio(1f)

.weight(1f)

) {

viewModel.onAction(CalculatorAction.Number(2))

}

CalculatorButton(

symbol = "3",

color = MediumGray,

modifier = Modifier

.aspectRatio(1f)

.weight(1f)

) {

viewModel.onAction(CalculatorAction.Number(3))

}

CalculatorButton(

symbol = "+",

color = Orange,

modifier = Modifier

.aspectRatio(1f)

.weight(1f)

) {

viewModel.onAction(CalculatorAction.Operation(CalculatorOperation.Add))

}

}

Row(

modifier = Modifier

.fillMaxWidth(),

horizontalArrangement = Arrangement.spacedBy(buttonSpacing)

) {

CalculatorButton(

symbol = "0",

color = MediumGray,

modifier = Modifier

.aspectRatio(2f)

.weight(2f)

) {

viewModel.onAction(CalculatorAction.Number(0))

}

CalculatorButton(

symbol = ".",

color = MediumGray,

modifier = Modifier

.aspectRatio(1f)

.weight(1f)

) {

viewModel.onAction(CalculatorAction.Decimal)

}

CalculatorButton(

symbol = "=",

color = Orange,

modifier = Modifier

.aspectRatio(1f)

.weight(1f)

) {

viewModel.onAction(CalculatorAction.Calculate)

}

}

}

}

}

}

}

}

9.Finally add logic that will trigger the calculations in the CalculatorViewModel

package com.example.scientificcalculator
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel

class CalculatorViewModel: ViewModel() {

    var state by mutableStateOf(CalculatorState())

    fun onAction(action: CalculatorAction) {
        when(action) {
            is CalculatorAction.Number -> enterNumber(action.number)
            is CalculatorAction.Delete -> delete()
            is CalculatorAction.Clear -> state = CalculatorState()
            is CalculatorAction.Operation -> enterOperation(action.operation)
            is CalculatorAction.Decimal -> enterDecimal()
            is CalculatorAction.Calculate -> calculate()
        }
    }

    private fun enterOperation(operation: CalculatorOperation) {
        if(state.number1.isNotBlank()) {
            state = state.copy(operation = operation)
        }
    }

    private fun calculate() {
        val number1 = state.number1.toDoubleOrNull()
        val number2 = state.number2.toDoubleOrNull()
        if(number1 != null && number2 != null) {
            val result = when(state.operation) {
                is CalculatorOperation.Add -> number1 + number2
                is CalculatorOperation.Subtract -> number1 - number2
                is CalculatorOperation.Multiply -> number1 * number2
                is CalculatorOperation.Divide -> number1 / number2
                null -> return
            }
            state = state.copy(
                number1 = result.toString().take(15),
                number2 = "",
                operation = null
            )
        }
    }

    private fun delete() {
        when {
            state.number2.isNotBlank() -> state = state.copy(
                number2 = state.number2.dropLast(1)
            )
            state.operation != null -> state = state.copy(
                operation = null
            )
            state.number1.isNotBlank() -> state = state.copy(
                number1 = state.number1.dropLast(1)
            )
        }
    }

    private fun enterDecimal() {
        if(state.operation == null && !state.number1.contains(".") && state.number1.isNotBlank()) {
            state = state.copy(
                number1 = state.number1 + "."
            )
            return
        } else if(!state.number2.contains(".") && state.number2.isNotBlank()) {
            state = state.copy(
                number2 = state.number2 + "."
            )
        }
    }

    private fun enterNumber(number: Int) {
        if(state.operation == null) {
            if(state.number1.length >= MAX_NUM_LENGTH) {
                return
            }
            state = state.copy(
                number1 = state.number1 + number
            )
            return
        }
        if(state.number2.length >= MAX_NUM_LENGTH) {
            return
        }
        state = state.copy(
            number2 = state.number2 + number
        )
    }

    companion object {
        private const val MAX_NUM_LENGTH = 8
    }
}

Image

Please use the knowledge gathered in this tutorial,to build a scientific calculator for practice.This will enhance your understanding on concepts such as composables and lambdas in the context of Jetpack and Kotlin.

Wilson Ochieng

About Wilson Ochieng

Android Expert developing mobile applications with 4+ Years of Experience.