Aplikasi BMI Berbasis Flutter

Aplikasi BMI Berbasis Flutter


Di era digital seperti sekarang, menjaga kesehatan bisa dibantu dengan teknologi. Salah satu cara sederhana untuk mengetahui kondisi tubuh adalah dengan menghitung Body Mass Index (BMI). Karena itu, saya membuat sebuah aplikasi BMI berbasis Flutter yang dapat dijalankan sebagai PWA (Progressive Web App).

Aplikasi ini tidak hanya menghitung BMI, tetapi juga memiliki tampilan modern dan fitur interaktif yang membuat pengalaman pengguna menjadi lebih menarik.


📌 Apa Itu BMI?

BMI (Body Mass Index) adalah metode sederhana untuk mengetahui apakah berat badan seseorang tergolong ideal, kurang, berlebih, atau obesitas berdasarkan tinggi dan berat badan.

Rumus BMI adalah:

BMI = Berat Badan (kg) / (Tinggi Badan (m) × Tinggi Badan (m))

Hasil perhitungan ini kemudian dikategorikan untuk mengetahui kondisi tubuh secara umum.


🚀 Tentang Aplikasi yang Dibuat

Aplikasi BMI ini dibuat menggunakan Flutter dan dirancang agar bisa berjalan di browser sebagai PWA, sehingga tidak perlu di-install dari Play Store.

Berikut beberapa fitur utama dari aplikasi ini:

🎨 1. Tema Warna Dinamis

Pengguna bisa mengganti warna tampilan aplikasi melalui menu di AppBar. Warna yang dipilih akan langsung mengubah tampilan latar belakang aplikasi.

👤 2. Pilihan Gender

Tersedia tombol pilihan gender dengan ikon visual agar pengguna dapat memilih antara pria atau wanita dengan lebih interaktif.

🎂 3. Input Usia

Aplikasi tidak hanya meminta tinggi dan berat badan, tetapi juga usia pengguna. Usia digunakan untuk memberikan pesan kesehatan yang lebih sesuai.

📊 4. Hasil BMI dengan Animasi

Hasil BMI ditampilkan dengan animasi angka yang bergerak dari 0 hingga nilai akhir, sehingga terlihat lebih modern dan menarik.

📈 5. Indikator Kategori BMI

Aplikasi menampilkan kategori BMI seperti:

  • Yang

  • Normal

  • Berlebih

  • Obesitas

Setiap kategori memiliki warna berbeda agar mudah dipahami secara visual.

🕘 6. Riwayat Perhitungan

Setiap hasil BMI yang dihitung akan disimpan sementara dalam riwayat, sehingga pengguna bisa melihat hasil sebelumnya.


KODE Dart Flutter yang saya pakai

import 'dart:ui';
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatefulWidget {
  const MyApp({super.key});

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  Color seedColor = Colors.blueAccent;

  void updateColor(Color color) {
    setState(() => seedColor = color);
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        useMaterial3: true,
        colorScheme: ColorScheme.fromSeed(seedColor: seedColor),
      ),
      home: HomePage(onColorChange: updateColor, themeColor: seedColor),
    );
  }
}

class HomePage extends StatefulWidget {
  final Function(Color) onColorChange;
  final Color themeColor;

  const HomePage({super.key, required this.onColorChange, required this.themeColor});

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> with SingleTickerProviderStateMixin {
  final ageController = TextEditingController();
  final heightController = TextEditingController();
  final weightController = TextEditingController();

  String gender = "Pria";
  double? bmi;
  double animatedBMI = 0;
  String message = "";
  Color resultColor = Colors.transparent;

  List<String> history = [];

  late AnimationController controller;
  late Animation<double> animation;

  @override
  void initState() {
    super.initState();
    controller = AnimationController(
      vsync: this,
      duration: const Duration(milliseconds: 800),
    );
  }

  void calculateBMI() {
    final h = double.tryParse(heightController.text);
    final w = double.tryParse(weightController.text);
    final age = int.tryParse(ageController.text);

    if (h == null || w == null || age == null || h <= 0 || w <= 0 || age <= 0) {
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(content: Text("Isi usia, tinggi & berat yang valid ya 👀")),
      );
      return;
    }

    final result = w / ((h / 100) * (h / 100));

    animation = Tween<double>(begin: 0, end: result).animate(
      CurvedAnimation(parent: controller, curve: Curves.easeOut),
    )
      ..addListener(() {
        setState(() {
          animatedBMI = animation.value;
        });
      });

    controller.forward(from: 0);

    setState(() {
      bmi = result;
      resultColor = getColor(result);
      message = getMessage(result, age);
      history.insert(0, "BMI ${result.toStringAsFixed(2)} • $gender • $age th");
    });
  }

  String getMessage(double bmi, int age) {
    String ageNote;

    if (age < 18) {
      ageNote = "Nutrisi penting di usia pertumbuhan 🍎";
    } else if (age < 40) {
      ageNote = "Jaga gaya hidup aktif ⚡";
    } else {
      ageNote = "Fokus kesehatan & metabolisme ❤️";
    }

    if (bmi < 18.5) return "Kurus 🌬️ $ageNote";
    if (bmi < 24.9) return "Ideal 🎯 $ageNote";
    if (bmi < 29.9) return "Berlebih 💪 $ageNote";
    return "Obesitas 🔥 $ageNote";
  }

  Color getColor(double bmi) {
    if (bmi < 18.5) return Colors.cyan;
    if (bmi < 24.9) return Colors.green;
    if (bmi < 29.9) return Colors.orange;
    return Colors.red;
  }

  Widget glassCard({required Widget child}) {
    return ClipRRect(
      borderRadius: BorderRadius.circular(25),
      child: BackdropFilter(
        filter: ImageFilter.blur(sigmaX: 12, sigmaY: 12),
        child: Container(
          padding: const EdgeInsets.all(20),
          decoration: BoxDecoration(
            color: Colors.white.withOpacity(0.15),
            borderRadius: BorderRadius.circular(25),
            border: Border.all(color: Colors.white.withOpacity(0.25)),
          ),
          child: child,
        ),
      ),
    );
  }

  Widget genderButton(String label, IconData icon, Color glow) {
    bool selected = gender == label;
    return Expanded(
      child: GestureDetector(
        onTap: () => setState(() => gender = label),
        child: AnimatedContainer(
          duration: const Duration(milliseconds: 300),
          margin: const EdgeInsets.symmetric(horizontal: 8),
          padding: const EdgeInsets.all(16),
          decoration: BoxDecoration(
            color: selected ? glow.withOpacity(0.25) : Colors.white.withOpacity(0.1),
            borderRadius: BorderRadius.circular(20),
            border: Border.all(color: selected ? glow : Colors.white24),
          ),
          child: Column(
            children: [
              Icon(icon, size: 38, color: selected ? glow : Colors.white),
              const SizedBox(height: 6),
              Text(label, style: TextStyle(color: selected ? glow : Colors.white)),
            ],
          ),
        ),
      ),
    );
  }

  InputDecoration inputStyle(String label, IconData icon) {
    return InputDecoration(
      labelText: label,
      labelStyle: const TextStyle(color: Colors.white),
      prefixIcon: Icon(icon, color: Colors.white),
      enabledBorder: OutlineInputBorder(
        borderRadius: BorderRadius.circular(15),
        borderSide: const BorderSide(color: Colors.white54),
      ),
      focusedBorder: OutlineInputBorder(
        borderRadius: BorderRadius.circular(15),
        borderSide: BorderSide(color: widget.themeColor),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: widget.themeColor.withOpacity(0.9),
      appBar: AppBar(
        backgroundColor: widget.themeColor,
        elevation: 0,
        centerTitle: true,
        title: const Text(
          "Be Healthy With BMI",
          style: TextStyle(fontWeight: FontWeight.bold, color: Colors.white),
        ),
        iconTheme: const IconThemeData(color: Colors.white),
        actions: [
          PopupMenuButton<Color>(
            icon: const Icon(Icons.palette_outlined, color: Colors.white),
            onSelected: widget.onColorChange,
            itemBuilder: (context) => const [
              PopupMenuItem(value: Colors.blueAccent, child: Text("Blue")),
              PopupMenuItem(value: Colors.pinkAccent, child: Text("Pink")),
              PopupMenuItem(value: Colors.greenAccent, child: Text("Green")),
              PopupMenuItem(value: Colors.deepPurpleAccent, child: Text("Purple")),
            ],
          )
        ],
      ),
      body: SingleChildScrollView(
        padding: const EdgeInsets.all(20),
        anak : Kolom (
          anak-anak : [
            Kartu kaca (
              anak : Kolom (
                anak-anak : [
                  const Text("Hitung BMI Kamu",
                      gaya : TextStyle ( ukuran font : 22 , warna : Colors . putih )),
                  const SizeBox ( tinggi : 20 ),

                  Baris (
                    anak-anak : [
                      genderButton ( "Pria" , Icons . male , Colors . white ),
                      genderButton ( "Wanita" , Icons . female , Colors . pink ),
                    ],
                  ),

                  const SizeBox ( tinggi : 20 ),

                  Kolom Teks (
                    pengontrol : pengontrol usia ,
                    keyboardType : TextInputType . number ,
                    gaya : const TextStyle ( warna : Colors . putih ),
                    decoration: inputStyle("Usia (tahun)", Icons.cake),
                  ),
                  const SizeBox ( tinggi : 15 ),
                  Kolom Teks (
                    pengontrol : pengontrol ketinggian ,
                    keyboardType : TextInputType . number ,
                    gaya : const TextStyle ( warna : Colors . putih ),
                    dekorasi : inputStyle ( "Tinggi (cm)" , Icons . height ),
                  ),
                  const SizeBox ( tinggi : 15 ),
                  Kolom Teks (
                    pengontrol : pengontrol berat ,
                    keyboardType : TextInputType . number ,
                    gaya : const TextStyle ( warna : Colors . putih ),
                    dekorasi : inputStyle ( "Berat (kg)" , Icons . monitor_weight ),
                  ),
                  const SizeBox ( tinggi : 20 ),

                  Tombol Elevated (
                    onPressed : calculateBMI ,
                    gaya : Tombol yang Ditinggikan . gayaDari (
                      backgroundColor : Colors . white ,
                      foregroundColor : widget . themeColor ,
                      padding : const EdgeInsets . symmetric ( horizontal : 40 , vertical : 15 ),
                      bentuk : RoundedRectangleBorder ( borderRadius : BorderRadius . circular ( 20 )),
                    ),
                    child: const Text("HITUNG BMI"),
                  ),

                  jika ( bmi != null ) ...[
                    const SizeBox ( tinggi : 25 ),
                    Teks ( animatedBMI.toStringAsFixed ( 2 ) ,
                        gaya : Gaya Teks (
Ukuran                             Huruf : 42
                            warna : resultColor ,
                            FontWeight : FontWeight . bold )),
                    const SizeBox ( tinggi : 10 ),
                    Indikator Kemajuan Linier (
                      nilai : ( bmi ! / 40 ). clamp ( 0 , 1 ),
                      minTinggi : 10
                      backgroundColor : Colors . white24 ,
                      warna : resultColor ,
                    ),
                    const SizeBox ( tinggi : 10 ),
                    Teks ( pesan ,
                        Perataan teks : Perataan Teks . tengah ,
                        gaya : const TextStyle ( warna : Colors . putih )),
                  ]
                ],
              ),
            ),

            jika ( history.isNotEmpty ) ... [
              const SizeBox ( tinggi : 25 ),
              Kartu kaca (
                anak : Kolom (
                  crossAxisAlignment : CrossAxisAlignment . start ,
                  anak-anak : [
                    const Text("Riwayat BMI",
                        gaya : Gaya Teks (
                            warna : Warna . putih ,
Ukuran                             Huruf : 18
                            FontWeight : FontWeight . bold )),
                    const SizeBox ( tinggi : 10 ),
                    ... sejarah . ambil ( 5 ). peta (( e ) => Teks (
                          Dan ,
                          gaya : const TextStyle ( warna : Colors . putih70 ),
                        ))
                  ],
                ),
              )
            ]
          ],
        ),
      ),
    );
  }
}


💡 Keunggulan Menggunakan Flutter PWA

Menggunakan Flutter berbasis PWA memiliki beberapa kelebihan:

  • Bisa dijalankan di HP maupun laptop melalui browser

  • Tidak perlu instalasi aplikasi

  • Tampilan tetap seperti aplikasi mobile

  • Ringan dan responsif

Ini membuat aplikasi BMI ini mudah diakses oleh siapa saja.


🎯 Kesimpulan

Aplikasi BMI berbasis Flutter PWA ini adalah contoh bagaimana teknologi bisa membantu memantau kesehatan secara sederhana namun efektif. Dengan fitur interaktif, desain modern, dan kemudahan akses, aplikasi ini dapat digunakan oleh siapa saja untuk mengecek kondisi tubuhnya secara cepat.

Ke depannya, aplikasi ini masih bisa dikembangkan dengan fitur tambahan seperti grafik perkembangan BMI dan penyimpanan data secara permanen.

LINK AKSES:  https://z5yi064x5yj0.zapp.page/#/

Komentar

Postingan populer dari blog ini

MEMBUAT TAMPILAN APLIKASI SEDERANA MENGGUNAKAN DART FLUTTER

Aplication Flutter With Image 7/31/2025