Flutter Beginners’ Guide: Crafting Your First BMI Calculator App |Part 2
It’s time to elevate our BMI calculator by enhancing the UI and adding exception handling. In this installment, we’ll focus primarily on UI modifications and incorporating neumorphic design elements.
To start, we’ll define a neumorphic decoration object that will be used in multiple places throughout our app. If you missed Part 1 of this tutorial, you can find it here.
class _HomePageState extends State<HomePage> {
double? bmiValue;
TextEditingController heightController = TextEditingController();
TextEditingController weightController = TextEditingController();
....
final BoxDecoration boxDecoration = BoxDecoration(
color: Colors.grey[900],
borderRadius: const BorderRadius.all(Radius.circular(10)),
boxShadow: const [
BoxShadow(color: Colors.black38, offset: Offset(5, 5), blurRadius: 10),
BoxShadow(color: Colors.white12, offset: Offset(-5, -5), blurRadius: 10)
]);
....
}
We’ve established a decoration object featuring a dark grey color scheme, border radius of 10, and two shadow variants — light and dark. This neumorphic design element will serve as a foundational component in enhancing the visual appeal of our BMI calculator app.
Input Ui Modification
Widget buildInputUi() {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
height: 50,
decoration: boxDecoration,
child: TextField(
cursorColor: Colors.white60,
style: const TextStyle(color: Colors.white70),
controller: heightController,
decoration: const InputDecoration(
border: OutlineInputBorder(borderSide: BorderSide.none),
hintText: "Height in cm",
hintStyle: TextStyle(color: Colors.white24)),
),
),
const SizedBox(
height: 50,
),
Container(
height: 50,
decoration: boxDecoration,
child: TextField(
cursorColor: Colors.white60,
style: const TextStyle(color: Colors.white70),
controller: weightController,
decoration: const InputDecoration(
hintText: 'Weight in Kg',
border: OutlineInputBorder(borderSide: BorderSide.none),
hintStyle: TextStyle(color: Colors.white24)),
),
),
const SizedBox(
height: 50,
),
Container(
decoration: boxDecoration,
child: MaterialButton(
height: 60,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(10)),
),
minWidth: double.infinity,
onPressed: calculateBMI,
child: Text(
"CALCULATE BMI",
style: TextStyle(
color: Colors.green[200],
fontSize: 16,
fontWeight: FontWeight.bold),
),
),
)
],
);
}
We’ve wrapped each text field and button with a Container
and applied the decoration we created. For the text fields, we've added border: OutlineInputBorder(borderSide: BorderSide.none)
to remove the border, and also applied text styles for the text and hint text.
Output UI Modification
To display the BMI value, we’ll utilize the percent_indicator
package(Link : https://pub.dev/packages/percent_indicator). You can add this package to our project using the following command
flutter pub add percent_indicator
Next, we’ll integrate a LinearPercentIndicator
from the percent_indicator
package. Since we're not displaying a percentage but rather the BMI range (18.5–30), we perform a small calculation to convert it accordingly. The LinearPercentIndicator
accepts values between 0 and 1 only, so we calculate the value as follows: ((indicatorValue - 18.5) * 8.70 / 100). Additionally, to ensure that the indicator value falls within the BMI range of 18.5 to 30, we limit its value accordingly:
The rest of the implementation is similar to the input UI, with the addition of using a gradient to indicate the safe range of BMI.
Widget buildOutputUi() {
double indicatorValue = bmiValue!;
if (indicatorValue >= 30.0) {
indicatorValue = 29.99;
} else if (indicatorValue <= 18.5) {
indicatorValue = 18.6;
}
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
width: double.infinity,
height: 50,
alignment: Alignment.center,
decoration: boxDecoration,
child: Text(
"BMI : ${bmiValue!.toStringAsFixed(1)}",
style: TextStyle(color: Colors.white70, fontSize: 28),
),
),
const SizedBox(
height: 25,
),
Container(
height: 50,
decoration: boxDecoration,
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
gradient: LinearGradient(colors: [
Colors.lightBlue.withOpacity(0.75),
Colors.green.withOpacity(0.75),
Colors.yellow.withOpacity(0.75),
Colors.red.withOpacity(0.75)
])),
child: LinearPercentIndicator(
barRadius: Radius.circular(10),
lineHeight: 12,
linearGradient:
LinearGradient(colors: [Colors.purple, Colors.purple[300]!]),
animation: true,
percent: ((indicatorValue - 18.5) * 8.70 / 100),
),
),
),
const SizedBox(
height: 50,
),
Container(
decoration: boxDecoration,
child: MaterialButton(
height: 60,
shape: const RoundedRectangleBorder(
borderRadius: const BorderRadius.all(Radius.circular(10)),
),
minWidth: double.infinity,
onPressed: checkAnother,
child: Text(
"CHECK ANOTHER",
style: TextStyle(
color: Colors.green[200],
fontSize: 16,
fontWeight: FontWeight.bold),
),
),
)
],
);
}
calculateBMI()
In the calculateBMI()
function, the issue lies in the absence of exception handling. If the user inputs an invalid value, it may lead to an error and app crash. To address this, we'll add a try-catch block to handle errors. In the catch section, we'll implement a showDialog
to inform the user about the error gracefully
void calculateBMI() {
//bmi = Formula: weight (kg) / [height (m)]^2
try {
double h = double.tryParse(heightController.text)! / 100;
double w = double.tryParse(weightController.text)!;
setState(() {
bmiValue = w / (h * h);
});
} catch (e) {
showDialog(
context: context,
builder: (_) {
return Dialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10)),
backgroundColor: Colors.grey[900],
child: Container(
padding: EdgeInsets.all(25),
child: const Text(
"Invalid values for height or weight",
style: TextStyle(color: Colors.redAccent, fontSize: 18),
)),
);
});
}
}
build()
In the modified build
method, we've improved the visual layout of our BMI calculator app. The Scaffold
sets up the basic screen structure, while SafeArea
ensures content is within the safe area. A Column
organizes UI elements vertically, including a prominent title. The Container
dynamically renders either input or output UI based on bmiValue
, enhancing user interaction.
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: const Color.fromRGBO(33, 33, 33, 1),
body: SafeArea(
child: SizedBox(
width: double.infinity,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const Text(
"BMI CALCULATOR",
style: TextStyle(
color: Colors.white,
fontSize: 42,
),
),
Container(
width: 360,
alignment: Alignment.center,
padding: const EdgeInsets.all(8.0),
child: bmiValue == null ? buildInputUi() : buildOutputUi(),
),
]),
)),
);
}
As you conclude this tutorial, remember that this is just the beginning of your journey in Flutter development. While we’ve made significant improvements to our BMI calculator app, there’s still much more to explore and learn. Keep pushing your boundaries, experimenting with new concepts, and expanding your skills. This tutorial serves as a stepping stone towards your goals, and there’s a vast world of possibilities waiting for you to explore.
Source code : https://github.com/AbhijithKonnayil/bmi_calculator
you can access the tutorial in the part-2
branch.
If you found this tutorial helpful and enjoyed building your first BMI calculator app with Flutter, please consider giving it an applause on Medium and starring the repository on GitHub. Your support encourages me to create more content to assist you on your Flutter development journey. Thank you for your support, and happy coding!