How to Create a Simple Calculator App in Jetpack Compose?
Note : compileSdk = 34 ( necessary to run on latest emulator or latest android devices ).
MainActivity
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
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
package com.example.composecalculator
sealed class CalculatorOperation(val symbol: String){
object Add: CalculatorOperation("+")
object Subtract: CalculatorOperation("-")
object Multiply: CalculatorOperation("x")
object Divide: CalculatorOperation("÷")
}CalculatorAction
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
package com.example.composecalculator
data class CalculatorState(
val number1: String = "",
val number2: String = "",
val equation:String="",
val result:String="",
val operation: CalculatorOperation? = null
)CalculatorViewModel
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
}

.png)


