# BMI Calulator with Flutter

# 0. Final Product

  • input_page의 StatefulWidget에서 값을 touch/click과 slider로 인풋을 받고
  • results_page 에서 BMI 결과를 보여주는 어플리케이션.
  • 간단한 Navigator가 이용된다.
  • (성별과 나이는 BMI 연산에 사용되지 않지만 연습겸 추가된 기능이다)

image-20220127111950185

image-20220127112055209

  • 그렇지만 인바디 결과는 안 좋다...

# 1. BMI Brain

bmi_brain.dart

import 'dart:math';

class BMIBrain {
  final int weight;
  final double height;
  double _bmi;
  String _result;
  String _advice;

  BMIBrain(this.weight, this.height) {
    _bmi = weight / pow(height / 100, 2);
    if (_bmi >= 25) {
      _result = 'overweight';
      _advice = 'Exercise more!';
    } else if (_bmi >= 18.5) {
      _result = 'normal';
      _advice = 'Good job!';
    } else {
      _result = 'underweight';
      _advice = 'Eat More!';
    }
  }

  String calculateBMI() {
    return _bmi.toStringAsFixed(1);
  }

  String result() {
    return _result;
  }

  String advice() {
    return _advice;
  }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
  • BMIBrain 클래스에서는 입력된 값들을 바탕으로 bmi, result, advice를 리턴한다

# 2. Components

konstants.dart

import 'package:flutter/material.dart';

const kBoxTextStyle = TextStyle(fontSize: 18.0, color: Color(0xFF8d8e98));
const kLargeButtonTextStyle =
    TextStyle(fontSize: 20, color: Colors.white, fontWeight: FontWeight.bold);

const kBoxNumberStyle = TextStyle(
    fontSize: 50.0, fontWeight: FontWeight.w900, color: Color(0xFF000000));

const kTitleTextStyle = TextStyle(fontSize: 50, fontWeight: FontWeight.bold);
const kResultTextStyle = TextStyle(
    color: Color(0xFF24D876), fontSize: 22, fontWeight: FontWeight.bold);

const kBMITextStyle = TextStyle(fontSize: 100, fontWeight: FontWeight.bold);
const kBodyTextStyle = TextStyle(fontSize: 22);

const kBottomContainerHeight = 80.0;
const Color kActiveBoxColor = Color(0xFFABD9CF);
const Color kInactiveBoxColor = Color(0xFF89B5AF);
const Color kBottomColor = Color(0xffD0CAB2);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
  • 사용한 constants

components/bottom_bar.dart

import 'package:bmi_calculator/konstants.dart';
import 'package:flutter/material.dart';

class BottomBar extends StatelessWidget {
  final Function onTap;
  final String text;

  BottomBar(this.onTap, this.text);

  
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: onTap,
      child: Container(
          color: kBottomColor,
          margin: EdgeInsets.only(top: 10),
          width: double.infinity,
          height: kBottomContainerHeight,
          child: Center(
              child: Text(
            text,
            style: kLargeButtonTextStyle,
            textAlign: TextAlign.center,
          ))),
    );
  }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
  • 생성시에 onTap함수와 text를 전달해 만든다.

components/icon_content.dart

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

class CardChildColumn extends StatelessWidget {
  
  CardChildColumn(this.text, this.icon);

  final String text;
  final IconData icon;

  Widget build(BuildContext context) {
    return Column(mainAxisAlignment: MainAxisAlignment.center, children: [
      Icon(icon, size: 80.0),
      SizedBox(height: 15.0),
      Text(text, style: kBoxTextStyle)
    ]);
  }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

components/reusable_card

import 'package:flutter/material.dart';

class ReusableCard extends StatelessWidget {
  ReusableCard({ this.color, this.cardChild, this.onPress});
  final Color color;
  final Widget cardChild;
  final Function onPress;

  
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap:onPress,
      child: Container(
          child: cardChild,
          margin: EdgeInsets.all(15),
          decoration: BoxDecoration(
            borderRadius: BorderRadius.circular(10.0),
            color: color,
          )),
    );
  }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

components/round_icon_button.dart

import 'package:bmi_calculator/konstants.dart';
import 'package:flutter/material.dart';

class RoundIconButton extends StatelessWidget {
  final IconData icon;
  final Function onPressed;

  RoundIconButton(this.icon, this.onPressed);

  
  Widget build(BuildContext context) {
    return RawMaterialButton(
        elevation: 0.0,
        child: Icon(icon),
        onPressed: () {
          onPressed();
        },
        constraints: BoxConstraints.tightFor(width: 56, height: 56),
        shape: CircleBorder(),
        fillColor: Color(0XFF4C4F5E));
  }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 3. Pages

pages/input_page

import 'package:bmi_calculator/konstants.dart';
import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import '../components/reusable_card.dart';
import '../components/icon_content.dart';
import '../konstants.dart';
import '../components/bottom_bar.dart';
import '../components/round_icon_button.dart';
import 'package:bmi_calculator/bmi_brain.dart';

enum Gender { Male, Female }

class InputPage extends StatefulWidget {
  
  _InputPageState createState() => _InputPageState();
}

class _InputPageState extends State<InputPage> {
  Gender selectedGender;
  double _height = 170;
  int _weight = 70;
  int _age = 20;

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('BMI CALCULATOR'),
      ),
      body: Column(crossAxisAlignment: CrossAxisAlignment.stretch, children: [
        Expanded(
            child: Row(
          children: [
            Expanded(
                child: ReusableCard(
              color: selectedGender == Gender.Male
                  ? kActiveBoxColor
                  : kInactiveBoxColor,
              cardChild: CardChildColumn('MALE', FontAwesomeIcons.mars),
              onPress: () {
                setState(() {
                  selectedGender = Gender.Male;
                });
              },
            )),
            Expanded(
                child: ReusableCard(
                    color: selectedGender == Gender.Female
                        ? kActiveBoxColor
                        : kInactiveBoxColor,
                    cardChild:
                        CardChildColumn('FEMALE', FontAwesomeIcons.venus),
                    onPress: () {
                      setState(() {
                        selectedGender = Gender.Female;
                      });
                    }))
          ],
        )),
        Expanded(
            child: ReusableCard(
          color: kActiveBoxColor,
          cardChild: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Text('HEIGHT', style: kBoxTextStyle),
              Row(
                mainAxisAlignment: MainAxisAlignment.center,
                crossAxisAlignment: CrossAxisAlignment.baseline,
                textBaseline: TextBaseline.alphabetic,
                children: [
                  Text(_height.round().toString(), style: kBoxNumberStyle),
                  Text('cm', style: kBoxTextStyle)
                ],
              ),
              SliderTheme(
                data: SliderTheme.of(context).copyWith(
                    activeTrackColor: Colors.white,
                    inactiveTrackColor: Color(0XFF8D8E98),
                    thumbColor: Color(0XFFEB1555),
                    overlayColor: Color(0X292B1555),
                    thumbShape: RoundSliderThumbShape(enabledThumbRadius: 15),
                    overlayShape: RoundSliderOverlayShape(overlayRadius: 30)),
                child: Slider(
                  value: _height,
                  min: 120,
                  max: 220,
                  label: _height.round().toString(),
                  onChanged: (double value) {
                    setState(() {
                      _height = value;
                    });
                  },
                ),
              )
            ],
          ),
        )),
        Expanded(
            child: Row(
          children: [
            Expanded(
              child: ReusableCard(
                color: kActiveBoxColor,
                cardChild: Column(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: [
                    Text('WEIGHT', style: kBoxTextStyle),
                    Text(_weight.toString(), style: kBoxNumberStyle),
                    Row(
                      mainAxisAlignment: MainAxisAlignment.center,
                      children: [
                        RoundIconButton(FontAwesomeIcons.minus, () {
                          setState(() {
                            _weight -= 1;
                          });
                        }),
                        SizedBox(width: 20),
                        RoundIconButton(FontAwesomeIcons.plus, () {
                          setState(() {
                            _weight += 1;
                          });
                        })
                      ],
                    )
                  ],
                ),
              ),
            ),
            Expanded(
              child: ReusableCard(
                color: kActiveBoxColor,
                cardChild: Column(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: [
                    Text('AGE', style: kBoxTextStyle),
                    Text(_age.toString(), style: kBoxNumberStyle),
                    Row(
                      mainAxisAlignment: MainAxisAlignment.center,
                      children: [
                        RoundIconButton(FontAwesomeIcons.minus, () {
                          setState(() {
                            _age -= 1;
                          });
                        }),
                        SizedBox(width: 20),
                        RoundIconButton(FontAwesomeIcons.plus, () {
                          setState(() {
                            _age += 1;
                          });
                        })
                      ],
                    )
                  ],
                ),
              ),
            )
          ],
        )),
        BottomBar(() {
          BMIBrain bmiBrain = BMIBrain(_weight, _height);
          Navigator.pushNamed(context, '/result', arguments: bmiBrain);
        }, 'To Result'),
      ]),
    );
  }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168

results_page

import 'package:flutter/material.dart';
import '../components/reusable_card.dart';
import '../components/bottom_bar.dart';
import '../konstants.dart';
import '../bmi_brain.dart';

class ResultsPage extends StatelessWidget {
  
  Widget build(BuildContext context) {
    final BMIBrain _brain = ModalRoute.of(context).settings.arguments;
    return Scaffold(
        appBar: AppBar(
          title: Text('BMI CALCULATOR!'),
        ),
        body: Column(
          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            Expanded(
                child: Container(
                    padding: EdgeInsets.all(10),
                    alignment: Alignment.bottomCenter,
                    child: Text(
                      'Your Result',
                      style: kTitleTextStyle,
                    ))),
            Expanded(
              flex: 5,
              child: Container(
                child: ReusableCard(
                  color: kActiveBoxColor,
                  cardChild: Column(
                    mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                    crossAxisAlignment: CrossAxisAlignment.center,
                    children: [
                      Text(_brain.result(), style: kResultTextStyle),
                      Text(_brain.calculateBMI(), style: kBMITextStyle),
                      Text(_brain.advice(), style: kBodyTextStyle)
                    ],
                  ),
                ),
              ),
            ),
            BottomBar(() {
              Navigator.pushNamed(context, '/');
            }, 'Retry')
          ],
        ));
  }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51

# 4. Main

main.dart

import 'package:flutter/material.dart';
import 'pages/input_page.dart';
import 'pages/results_page.dart';

void main() => runApp(BMICalculator());

class BMICalculator extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData().copyWith(
        colorScheme: ThemeData().colorScheme.copyWith(
            primary: Color(0xff96C7C1),
            secondary: Color(0xff96C7C1),
            background: Color(0xffD0CAB2)),
        scaffoldBackgroundColor: Color(0xffDED9C4),
      ),
      routes: {
        '/': (context) => InputPage(),
        '/result': (context) => ResultsPage()
      },
      initialRoute: '/',
    );
  }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
  • namedRoute를 사용했다.
  • Themedata().colorScheme.copyWith 를 이용해 커스터마이징을 해줬다.