How to Create a Simple Calculator App in Jetpack Compose?
Welcome to codingbihar.com. Today we will learn to create a simple calculator app using jetpack compose which is the latest tools for building UI. In this we are using the latest version of Android Studio. Dependencies which we use to create this simple android calculator app.
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.7.0")
Note : compileSdk = 34 ( necessary to run on latest emulator or latest android devices ).
To create a simple calculator in jetpack compose we need the following files:
1. MainActivity - This is default created file When we create a new project in this file we create our UI for the calculator.
2. CalculatorButton - This is the common file in this file for our calculator Buttons and Texts.
In CalculatorOperation we define the mathematical operators symbols for add, subtract, multiply and divide etc. which is used in our calculator and
In CalculatorAction we define the action for our special buttons such as Delete, Clear All, Equal, Decimal and Numbers etc.
4. CalculatorState - We have one data class named CalculatorState where we defined the variables because this is the simple calculator so we are not using any third party library for calculation. We created our own calculation method for two input numbers number1 and number2 and result our output after calculation.
5. CalculatorViewModel - This is most important class file for our calculator as this is the ViewModel of our calculator where we write the code to perform action on clicking buttons of calculator and method of processing of calculations.
So in all we have six files in the above picture.
MainActivity
Copy this code →
package com.example.calculatorjetpack
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.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.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.example.calculatorjetpack.ui.theme.CalculatorJetpackTheme
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
CalculatorJetpackTheme {
val viewModel = viewModel<CalculatorViewModel>()
val state = viewModel.state
val buttonSpacing = 8.dp
Calculator(state = state,
onAction = viewModel::onAction,
buttonSpacing = buttonSpacing,
modifier = Modifier
.fillMaxSize()
.background(Color.Gray)
.padding(16.dp)
)
}
}
}
}
@Composable
fun Calculator(
state: CalculatorState,
modifier: Modifier = Modifier,
buttonSpacing: Dp = 8.dp,
onAction: (CalculatorAction) -> Unit
) {
Box(modifier = modifier) {
Column(
modifier = Modifier
.fillMaxWidth()
.align(Alignment.BottomCenter),
verticalArrangement = Arrangement.spacedBy(buttonSpacing)
) {
Text(
text = state.equation,
textAlign = TextAlign.End,
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 32.dp),
fontWeight = FontWeight.Light,
fontSize = 80.sp,
color = Color.White,
maxLines = 2
)
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",
modifier = Modifier
.background(Color.Red)
.aspectRatio(2f)
.weight(2f),
onClick = {
onAction(CalculatorAction.Clear)
}
)
CalculatorButton(symbol = "Del",
modifier = Modifier
.background(Color.Red)
.aspectRatio(1f)
.weight(1f),
onClick = {
onAction(CalculatorAction.Delete)
}
)
CalculatorButton(symbol = "÷",
modifier = Modifier
.background(Color.Red)
.aspectRatio(1f)
.weight(1f),
onClick = {
onAction(CalculatorAction.Operation(CalculatorOperation.Divide))
}
)
}
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(buttonSpacing)
) {
CalculatorButton(symbol = "7",
modifier = Modifier
.background(Color.DarkGray)
.aspectRatio(1f)
.weight(1f),
onClick = {
onAction(CalculatorAction.Number(7))
}
)
CalculatorButton(symbol = "8",
modifier = Modifier
.background(Color.DarkGray)
.aspectRatio(1f)
.weight(1f),
onClick = {
onAction(CalculatorAction.Number(8))
}
)
CalculatorButton(symbol = "9",
modifier = Modifier
.background(Color.DarkGray)
.aspectRatio(1f)
.weight(1f),
onClick = {
onAction(CalculatorAction.Number(9))
}
)
CalculatorButton(symbol = "x",
modifier = Modifier
.background(Color.Red)
.aspectRatio(1f)
.weight(1f),
onClick = {
onAction(CalculatorAction.Operation(CalculatorOperation.Multiply))
}
)
}
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(buttonSpacing)
) {
CalculatorButton(symbol = "4",
modifier = Modifier
.background(Color.DarkGray)
.aspectRatio(1f)
.weight(1f),
onClick = {
onAction(CalculatorAction.Number(4))
}
)
CalculatorButton(symbol = "5",
modifier = Modifier
.background(Color.DarkGray)
.aspectRatio(1f)
.weight(1f),
onClick = {
onAction(CalculatorAction.Number(5))
}
)
CalculatorButton(symbol = "6",
modifier = Modifier
.background(Color.DarkGray)
.aspectRatio(1f)
.weight(1f),
onClick = {
onAction(CalculatorAction.Number(6))
}
)
CalculatorButton(symbol = "-",
modifier = Modifier
.background(Color.Red)
.aspectRatio(1f)
.weight(1f),
onClick = {
onAction(CalculatorAction.Operation(CalculatorOperation.Subtract))
}
)
}
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(buttonSpacing)
) {
CalculatorButton(symbol = "1",
modifier = Modifier
.background(Color.DarkGray)
.aspectRatio(1f)
.weight(1f),
onClick = {
onAction(CalculatorAction.Number(1))
}
)
CalculatorButton(symbol = "2",
modifier = Modifier
.background(Color.DarkGray)
.aspectRatio(1f)
.weight(1f),
onClick = {
onAction(CalculatorAction.Number(2))
}
)
CalculatorButton(symbol = "3",
modifier = Modifier
.background(Color.DarkGray)
.aspectRatio(1f)
.weight(1f),
onClick = {
onAction(CalculatorAction.Number(3))
}
)
CalculatorButton(symbol = "+",
modifier = Modifier
.background(Color.Red)
.aspectRatio(1f)
.weight(1f),
onClick = {
onAction(CalculatorAction.Operation(CalculatorOperation.Add))
}
)
}
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(buttonSpacing)
) {
CalculatorButton(symbol = "0",
modifier = Modifier
.background(Color.DarkGray)
.aspectRatio(2f)
.weight(2f),
onClick = {
onAction(CalculatorAction.Number(0))
}
)
CalculatorButton(symbol = ".",
modifier = Modifier
.background(Color.DarkGray)
.aspectRatio(1f)
.weight(1f),
onClick = {
onAction(CalculatorAction.Decimal)
}
)
CalculatorButton(symbol = "=",
modifier = Modifier
.background(Color.Red)
.aspectRatio(1f)
.weight(1f),
onClick = {
onAction(CalculatorAction.Calculate)
}
)
}
}
}
}
CalculatorButton
Copy this code →
package com.example.composecalculator
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
@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
)
}
}
CalculatorOperation
Copy this code →
package com.example.composecalculator
sealed class CalculatorOperation(val symbol: String){
object Add: CalculatorOperation("+")
object Subtract: CalculatorOperation("-")
object Multiply: CalculatorOperation("x")
object Divide: CalculatorOperation("÷")
}
CalculatorAction
Copy this code →
package com.example.composecalculator
sealed class CalculatorAction{
data class Number(val number: Int): CalculatorAction()
object Clear: CalculatorAction()
object Delete: CalculatorAction()
data class Operation(val operation: CalculatorOperation): CalculatorAction()
object Calculate: CalculatorAction()
object Decimal: CalculatorAction()
}
CalculatorState
Copy this code →
package com.example.composecalculator
data class CalculatorState(
val number1: String = "",
val number2: String = "",
val equation:String="",
val result:String="",
val operation: CalculatorOperation? = null
)
CalculatorViewModel
Copy this code →
package com.example.composecalculator
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.toBigDecimalOrNull()
val number2 = state.number2.toBigDecimalOrNull()
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.toDouble() / number2.toDouble()
null -> return
}
state = state.copy(
number1 = state.number1 + (state.operation?.symbol ?: ""),
result=result.toString().take(8),
equation =state.number1 + (state.operation?.symbol ?: "") + state.number2,
operation = null
)
}
}
private fun delete() {
when {
state.result.isNotBlank() -> state = state.copy(
result = state.result.dropLast(10)
)
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
}