How to create BMI App?
BMI (Body Mass Index) is used to check whether a person is underweight, normal weight, overweight, or obese.
We will learn to create an android app for calculating BMI using Jetpack Compose. This is not for medical purposes but you can learn the basics of Jetpack Compose by building this small app. I think this is a very interesting and simple project that teaches you a lot about jetpack compos if you are a beginner.
Steps :
package com.example.bmicodingbihar
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.ui.Modifier
import com.example.bmicodingbihar.ui.theme.BMICodingBiharTheme
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
BMICodingBiharTheme {
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
BMIApp()
}
}
}
}
}
package com.example.bmicodingbihar
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.CenterAlignedTopAppBar
import androidx.compose.material3.Divider
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun BMIApp(viewModel: BmiViewModel = viewModel()) {
Scaffold(
topBar = {
CenterAlignedTopAppBar(
title = {
Text(text = "BMI Coding Bihar")
}
)
}
) { paddingValues ->
Column(
modifier = Modifier
.fillMaxSize()
.padding(paddingValues),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.SpaceAround
) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(paddingValues),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.SpaceAround
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Text(
text = "Coding Bihar".format(viewModel.bmi),
style = MaterialTheme.typography.headlineSmall,
)
Divider(modifier = Modifier.fillMaxWidth(.7f), thickness = 2.5.dp)
Text(
text = viewModel.message,
style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.secondary
)
}
Column(horizontalAlignment = Alignment.CenterHorizontally) {
ModeSelector(viewModel.selectedMode, updateMode = viewModel::updateMode)
CustomTextField(viewModel.heightState, ImeAction.Next, viewModel::updateHeight)
CustomTextField(viewModel.weightState, ImeAction.Done, viewModel::updateWeight)
}
Spacer(modifier = Modifier)
Row(
horizontalArrangement = Arrangement.spacedBy(8.dp),
modifier = Modifier
.padding(16.dp, 12.dp)
.fillMaxWidth(),
) {
ActionButton(text = "Clear", viewModel::clear)
ActionButton(text = "Calculate", viewModel::calculate)
}
}
}
}
}
package com.example.bmicodingbihar
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.Button
import androidx.compose.material3.ElevatedFilterChip
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ModeSelector(selectedMode: BmiViewModel.Mode, updateMode: (BmiViewModel.Mode) -> Unit) {
Row(horizontalArrangement = Arrangement.spacedBy(10.dp)) {
BmiViewModel.Mode.entries.forEach {
ElevatedFilterChip(selectedMode == it, onClick = { updateMode(it) },
label = {
Text(it.name)
}
)
}
}
}
@Composable
fun RowScope.ActionButton(text: String, onClick: () -> Unit) {
val focusManager = LocalFocusManager.current
Button(
onClick = { focusManager.clearFocus(); onClick() },
shape = RoundedCornerShape(8.dp),
modifier = Modifier.weight(1f),
contentPadding = PaddingValues(14.dp)
) {
Text(text, fontSize = 15.sp)
}
}
@Composable
fun CustomTextField(state: ValueState,
imeAction: ImeAction, onValueChange: (String) -> Unit, ) {
val focusManager = LocalFocusManager.current
OutlinedTextField(
value = state.value,
isError = state.error != null,
supportingText = { state.error?.let { Text(it) } },
label = { Text(state.label) },
suffix = { Text(state.suffix) },
onValueChange = onValueChange,
singleLine = true,
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Decimal,
imeAction = imeAction),
keyboardActions = KeyboardActions(onDone = {
focusManager.clearFocus()
}
)
)
}
package com.example.bmicodingbihar
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableDoubleStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
class BmiViewModel : ViewModel() {
var bmi by mutableDoubleStateOf(0.0)
private set
var message by mutableStateOf("")
private set
var selectedMode by mutableStateOf(Mode.Metric)
private set
var heightState by mutableStateOf(ValueState("Height", "m"))
private set
var weightState by mutableStateOf(ValueState("Weight", "kg"))
private set
fun updateHeight(it: String) {
heightState = heightState.copy(value = it, error = null)
}
fun updateWeight(it: String) {
weightState = weightState.copy(value = it, error = null)
}
fun calculate() {
val height = heightState.toNumber()
val weight = weightState.toNumber()
if (height == null)
heightState = heightState.copy(error = "Invalid number")
else if (weight == null)
weightState = weightState.copy(error = "Invalid number")
else calculateBMI(height, weight, selectedMode == Mode.Metric)
}
private fun calculateBMI(height: Double, weight: Double, isMetric: Boolean = true) {
bmi = if (isMetric)
weight / (height * height)
else (703 * weight) / (height * height)
message = when {
bmi < 18.5 -> "Your BMI is Underweight"
bmi in 18.5..24.9 -> "Your BMI is Normal"
bmi in 25.0..29.9 -> "Your BMI is Overweight"
bmi >= 30.0 -> "Your BMI is Obsess"
else -> error("Invalid params")
}
}
fun updateMode(it: Mode) {
selectedMode = it
when (selectedMode) {
Mode.Imperial -> {
heightState = heightState.copy(suffix = "inch")
weightState = weightState.copy(suffix = "pound")
}
Mode.Metric -> {
heightState = heightState.copy(suffix = "m")
weightState = weightState.copy(suffix = "kg")
}
}
}
fun clear() {
heightState = heightState.copy(value = "", error = null)
weightState = weightState.copy(value = "", error = null)
bmi = 0.0
message = ""
}
enum class Mode { Imperial, Metric }
}
package com.example.bmicodingbihar
data class ValueState(
val label: String,
val suffix: String,
val value: String = "",
val error: String? = null
) {
fun toNumber() = value.toDoubleOrNull()
}




