Flutter Beginner Lesson 125 of 135 3 min read
BMI Calculator App in Flutter (Beginner Project with Source Code)
Make a BMI calculator in Flutter with sliders, metric and imperial units, and a coloured result card. Beginner project with full runnable code.
On this page
A small, useful app that practises sliders, segmented buttons, cards and turning numbers into a result with colour.
BMI (body mass index) = weight in kg ÷ (height in m)². It is a rough guide, not medical advice.
The full app
import 'package:flutter/material.dart';
void main() => runApp(MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(colorSchemeSeed: Colors.teal),
home: const BmiPage(),
));
enum Units { metric, imperial }
class BmiPage extends StatefulWidget {
const BmiPage({super.key});
@override
State<BmiPage> createState() => _BmiPageState();
}
class _BmiPageState extends State<BmiPage> {
Units units = Units.metric;
double heightCm = 170;
double weightKg = 65;
double get bmi => weightKg / ((heightCm / 100) * (heightCm / 100));
(String, Color) get category => switch (bmi) {
< 18.5 => ('Underweight', Colors.blue),
< 25 => ('Normal', Colors.green),
< 30 => ('Overweight', Colors.orange),
_ => ('Obese', Colors.red),
};
String get heightLabel {
if (units == Units.metric) return '${heightCm.round()} cm';
final totalInches = heightCm / 2.54;
return "${totalInches ~/ 12} ft ${(totalInches % 12).round()} in";
}
String get weightLabel =>
units == Units.metric ? '${weightKg.round()} kg' : '${(weightKg * 2.20462).round()} lb';
@override
Widget build(BuildContext context) {
final (label, color) = category;
return Scaffold(
appBar: AppBar(title: const Text('BMI Calculator')),
body: ListView(
padding: const EdgeInsets.all(20),
children: [
SegmentedButton<Units>(
segments: const [
ButtonSegment(value: Units.metric, label: Text('Metric')),
ButtonSegment(value: Units.imperial, label: Text('Imperial')),
],
selected: {units},
onSelectionChanged: (s) => setState(() => units = s.first),
),
const SizedBox(height: 24),
Text('Height: $heightLabel', style: Theme.of(context).textTheme.titleMedium),
Slider(
min: 120,
max: 220,
value: heightCm,
onChanged: (v) => setState(() => heightCm = v),
),
Text('Weight: $weightLabel', style: Theme.of(context).textTheme.titleMedium),
Slider(
min: 30,
max: 150,
value: weightKg,
onChanged: (v) => setState(() => weightKg = v),
),
const SizedBox(height: 24),
Card(
color: color.withValues(alpha: 0.12),
child: Padding(
padding: const EdgeInsets.all(24),
child: Column(
children: [
const Text('Your BMI'),
Text(
bmi.toStringAsFixed(1),
style: TextStyle(fontSize: 56, fontWeight: FontWeight.bold, color: color),
),
Text(label, style: TextStyle(fontSize: 22, color: color)),
const SizedBox(height: 12),
const Text('Healthy range: 18.5 to 24.9', textAlign: TextAlign.center),
],
),
),
),
],
),
);
}
}
How it works
- State is just three values: the unit system, height and weight. Everything else (BMI, category, labels) is calculated from them in getters, so it can never be out of date.
- The category getter returns a record
(String, Color)from a switch expression with relational patterns (< 18.5). Read more about records. - Values are always stored in metric; the imperial setting only changes the labels. Storing one unit avoids rounding errors.
SegmentedButtonis the Material 3 way to pick one option from a few. See selection controls.
Make it yours
- Save the last height and weight with shared_preferences.
- Show a history of BMI values with dates in a
ListView. - Add an age field and show a short tip for each category.