How to Make an BMI Calculator App
How to Vibe Code a Native BMI Calculator App
Stay in your creative flow. Direct AI models with high-level intent, stitch together Jetpack Compose UI, and ship an installed BMI & wellness tracker APK on your phone in under 25 minutes.
Core Stack
Kotlin Compose
Persistence
DataStore / Room
AI Partner
Claude / Gemini
Target Time
15โ25 Mins
The Vibe Coding Mobile Philosophy
Traditional mobile health app development has an intimidating reputation: XML layouts, manual unit conversion listeners, division-by-zero edge cases, and boilerplate SQLite helpers. Vibe coding turns this upside down. You operate as the Product Director & Architect: defining the visual feel, interactive sliders, and WHO category gauge while conversational AI synthesizes the single-file Compose code and local storage wiring.
Old-School Mobile Stress
- Manually wiring XML input fields and matching IDs with
findViewById. - Debugging Metric (
kg/mยฒ) vs. Imperial (703 ร lb/inยฒ) conversion bugs by hand. - Writing boilerplate SQLite open helpers just to log weekly weight entries.
- Wrestling custom Canvas trigonometry for hours to draw a category gauge.
The Vibe Coder Way
- Modern Jetpack Compose (pure Kotlin, reactive sliders, instant live calculation).
- Describe the UI vibe: "Clean wellness card with smooth height/weight sliders and an animated WHO spectrum bar."
- Feed compiler errors back into AI: "Fix this missing import without altering my BMI math."
- Test immediately on your real phone with Wireless Debugging.
The 4-Step Blueprint
From a blank Android Studio canvas to logging real health check-ins on your handset.
Spawning the Empty Compose Canvas
Open Android Studio and select New Project > Empty Activity (with Compose logo). Name it VibeBMI.
The "One-Shot Architect" Prompt
Give your LLM the full mental model upfront: Metric/Imperial switcher, smooth height & weight sliders, live BMI calculation, and WHO category interpretation.
MainActivity.kt so I can copy-paste and verify immediately."
Styling & Health Micro-Interactions
Inject polish. Add a color-coded WHO spectrum bar (Underweight, Normal, Overweight, Obese), healthy target weight range hints, and haptic feedback as sliders move.
Run on Your Real Phone
Turn on Developer Options > Wireless Debugging on your Android phone. Pair via QR code in Android Studio and hit Run (Shift+F10).
Vibe Prompt Configurator
Select your desired aesthetics and health features. Copy the generated prompt directly into Claude, Cursor, or Gemini.
Essential Elements of a BMI Calculator App
Body Mass Index (BMI) uses height and weight to estimate body fat and screen for health risks. Make sure your app covers these core building blocks:
-
Height & Weight Input Controls
Allow users to smoothly enter or slide their height and weight with instant toggling between Metric (
cm/kg) and Imperial (ft-in/lbs) units. -
Real-Time BMI Calculation
Compute BMI accurately on the fly (
weight / heightยฒ) with safe minimum bounds so zero or incomplete inputs never crash the app. -
WHO Standard Interpretation
Classify results clearly according to World Health Organization thresholds: Underweight (
<18.5), Normal (18.5โ24.9), Overweight (25โ29.9), and Obese (30+). -
Customized Feedback & Target Range
Provide encouraging, plain-language guidance based on the user's score alongside the healthy weight range for their specific height.
-
Progress & Companion Health Tools
Let users log check-ins to track weight over time, and optionally extend the app with a daily calorie (BMR/TDEE) calculator or workout tracker.
-
Clean, Uncluttered UI & UX
Avoid overwhelming medical jargon, guide users step-by-step from input to result, and keep high contrast on numbers and category badges.
The "Golden" Single-File Compose Template
Replace the contents of your MainActivity.kt with this to run a working BMI Calculator right now.
package com.example.vibebmi
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.animation.animateColorAsState
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.*
import androidx.compose.runtime.*
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.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import java.util.Locale
import kotlin.math.pow
// --- 1. Data Model & WHO Classification ---
data class BmiLog(
val id: Long = System.currentTimeMillis(),
val bmi: Float,
val category: String,
val summary: String
)
enum class BmiCategory(val label: String, val color: Color, val advice: String) {
UNDERWEIGHT("Underweight", Color(0xFF38BDF8), "Focus on nutrient-dense meals and strength training."),
NORMAL("Normal Weight", Color(0xFF10B981), "Great balance! Keep up your active lifestyle."),
OVERWEIGHT("Overweight", Color(0xFFF59E0B), "A modest calorie deficit and daily walks help."),
OBESE("Obese", Color(0xFFF43F5E), "Consider consulting a healthcare provider for a tailored plan.")
}
fun classifyBmi(bmi: Float): BmiCategory = when {
bmi < 18.5f -> BmiCategory.UNDERWEIGHT
bmi < 25.0f -> BmiCategory.NORMAL
bmi < 30.0f -> BmiCategory.OVERWEIGHT
else -> BmiCategory.OBESE
}
// --- 2. Main Entry Point ---
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MaterialTheme(
colorScheme = darkColorScheme(
background = Color(0xFF0D141C),
surface = Color(0xFF16202B),
primary = Color(0xFF10B981)
)
) {
Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) {
VibeBmiScreen()
}
}
}
}
}
// --- 3. Interactive Vibe BMI Screen ---
@Composable
fun VibeBmiScreen() {
var isMetric by remember { mutableStateOf(true) }
var heightCm by remember { mutableFloatStateOf(172f) }
var weightKg by remember { mutableFloatStateOf(68f) }
var logs by remember { mutableStateOf(listOf<BmiLog>()) }
val heightM = (heightCm / 100f).coerceAtLeast(0.5f)
val bmi = weightKg / heightM.pow(2)
val category = classifyBmi(bmi)
val badgeColor by animateColorAsState(targetValue = category.color, label = "bmiColor")
val minHealthyKg = 18.5f * heightM.pow(2)
val maxHealthyKg = 24.9f * heightM.pow(2)
Scaffold(
topBar = {
Row(
modifier = Modifier
.fillMaxWidth()
.statusBarsPadding()
.padding(horizontal = 20.dp, vertical = 16.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Column {
Text("Vibe BMI โก", fontSize = 26.sp, fontWeight = FontWeight.ExtraBold, color = Color.White)
Text("WHO Standard Index & Target Range", fontSize = 12.sp, color = Color(0xFF94A3B8))
}
FilterChip(
selected = isMetric,
onClick = { isMetric = !isMetric },
label = { Text(if (isMetric) "Metric (kg/cm)" else "Imperial (lb/in)") }
)
}
},
containerColor = Color.Transparent
) { innerPadding ->
Column(
modifier = Modifier
.padding(innerPadding)
.fillMaxSize()
.padding(horizontal = 20.dp),
verticalArrangement = Arrangement.spacedBy(14.dp)
) {
// Live Result Card
Column(
modifier = Modifier
.fillMaxWidth()
.clip(RoundedCornerShape(20.dp))
.background(Color(0xFF16202B))
.padding(20.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text("YOUR BODY MASS INDEX", fontSize = 11.sp, color = Color(0xFF94A3B8), fontWeight = FontWeight.Bold)
Text(
text = String.format(Locale.US, "%.1f", bmi),
fontSize = 48.sp,
fontWeight = FontWeight.Black,
color = badgeColor
)
Box(
modifier = Modifier
.clip(CircleShape)
.background(badgeColor.copy(alpha = 0.18f))
.padding(horizontal = 14.dp, vertical = 4.dp)
) {
Text(category.label, color = badgeColor, fontWeight = FontWeight.Bold, fontSize = 13.sp)
}
Spacer(modifier = Modifier.height(8.dp))
Text(category.advice, fontSize = 12.sp, color = Color(0xFFCBD5E1))
Spacer(modifier = Modifier.height(6.dp))
Text(
text = if (isMetric) {
String.format(Locale.US, "Healthy range: %.1f โ %.1f kg", minHealthyKg, maxHealthyKg)
} else {
String.format(Locale.US, "Healthy range: %.0f โ %.0f lbs", minHealthyKg * 2.20462f, maxHealthyKg * 2.20462f)
},
fontSize = 12.sp,
color = Color(0xFF10B981),
fontWeight = FontWeight.SemiBold
)
}
// Height Slider Card
Column(
modifier = Modifier
.fillMaxWidth()
.clip(RoundedCornerShape(16.dp))
.background(Color(0xFF16202B))
.padding(16.dp)
) {
val heightLabel = if (isMetric) {
"${heightCm.toInt()} cm"
} else {
val totalInches = (heightCm / 2.54f).toInt()
"${totalInches / 12}' ${totalInches % 12}\""
}
Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
Text("Height", color = Color(0xFF94A3B8), fontSize = 14.sp)
Text(heightLabel, color = Color.White, fontWeight = FontWeight.Bold, fontSize = 16.sp)
}
Slider(
value = heightCm,
onValueChange = { heightCm = it },
valueRange = 120f..220f
)
}
// Weight Slider Card
Column(
modifier = Modifier
.fillMaxWidth()
.clip(RoundedCornerShape(16.dp))
.background(Color(0xFF16202B))
.padding(16.dp)
) {
val weightLabel = if (isMetric) {
String.format(Locale.US, "%.1f kg", weightKg)
} else {
String.format(Locale.US, "%.0f lbs", weightKg * 2.20462f)
}
Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
Text("Weight", color = Color(0xFF94A3B8), fontSize = 14.sp)
Text(weightLabel, color = Color.White, fontWeight = FontWeight.Bold, fontSize = 16.sp)
}
Slider(
value = weightKg,
onValueChange = { weightKg = it },
valueRange = 35f..160f
)
}
Button(
onClick = {
val entry = BmiLog(
bmi = bmi,
category = category.label,
summary = "${heightCm.toInt()} cm โข ${weightKg.toInt()} kg"
)
logs = listOf(entry) + logs
},
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(14.dp)
) {
Text("Save Check-In to History", color = Color.Black, fontWeight = FontWeight.Bold)
}
// Recent Logs
LazyColumn(
modifier = Modifier.weight(1f),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
items(logs, key = { it.id }) { log ->
Row(
modifier = Modifier
.fillMaxWidth()
.clip(RoundedCornerShape(12.dp))
.background(Color(0xFF16202B))
.padding(12.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Column {
Text(log.category, color = Color.White, fontWeight = FontWeight.SemiBold, fontSize = 14.sp)
Text(log.summary, color = Color(0xFF94A3B8), fontSize = 12.sp)
}
Text(
text = String.format(Locale.US, "BMI %.1f", log.bmi),
color = Color(0xFF10B981),
fontWeight = FontWeight.Bold
)
}
}
}
}
}
}
How to Debug Without Losing Your Vibe
When Gradle or Compose flags an unresolved reference or unit conversion quirk, use these three vibe debugging rituals:
The Error Dump Rule
Copy the exact red text from the Android Studio Build tab and prompt: "Here is my Gradle compiler error. Tell me only which dependencies or imports are missing."
Single Source of Truth Math
Avoid slider drift when toggling Metric and Imperial units by instructing the AI: "Store height in cm and weight in kg internally, and only convert for display labels when Imperial is active."
Instant Wireless Run
Emulators eat heavy RAM. Pair your physical phone via Wi-Fi once, and hit Apply Changes whenever you tweak WHO category colors or slider ranges.
The Vibe Prompt Vault
Copy-paste prompts to level up your BMI Calculator with gauges, charts, and body composition metrics.
๐ฏ Prompt: Add an Animated Semicircle Canvas Gauge for WHO Categories
"I have a working Jetpack Compose BMI Calculator in MainActivity.kt. Add a custom Compose Canvas semicircle gauge above the BMI score with 4 colored arc segments (Underweight <18.5, Normal 18.5โ24.9, Overweight 25โ29.9, Obese 30+) and an animated needle that smoothly rotates to the current BMI value using animateFloatAsState."
๐๏ธ Prompt: Upgrade to Persistent Weight & BMI History with Room SQLite
"Refactor my BMI Calculator app to store check-ins in a local Room database. 1) Give me the exact lines to add to build.gradle.kts for Room & KSP. 2) Provide the BmiEntryEntity, BmiDao, AppDatabase, and a ViewModel collecting a Flow of entries sorted newest-first, showing the weight difference (+/- kg) since the previous entry."
๐ Prompt: Add Waist-to-Height Ratio & Body Fat Percentage Estimator
"Extend my Jetpack Compose BMI app with an optional 'Advanced Metrics' expandable card that lets the user enter Age, Waist circumference, and Neck circumference to calculate Waist-to-Height Ratio (WHtR) and estimated Body Fat % alongside standard BMI, using clean Material 3 chips and cards."