Jetpack Compose Calculator App
Calculator is a tool used in calculation. It is found every android phone. Today in this tutorial we will learn to create a simple calculator using jetpack compose. The calculator that we are going to create is a basic calculator so it can be used to do mathematical calculations such as addition, subtraction, multiplication and division. The version of Android Studio that we are using is Android Studio Jellyfish.
Steps
- Create a new project in Android Studio
- Add dependency for view model
- Create a new file for calculator Screen.
- Create a Calculator Operators file where we will keep our sealed class for defining operators and action.
- Create a data class named as Calculator State
- Create a Class named Calculator View Model
What we need?
To create a simple calculator we need a dependency for compose view model
How to add dependency?
Step 1. To add dependency for this project Go to build.gradle.kts and add the following dependency
implementation(libs.androidx.lifecycle.viewModelCompose)
Step 2. Go to libs.versions.toml and the following
[versions]
androidx-lifecycle = "2.8.2"
[libraries]
androidx-lifecycle-viewModelCompose = { module = "androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "androidx-lifecycle" }
CalculatorScreen
Copy this code →
package com.codingbihar.androidcomposecalculator
import ...
@Composable
fun CalculatorScreen(
state: CalculatorState,
modifier: Modifier = Modifier,
buttonSpacing: Dp = 8.dp,
onAction: (CalculatorAction) -> Unit
) {
Column (modifier = Modifier
.fillMaxWidth()
){
Box(modifier = modifier) {
Column(
modifier = Modifier
.fillMaxWidth()
.align(Alignment.BottomCenter),
verticalArrangement = Arrangement.spacedBy(buttonSpacing)
) {
// Text(text = state.input)
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.Black,
maxLines = 2
)
Text(
text = state.result,
textAlign = TextAlign.End,
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 32.dp),
fontWeight = FontWeight.Light,
fontSize = 80.sp,
color = Color.Black,
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)
}
)
}
}
}
}
}
@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
)
}
}
CalculatorOperators
Copy this code →
package com.codingbihar.androidcomposecalculator
sealed class CalculatorOperation(val symbol: String){
data object Add: CalculatorOperation("+")
data object Subtract: CalculatorOperation("-")
data object Multiply: CalculatorOperation("x")
data object Divide: CalculatorOperation("÷")
}
sealed class CalculatorAction{
data class Number(val number: Int): CalculatorAction()
data object Clear: CalculatorAction()
data object Delete: CalculatorAction()
data class Operation(val operation: CalculatorOperation): CalculatorAction()
data object Calculate: CalculatorAction()
data object Decimal: CalculatorAction()
}
CalculatorState
Copy this code →
package com.codingbihar.androidcomposecalculator
data class CalculatorState(
val number1: String = "",
val number2: String = "",
val input:String="",
val result:String="",
val operation: CalculatorOperation? = null
)
CalculatorViewModel
Copy this code →
package com.codingbihar.androidcomposecalculator
import ...
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),
input = 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
}
}
MainActivity
Copy this code →
package com.codingbihar.androidcomposecalculator
import ...
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
AndroidComposeClassTheme {
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
val viewModel = viewModel()
val state = viewModel.state
val buttonSpacing = 8.dp
CalculatorScreen(
state = state,
buttonSpacing = buttonSpacing,
onAction = viewModel::onAction,
modifier = Modifier.padding(innerPadding)
)
}
}
}
}
}