Coding Bihar

How to create BMI App

How to create BMI App - Coding Bihar
How to create BMI App

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 purpose but you can learn Basics of Jetpack Compose by building this small app. I think this is very interesting and simple project that teach you a lot about jetpack compose if you are a beginner.

This app is based on the data given by user. There is a mathematical concept if a user input his weight and height the app do calculations and gives result
For matric weight id divided by square of height and For Imperial the result will multiply by 703 and show the message according the following
            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"

For this project I created 2 file named as BMI and CommonFile and 1 Kotlin Class for BmiViewModel and 1 data class named ValueState and MainActivity is the default file we have when we created a project.
1. MainActivity
2. BMI
3. CommonFile
4. BmiViewModel
5. ValueState

So follow the following steps to build a simple BMI Android App using Jetpack Compose with us. 

Steps :


We are using the latest version of Android Studio Hedgehog.

1. Create a New Project and type the name as you want in my case it is BMI Coding Bihar.

The dependencies we use in this project

2. First add this in the build.gradle.kts (Module: app) file and sync.

implementation ("androidx.lifecycle:lifecycle-viewmodel-compose:2.5.1")

3. MainActivity
Copy this code →

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()
                }
            }
        }
    }
}
4. BMI.kt
Copy this code →

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)
                }
            }
        }
    }
}
5. CommonFile.kt
Copy this code →

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()
        }

        )
    )
}
6. BmiViewModel
Copy this code →

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 }
}
7. ValueState
Copy this code →

package com.example.bmicodingbihar

data class ValueState(
    val label: String, 
    val suffix: String, 
    val value: String = "", 
    val error: String? = null
) {
    fun toNumber() = value.toDoubleOrNull()
}
OUTPUT:
Android Project Image

Android Project Image

Android Project Image

Android Project Image


********************End********************


 Sandeep Gupta

Posted by Sandeep Gupta

Please share your feedback us at:sandeep@codingbihar.com. Thank you for being a part of our community!

Special Message

Welcome to coding bihar!