Peter Moran
Peter Moran

Reputation: 23

Passing data from custom widget textfield to a calculator flutter

I am trying to pass data from a custom widget that contains a textfield to a calculator widget. The problem I am facing is that I am hoping to utilize my custom widget to create multiple inputs that can go to the calculator (i.e. height and weight). Can anyone assist with passing the data using a custom widget?

Custom Textfield Widget created

import 'package:auto_size_text/auto_size_text.dart';

enum Units { unit1, unit2 }

class InputRow extends StatefulWidget {
  InputRow({this.inputParameter, this.unit1, this.unit2});
  final String inputParameter;
  final String unit1;
  final String unit2;

  @override
  _InputRowState createState() => _InputRowState();
}

class _InputRowState extends State<InputRow> {
  String newTaskTitle;
  Units selectedUnit;
  String unit;

  @override
  void initState() {
    super.initState();
    setState(() {
      unit = widget.unit1;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      constraints: BoxConstraints(maxWidth: 375, maxHeight: 50),
      child: Row(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          Container(
            child: AutoSizeText(
              widget.inputParameter,
              textAlign: TextAlign.center,
              style: TextStyle(
                fontSize: 20.0,
              ),
            ),
          ),
          Expanded(
            child: Container(
              decoration: BoxDecoration(
                border: Border.all(
                  color: Colors.red,
                  width: 3,
                ),
                borderRadius: BorderRadius.only(
                  topLeft: Radius.circular(10),
                  bottomLeft: Radius.circular(10),
                ),
              ),
              child: TextField(
                autofocus: true,
                textAlign: TextAlign.center,
                onChanged: (newText) {
                  newTaskTitle = newText;
                },
              ),
            ),
          ),
          Container(
            decoration: BoxDecoration(
              color: Colors.red,
              border: Border.all(
                color: Colors.red,
                width: 3,
              ),
              borderRadius: BorderRadius.only(
                topRight: Radius.circular(10),
                bottomRight: Radius.circular(10),
              ),
            ),
            child: Row(
              children: <Widget>[
                Container(
                  padding: EdgeInsets.all(5),
                  child: Center(
                      child: AutoSizeText(
                    unit,
                    style: TextStyle(fontSize: 20, fontWeight: FontWeight.w500),
                  )),
                ),
                Container(
                    constraints: BoxConstraints(maxHeight: 50, maxWidth: 60),
                    child: FlatButton(
                      highlightColor: Colors.transparent,
                      child: Column(
                        crossAxisAlignment: CrossAxisAlignment.center,
                        mainAxisAlignment: MainAxisAlignment.center,
                        children: <Widget>[
                          Icon(
                            Icons.loop,
                            size: 25,
                          ),
                        ],
                      ),
                      onPressed: () {
                        setState(() {
                          selectedUnit = selectedUnit == Units.unit2
                              ? Units.unit1
                              : Units.unit2;
                          if (selectedUnit == Units.unit1) {
                            unit = widget.unit1;
                          } else {
                            unit = widget.unit2;
                          }
                        });
                      },
                    )),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

Screen calling widgets and hopefully passing the height and weight entered in the text field to the calculator




class InputScreen extends StatefulWidget {
  static const String id = 'adjustments';
  @override
  _InputScreenState createState() =>
      _AdjustmentInputScreenState();
}

class AdjustmentInputScreenState
    extends State<AdjustmentInputScreen> {

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: kActiveButtonColor,
      body: Column(
        children: <Widget>[
          AppBar(
            leading: null,
            actions: <Widget>[
              IconButton(
                  icon: Icon(Icons.close),
                  onPressed: () {
                    Navigator.pop(context);
                  }),
            ],
            title: Text('Dose Adjustment'),
            backgroundColor: Colors.transparent,
            elevation: 0.0,
          ),
          InputRow(
            unit1: 'cm',
            unit2: 'inches',
            inputParameter: 'height',
          ),
          InputRow(unit1: 'lbs', unit2: 'kg', inputParameter: 'weight',),
          RoundedButton(
            title: 'Calculate',
            onPressed: () {
//- code needed to pass the custom textfield widget data
            },
          ),
        ],
      ),
    );
  }
}

CALCULATOR BRAIN

import 'dart:math';

class CalculatorTest {
  CalculatorTest({this.height, this.weight, this.heightUnit, this.weightUnit});

  double height;
  double weight;
  final String heightUnit;
  final String weightUnit;

  double _bmi;

  String calculateBMI() {
    if (weightUnit == 'lbs') {
      weight = weight / 2.2;
    } else {
      weight = weight;
    }

    if (heightUnit == 'inches') {
      height = height / 2.53;
    } else {
      height = height;
    }

    _bmi = weight / pow(height / 100, 2);
    return _bmi.toStringAsFixed(1);
  }
}

Round 3

Goal: To have the ability to select one of three buttons, the button selected will be a different color (as Button2 is below), and then I can print the title of the button (i.e. Button2) when I click the calculate button.

Example of Button2 selected

Currently, everything works except what is printed. I can only get information about Button1 (if selected.option is used I get "Option.one" and if selected.title is used I get "Button1") despite what button is actually selected

MyButton code

class MyButton extends ValueNotifier<Option> {
  final String _title1;
  final String _title2;
  final String _title3;

  MyButton(
      {Option option = Option.one,
      String title1 = 'A',
      String title2 = 'B',
      String title3 = 'C'})
      : _title1 = title1,
        _title2 = title2,
        _title3 = title3,
        super(option);

  //You can add a get method to retrieve the title based on the option selected with a switch
  String get title {
    switch (value) {
      case Option.one:
        return _title1;
      case Option.two:
        return _title2;
      case Option.three:
        return _title3;
      default:
        return _title1; //or a default String, but to be honest this will never be used
    }
  }

  Option get option => value;
  set option(Option newOption) => value = newOption;
}

TriButton Code

enum Option {
  one,
  two,
  three,
}

class TriButton extends StatefulWidget {
  TriButton(
      {this.title1, this.title2, this.title3, this.triWidth, this.myButton});

  final String title1;
  final String title2;
  final String title3;
  final Constraints triWidth;
  final MyButton myButton;

  @override
  _TriButtonState createState() => _TriButtonState();
}

class _TriButtonState extends State<TriButton> {
  Option selectedOption;

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Container(
        constraints: widget.triWidth,
        child: Row(
          crossAxisAlignment: CrossAxisAlignment.center,
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Expanded(
              child: RectButton(
                buttonChild: Text(
                  widget.title1,
                  style: TextStyle(color: Colors.white),
                ),
                onPress: () {
                  setState(() {
                    selectedOption = Option.one;
                  });
                },
                bgColor: selectedOption == Option.one
                    ? kActiveButtonColor
                    : kInactiveButtonColor,
              ),
            ),
            Expanded(
              child: RectButton(
                buttonChild: Text(
                  widget.title2,
                  style: TextStyle(color: Colors.white),
                ),
                onPress: () {
                  setState(() {
                    selectedOption = Option.two;
                  });
                },
                bgColor: selectedOption == Option.two
                    ? kActiveButtonColor
                    : kInactiveButtonColor,
              ),
            ),
            Expanded(
              child: RectButton(
                buttonChild: Text(
                  widget.title3,
                  style: TextStyle(color: Colors.white),
                ),
                onPress: () {
                  setState(() {
                    selectedOption = Option.three;
                  });
                },
                bgColor: selectedOption == Option.three
                    ? kActiveButtonColor
                    : kInactiveButtonColor,
              ),
            ),
          ],
        ),
      ),
    );
  }
}

InputScreen

class InputScreen extends StatefulWidget {
  static const String id = 'adjustments';

  @override
  _InputScreenState createState() =>
      _InputScreenState();
}

class _InputScreenState
    extends State<InputScreen> {
  final TextEditingController weightController = TextEditingController();
  final TextEditingController heightController = TextEditingController();
  final TextEditingController creatController = TextEditingController();
  final MyUnit heightUnit = MyUnit();
  final MyUnit weightUnit = MyUnit(imperial: 'lbs', metric: 'kg');
  final MyUnit creatUnit = MyUnit(imperial: 'mg/dL', metric: 'mg/dL');
  final MyButton selected = MyButton();

  @override
  void dispose() {
    super.dispose();
    weightController.dispose();
    heightController.dispose();
    creatController.dispose();
    heightUnit.dispose();
    weightUnit.dispose();
    selected.dispose();
  }

  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Color(0xff142651),
      body: Column(
        children: <Widget>[
          AppBar(
            leading: null,
            actions: <Widget>[
              IconButton(
                  icon: Icon(Icons.close),
                  onPressed: () {
                    Navigator.pop(context);
                  }),
            ],
            title: Text('Dose Adjustment'),
            backgroundColor: Colors.transparent,
            elevation: 0.0,
          ),
          ValueListenableBuilder<Option>(
            valueListenable: selectedAbx,
            builder: (context, option, _) => TriButton(
              title1: 'Button 1',
              title2: 'Button 2',
              title3: 'Button 3',
            ),
          ),
          InputRow(
            myUnit: heightUnit,
            inputParameter: 'height',
            textField: heightController,
            colour: kOrangePantone,
          ),
          InputRow(
            myUnit: weightUnit,
            inputParameter: 'weight',
            textField: weightController,
            colour: kRoyalPurple,
          ),
          InputRow(
            myUnit: creatUnit,
            inputParameter: 'SCr',
            textField: creatController,
            colour: kDogwoodRose,
          ),
          RoundedButton(
            title: 'Calculate',
            onPressed: () {
              print(selected.option);
              String inputHeight = heightController.text;
              String inputWeight = weightController.text;
              String inputCreat = creatController.text;

              double imperialHeight = double.parse(inputHeight) * 2.54;
              double metricHeight = double.parse(inputHeight);
              double imperialWeight = double.parse(inputWeight) / 2.2;
              double metricWeight = double.parse(inputWeight);

              double creat = double.parse(inputCreat);

              CalculatorTest calc;
              if (heightUnit.unitType == 'cm' && weightUnit.unitType == 'kg') {
                calc = CalculatorTest(
                    height: metricHeight,
                    weight: metricWeight,
                    creatinine: creat);
              } else if (heightUnit.unitType == 'inches' &&
                  weightUnit.unitType == 'lbs') {
                calc = CalculatorTest(
                    height: imperialHeight,
                    weight: imperialWeight,
                    creatinine: creat);
              } else if (heightUnit.unitType == 'cm' &&
                  weightUnit.unitType == 'lbs') {
                calc = CalculatorTest(
                    height: metricHeight,
                    weight: imperialWeight,
                    creatinine: creat);
              } else {
                heightUnit.unitType == 'inches' && weightUnit.unitType == 'kg';
                calc = CalculatorTest(
                    height: imperialHeight,
                    weight: metricWeight,
                    creatinine: creat);
              }
              ;

              Navigator.push(
                context,
                MaterialPageRoute(
                  builder: (context) => ResultsScreen(
                    bmiResult: calc.calculate(),
                  ),
                ),
              );
            },
          ),
        ],
      ),
    );
  }
}

Upvotes: 0

Views: 2588

Answers (5)

EdwynZN
EdwynZN

Reputation: 5601

RoundedButton(
    title: 'Calculate',
    onPressed: () {
      String inputHeight = heightController.text;
      String inputWeight = weightController.text;
      String inputCreat = creatController.text;
      String inputAge = ageController.text;

      double creat = double.parse(inputCreat);
      double age = double.parse(inputAge);

      print(weight);
      print(idealWeight);
      print(adjustWeight);


      /// Create a factory constructor to help you do the math before creating the Calculator
      Calculator calc = Calculator.idealCalculate(
        height: double.parse(inputHeight),
        weight: double.parse(inputWeight),
        creatinine: double.parse(inputCreat),
        age: double.parse(inputAge),
        isFemale: selected.title == 'Female',
        isMetricHeight: heightUnit.unitType == 'cm',
        isMetricWeight: weightUnit.unitType == 'cm'
      );

      Navigator.push(
         context,
         MaterialPageRoute(
           builder: (context) => ResultsScreen(
             Result: calc.calculate(),
             idealResult: calc.calculateIdeal(),
             adjustResult: calc.calculateAdjust(),
           ),
         ),
      );
   },
),

And then in your Calculate model just create a factory constructor like this

factory Calculator.idealCalculate({
      double height,
      double weight,
      double creatinine,
      double age,
      bool isFemale = true,
      bool isMetricHeight = true,
      bool isMetricWeight = true,
    }){
    double myHeight = isMetricHeight ? height : height * 2.54;
    double myWeight = isMetricWeight ? weight : weight / 2.2;
    double imperialHeight = isMetricHeight ? myHeight / 2.54 : height;
    double multiplier;
    double idealWeight;
    double adjustWeight;
    if(isFemale){
      multiplier = 0.85;
      idealWeight = 45 + 2.3 * (imperialHeight - 60);
    }
    else{
      multiplier = 1.0;
      idealWeight = 50 + 2.3 * (imperialHeight - 60);
    }
    adjustWeight = (myWeight - idealWeight) * 0.4 + idealWeight;
    return Calculator(
      height: myHeight,
      weight: myWeight,
      creatinine: creatinine,
      age: age,
      genderMultiplier: multiplier,
      ideal: idealWeight,
      adjust: adjustWeight,
    );
  }

Also I would recommend check different state management if you want to keep a single instance of a Calculator model across your app (Redux, Provider, Bloc, etc) and adding setters or methods to change the values you want on the fly

Example

import 'package:flutter/material.dart';

class Calculator {
  Calculator({
    this.height,
    this.weight,
    this.creatinine,
    this.age,
    this.genderMultiplier,
    this.ideal,
    this.adjust,
  });

  double height;
  double weight;
  double creatinine;
  double age;
  double genderMultiplier;
  double ideal;
  double adjust;
  String heightUnit;

  double _crcl;
  double _idealCrCL;
  double _adjustCrCL;
  
  factory Calculator.idealCalculate({
      double height,
      double weight,
      double creatinine,
      double age,
      bool isFemale = true,
      bool isMetricHeight = true,
      bool isMetricWeight = true,
    }){
    double myHeight = isMetricHeight ? height : height * 2.54;
    double myWeight = isMetricWeight ? weight : weight / 2.2;
    double imperialHeight = isMetricHeight ? myHeight / 2.54 : height;
    double multiplier;
    double idealWeight;
    double adjustWeight;
    if(isFemale){
      multiplier = 0.85;
      idealWeight = 45 + 2.3 * (imperialHeight - 60);
    }
    else{
      multiplier = 1.0;
      idealWeight = 50 + 2.3 * (imperialHeight - 60);
    }
    adjustWeight = (myWeight - idealWeight) * 0.4 + idealWeight;
    return Calculator(
      height: myHeight,
      weight: myWeight,
      creatinine: creatinine,
      age: age,
      genderMultiplier: multiplier,
      ideal: idealWeight,
      adjust: adjustWeight,
    );
  }
  
  
  set idealWeight(String title) {
    bool isFemale = title == 'Female';
    double imperialHeight = heightUnit == 'cm' ? height / 2.54 : height;
    if(isFemale){
      genderMultiplier = 0.85;
      ideal = 45 + 2.3 * (imperialHeight - 60);
    }
    else{
      genderMultiplier = 1.0;
      ideal = 50 + 2.3 * (imperialHeight - 60);
    }
    adjust = (weight - ideal) * 0.4 + ideal;
  }


  String calculate() {
    _crcl = (((140 - age) * weight) / (72 * creatinine)) * genderMultiplier;
    return _crcl.toStringAsFixed(1);
  }

  String calculateIdeal() {
    _idealCrCL = (((140 - age) * ideal) / (72 * creatinine)) * genderMultiplier;
    return _idealCrCL.toStringAsFixed(1);
  }

  String calculateAdjust() {
    _adjustCrCL = weight / ideal >= 1.4
        ? (((140 - age) * adjust) / (72 * creatinine)) * genderMultiplier
        : _idealCrCL;
    return _adjustCrCL.toStringAsFixed(1);
  }
}

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Center(
          child: MyWidget(),
        ),
      ),
    );
  }
}

class MyWidget extends StatelessWidget {
  
  final Calculator calc = Calculator.idealCalculate(
    age: 24,
    creatinine: 152,
    height: 162,
    weight: 64
  );
  
  
  @override
  Widget build(BuildContext context) {
    return Text(calc.calculate(), style: Theme.of(context).textTheme.headline4);
  }
}

Upvotes: 0

Peter Moran
Peter Moran

Reputation: 23

Input Screen

class InputScreen extends StatefulWidget {
  static const String id = 'Input';
  @override
  _InputScreenState createState() =>
      _InputScreenState();
}

class _InputScreenState
    extends State<InputScreen> {
  final TextEditingController weightController = TextEditingController();
  final TextEditingController heightController = TextEditingController();
  final TextEditingController creatController = TextEditingController();
  final TextEditingController ageController = TextEditingController();
  final MyUnit heightUnit = MyUnit();
  final MyUnit weightUnit = MyUnit(imperial: 'lbs', metric: 'kg');
  final MyUnit creatUnit = MyUnit(imperial: 'mg/dL', metric: 'mg/dL');
  final MyUnit ageUnit = MyUnit(imperial: 'years', metric: 'years');
  final MyButton selected = MyButton(title3: 'Female', title4: 'Male');

  @override
  void dispose() {
    super.dispose();
    weightController.dispose();
    heightController.dispose();
    creatController.dispose();
    heightUnit.dispose();
    weightUnit.dispose();
    ageUnit.dispose();
    selected.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.black,
      body: Column(
        children: <Widget>[
          ClipPath(
            clipper: MyClipper(),
            child: Container(
              height: 250,
              width: double.infinity,
              decoration: BoxDecoration(
                gradient: kHeaderGradient,
                image: DecorationImage(
                  image: AssetImage('images/virus.png'),
                ),
              ),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.center,
                mainAxisAlignment: MainAxisAlignment.center,
                children: <Widget>[
                  AppBar(
                    leading: null,
                    actions: <Widget>[
                      IconButton(
                          icon: Icon(Icons.close),
                          onPressed: () {
                            Navigator.pop(context);
                          }),
                    ],
                    title: Text(
                      'Creatinine Clearance',
                      style: kHeaderTextStyle,
                    ),
                    backgroundColor: Colors.transparent,
                    elevation: 0.0,
                  ),
                ],
              ),
            ),
          ),
          Column(
            crossAxisAlignment: CrossAxisAlignment.stretch,
            mainAxisAlignment: MainAxisAlignment.spaceEvenly,
            children: <Widget>[
              ValueListenableBuilder<Option>(
                valueListenable: selected,
                builder: (context, option, _) => MakeButtons(
                  num0: 3,
                  num1: 5,
                  makeButtonWidth: MediaQuery.of(context).size.width * 0.45,
                  selected: option,
                  onChanged: (newOption) => selected.option = newOption,
                ),
              ),
              InputRow(
                myUnit: heightUnit,
                inputParameter: 'height',
                textField: heightController,
                colour: kOrangePantone,
              ),
              InputRow(
                myUnit: weightUnit,
                inputParameter: 'weight',
                textField: weightController,
                colour: kRoyalPurple,
              ),
              InputRow(
                myUnit: creatUnit,
                inputParameter: 'SCr',
                textField: creatController,
                colour: kDogwoodRose,
              ),
              InputRow(
                myUnit: ageUnit,
                inputParameter: 'Age',
                textField: ageController,
                colour: kDogwoodRose,
              ),
              RoundedButton(
                title: 'Calculate',
                onPressed: () {
                  String inputHeight = heightController.text;
                  String inputWeight = weightController.text;
                  String inputCreat = creatController.text;
                  String inputAge = ageController.text;

                  double imperialHeight = double.parse(inputHeight) * 2.54;
                  double metricHeight = double.parse(inputHeight);
                  double imperialWeight = double.parse(inputWeight) / 2.2;
                  double metricWeight = double.parse(inputWeight);

                  double creat = double.parse(inputCreat);
                  double age = double.parse(inputAge);


                  double multiplier = selected.title == 'Female' ? 0.85 : 1.0; //- code I am trying to have performed on my calculator model //
                  double height = heightUnit.unitType == 'cm'
                      ? metricHeight
                      : imperialHeight;
                  double weight = weightUnit.unitType == 'cm'
                      ? metricWeight
                      : imperialWeight;


                  double idealWeight = selected.title == 'Female'//- Code I am trying to perform on my calculator model
                      ? (45 +
                          2.3 *
                              (heightUnit.unitType == 'cm'
                                  ? ((double.parse(inputHeight) - 152.4) / 2.54)
                                  : (double.parse(inputHeight) - 60)))
                      : (50 +
                          2.3 *
                              (heightUnit.unitType == 'cm'
                                  ? ((double.parse(inputHeight) - 152.4) / 2.54)
                                  : (double.parse(inputHeight) - 60)));

                  double adjustWeight = (weightUnit.unitType == 'kg'
                      ? (double.parse(inputWeight) - idealWeight) * 0.4 +
                          idealWeight
                      : ((double.parse(inputWeight) / 2.2) - idealWeight) *
                              0.4 +
                          idealWeight);

                  print(weight);
                  print(idealWeight);
                  print(adjustWeight);

                  Calculator calc;
                  calc = Calculator(
                    height: height,
                    weight: weight,
                    creatinine: creat,
                    age: age,

//- right now I can only pass the data this way. If I try to do math in my calculator model, I keep getting the else result of my if statements because no value is passed before the code is run
                    genderMultiplier: multiplier,
                    ideal: idealWeight,
                    adjust: adjustWeight,
                  );

                  Navigator.push(
                    context,
                    MaterialPageRoute(
                      builder: (context) => ResultsScreen(
                        Result: calc.calculate(),
                        idealResult: calc.calculateIdeal(),
                        adjustResult: calc.calculateAdjust(),
                      ),
                    ),
                  );
                },
              ),
            ],
          ),
        ],
      ),
    );
  }
}

class MyClipper extends CustomClipper<Path> {
  @override
  Path getClip(Size size) {
    var path = Path();
    path.lineTo(0, size.height - 80);
    path.quadraticBezierTo(
        size.width / 2, size.height, size.width, size.height - 80);
    path.lineTo(size.width, 0);
    path.close();
    return path;
  }

  @override
  bool shouldReclip(CustomClipper<Path> oldClipper) {
    return false;
  }
}

Calculator

class Calculator {
  Calculator({
    height,
    weight,
    creatinine,
    age,
    genderMultiplier,
    ideal,
    adjust,
  });

  double _crcl;
  double _idealCrCL;
  double _adjustCrCL;
  
  
  factory Calculator.idealCalculate({
    double height,
    double weight,
    double creatinine,
    double age,
    bool isFemale = true,
    bool isMetricHeight = true,
    bool isMetricWeight = true,
  }) {
    double myHeight = isMetricHeight ? height : height * 2.54;
    double myWeight = isMetricWeight ? weight : weight / 2.2;
    double imperialHeight = isMetricHeight ? myHeight / 2.54 : height;
    double multiplier;
    double idealWeight;
    double adjustWeight;
    if (isFemale) {
      multiplier = 0.85;
      idealWeight = 45 + 2.3 * (imperialHeight - 60);
    } else {
      multiplier = 1.0;
      idealWeight = 50 + 2.3 * (imperialHeight - 60);
    }
    adjustWeight = (myWeight - idealWeight) * 0.4 + idealWeight;
    return Calculator(
      height: myHeight,
      weight: myWeight,
      creatinine: creatinine,
      age: age,
      genderMultiplier: multiplier,
      ideal: idealWeight,
      adjust: adjustWeight,
      
    );
  }

  String calculate() {
    _crcl = (((140 - age) * weight) / (72 * creatinine)) * genderMultiplier;
    return _crcl.toStringAsFixed(1);
  }

  String calculateIdeal() {
    _idealCrCL = (((140 - age) * ideal) / (72 * creatinine)) * genderMultiplier;
    return _idealCrCL.toStringAsFixed(1);
  }

  String calculateAdjust() {
    _adjustCrCL = weight / ideal >= 1.4
        ? (((140 - age) * adjust) / (72 * creatinine)) * genderMultiplier
        : _idealCrCL;
    return _adjustCrCL.toStringAsFixed(1);
  }
}

Results Screen

class ResultsScreen extends StatelessWidget {
  static const String id = 'results';
  ResultsScreen({
    @required this.Result,
    this.idealResult,
    this.adjustResult,
  });

  final String Result;
  final String idealResult;
  final String adjustResult;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('BMI CALCULATOR'),
      ),
      body: Column(
        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: <Widget>[
          Container(
            padding: EdgeInsets.all(15),
            alignment: Alignment.bottomLeft,
            child: Text(
              'Your Result',
            ),
          ),
          ReuseableCard(
            bgColor: kGreyBackgroundColor,
            cardChild: Column(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              crossAxisAlignment: CrossAxisAlignment.center,
              children: <Widget>[
                Text(
                  Result,
                ),
                Text(idealResult),
                Text(adjustResult),
              ],
            ),
          ),
          RoundedButton(
            title: 'Re-Calc',
            onPressed: () {
              Navigator.pop(context);
            },
          )
        ],
      ),
    );
  }
}

Upvotes: 0

EdwynZN
EdwynZN

Reputation: 5601

Round 3

You almost got it, the problem is here

class _TriButtonState extends State<TriButton> {
  Option selectedOption; 
  // this value is not part of the notifier,
  // it's an independent variable

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Container(
        constraints: widget.triWidth,
        child: Row(
          crossAxisAlignment: CrossAxisAlignment.center,
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Expanded(
              child: RectButton(
                buttonChild: Text(
                  widget.title1,
                  style: TextStyle(color: Colors.white),
                ),
                onPress: () {
                  setState(() {
                    selectedOption = Option.one; 
                    //changing this value doesn't notify/change the ValueNotifier
                  });
                },
                bgColor: selectedOption == Option.one
                    ? kActiveButtonColor
                    : kInactiveButtonColor,
              ),
            ),

The Valuechanged you had was better to change the notifier

class TriButton extends StatefulWidget {
  TriButton(
      {this.title1, this.title2, this.title3, this.triWidth, this.selected, this.onChanged});

  final String title1;
  final String title2;
  final String title3;
  final BoxConstraints triWidth;
  final Option selected; //instead of passing the class, just pass the option from the class
  final ValueChanged<Option> onChanged; //create this to tell the notifier a value changed

  @override
  _TriButtonState createState() => _TriButtonState();
}

And now in the TriButton

class _TriButtonState extends State<TriButton> {
  Option selectedOption; 
  // this value is not part of the notifier,
  // it's an independent variable

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Container(
        constraints: widget.triWidth,
        child: Row(
          crossAxisAlignment: CrossAxisAlignment.center,
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Expanded(
              child: RectButton(
                buttonChild: Text(
                  widget.title1,
                  style: TextStyle(color: Colors.white),
                ),
                onPress: () => widget.onChanged(Option.one), //tell to which value you want to change when this is pressed
                bgColor: widget.selected == Option.one
                    ? kActiveButtonColor
                    : kInactiveButtonColor,
              ),
            ),
            .... //Repeat for the others

And in the parent (InputScreen) change the ValueNotifier to add those parameters

ValueListenableBuilder<Option>(
        valueListenable: selected,
        builder: (context, option, _) => TriButton(
          title1: 'Button 1',
          title2: 'Button 2',
          title3: 'Button 3',
          selected: option, //the value selected
          onChanged: (newOption) => selected.option = newOption //the value to change when one of the buttons is pressed
        ),
      ),

Now it will change accordingly and when you tap 'Calculate' it will print the right value


Also just to help you understand a bit about the logic this is how you can do it without ValueNotifier, there is a Cupertino Widget called CupertinoSegmentedControl (Don't focus the style of the widget for now, just the logic)

It take a Map<T, Widget> to make the buttons, where each widget is the Text('Button 1,2...) and group value to decide which T to select, in this case I will just make T an int and select accordingly to the index. In _InputScreenState create a Map with the widgets and an int that holds the value selected;

final Map<int,Widget> buttons = {
    1: Text('Button 1'),
    2: Text('Button 2'),
    3: Text('Button 3')
  };
  
  final Map<int,Widget> genereatedButtons = List<Widget>.generate(
    10, (index) => Text('Button $index')).asMap(); //This is the same as the above, just to generate as much as you want, in this case I just genereated 10
  int keySelected; //it holds the value selected, if null nothing is selected, but you could initilialize it at 0

and now create the widget before the InputRow

CupertinoSegmentedControl<int>(
    children: genereatedButtons, //or the other map buttons
    groupValue: keySelected,
    onValueChanged: (index) => setState(() => keySelected = index), 
),

and in the button Calculate print(keySelected) will give you the index selected

enter image description here

Upvotes: 1

EdwynZN
EdwynZN

Reputation: 5601

On your custom widget add the parameter TextEditingController for your TextField

class InputRow extends StatefulWidget {
  InputRow({this.inputParameter, this.unit1, this.unit2, this.textField});
  final String inputParameter;
  final String unit1;
  final String unit2;
  final TextEditingController textField; //Add this controller and also to the parameters of the constructor

  @override
  _InputRowState createState() => _InputRowState();
}



class _InputRowState extends State<InputRow> {
  String newTaskTitle;
  Units selectedUnit;
  String unit;

  @override
  void initState() {
    super.initState();
    setState(() {
      unit = widget.unit1;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      constraints: BoxConstraints(maxWidth: 375, maxHeight: 50),
      child: Row(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          Container(
            child: AutoSizeText(
              widget.inputParameter,
              textAlign: TextAlign.center,
              style: TextStyle(
                fontSize: 20.0,
              ),
            ),
          ),
          Expanded(
            child: Container(
              decoration: BoxDecoration(
                border: Border.all(
                  color: Colors.red,
                  width: 3,
                ),
                borderRadius: BorderRadius.only(
                  topLeft: Radius.circular(10),
                  bottomLeft: Radius.circular(10),
                ),
              ),
              child: TextField(
                controller: widget.textField, //  <-- The Controller
                autofocus: true,
                textAlign: TextAlign.center,
                onChanged: (newText) {
                  newTaskTitle = newText;
                },
              ),
            ),
          ),
          Container(
            decoration: BoxDecoration(
              color: Colors.red,
              border: Border.all(
                color: Colors.red,
                width: 3,
              ),
              borderRadius: BorderRadius.only(
                topRight: Radius.circular(10),
                bottomRight: Radius.circular(10),
              ),
            ),
            child: Row(
              children: <Widget>[
                Container(
                  padding: EdgeInsets.all(5),
                  child: Center(
                      child: AutoSizeText(
                    unit,
                    style: TextStyle(fontSize: 20, fontWeight: FontWeight.w500),
                  )),
                ),
                Container(
                    constraints: BoxConstraints(maxHeight: 50, maxWidth: 60),
                    child: FlatButton(
                      highlightColor: Colors.transparent,
                      child: Column(
                        crossAxisAlignment: CrossAxisAlignment.center,
                        mainAxisAlignment: MainAxisAlignment.center,
                        children: <Widget>[
                          Icon(
                            Icons.loop,
                            size: 25,
                          ),
                        ],
                      ),
                      onPressed: () {
                        setState(() {
                          selectedUnit = selectedUnit == Units.unit2
                              ? Units.unit1
                              : Units.unit2;
                          if (selectedUnit == Units.unit1) {
                            unit = widget.unit1;
                          } else {
                            unit = widget.unit2;
                          }
                        });
                      },
                    )),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

On the parent widget (The screen calling the custom widgets) create TextEditingController for each TextField you want to know, they have a parameter TextEditingController.text which gives you the value written on the Textfield that is controlling

class InputScreen extends StatefulWidget {
  static const String id = 'adjustments';
  
  @override
  AdjustmentInputScreenState createState() => AdjustmentInputScreenState();
}

class AdjustmentInputScreenState extends State<InputScreen> {
  final TextEditingController weightController = TextEditingController(); //create one for the height
  final TextEditingController heightController = TextEditingController(); //create one for the width
  

  //don't forget to dispose them
  @override
  void dispose(){
    super.dispose();
    weightController.dispose();
    heightController.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        children: <Widget>[
          AppBar(
            leading: null,
            actions: <Widget>[
              IconButton(
                  icon: Icon(Icons.close),
                  onPressed: () {
                    Navigator.pop(context);
                  }),
            ],
            title: Text('Dose Adjustment'),
            backgroundColor: Colors.transparent,
            elevation: 0.0,
          ),
          InputRow(
            unit1: 'cm',
            unit2: 'inches',
            inputParameter: 'height',
            textField: heightController, // The textcontroller to check the height
          ),
          InputRow(unit1: 'lbs', unit2: 'kg', inputParameter: 'weight',
                   textField: weightController, // The textcontroller to check the weight
                  ),
          FlatButton(
            child: Text('Calculate'),
            onPressed: () {
              //int.tryparse if you want a number, check for null, empty strings or strings that aren't number
              String height = heightController.text;
              String weight = weightController.text;
              print('Height: $height');
              print('Weight: $weight');
              //Do your math here
            },
          ),
        ],
      ),
    );
  }
}

With heightController.text or weightController.text you can see the value everywhere in the parent, as long as you have the TextEditingController attach it to the TextField widget you want to see

UPDATE

Try and see how a TextEditingController works, you will see it extends a class ValueNotifier that rebuilds its listeners when the value change, you can create your own like this

class MyUnit extends ValueNotifier<Units>{ //You want to check when the enum Units change, so that will be your ValueNotifier
  final String _label1;
  final String _label2;
  
  MyUnit({Units unit = Units.unit1, String label1 = 'cm', String label2 = 'inches'}) : _label1 = label1, _label2 = label2, super(unit);
  
  String get label => value == Units.unit1 ? _label1 : _label2; //The labels you define, just like unit1 and unit2 in InputRow 
  Units get unit => value; //the enum value 
  set unit(Units newUnit) => value = newUnit; //when this change, it will rebuild the listeners
}

Now just like TextEditingController you just need to create them and dispose them

final MyUnit heightUnit = MyUnit();
final MyUnit weightUnit = MyUnit(label1: 'lbs', label2: 'kg');


//don't forget to dispose them
@override
void dispose(){
  super.dispose();
  weightController.dispose();
  heightController.dispose();
  heightUnit.dispose();
  weightUnit.dispose();
}

...

InputRow(
   myUnit: heightUnit,
   inputParameter: 'height',
   textField: heightController,
),
InputRow(myUnit: weightUnit, inputParameter: 'weight',
   textField: weightController,
),
FlatButton(
   child: Text('Calculate'),
   onPressed: () {        
     //I change the names of the variables to avoid confusion
     String myHeight = heightController.text;
     String myWeight = weightController.text;
     String labelHeight = heightUnit.label;
     String labelWeight = weightUnit.label;
     print('Height: $myHeight $labelHeight');
     print('Weight: $myWeight $labelWeight');
          
     double weight = double.parse(myWeight); //this could throw an error if myWeight cannot be parsed
     if(weightUnit.unit == Units.unit1) weight = weight / 2.2;
     print(weight.toStringAsFixed(1));
     //Do your math here
   },
),

In InputRow you can pass this class just like the TextEditingController, and now you don't need to give the other values unit1, unit2, selectedUnit because that logic is now in the class MyUnit

class InputRow extends StatefulWidget {
  InputRow({this.inputParameter, this.textField, this.myUnit});
  final String inputParameter;
  final MyUnit myUnit;
  final TextEditingController textField; //Add this controller and also to the parameters of the constructor

  @override
  _InputRowState createState() => _InputRowState();
}



class _InputRowState extends State<InputRow> {

  @override
  void initState() {
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      constraints: BoxConstraints(maxWidth: 375, maxHeight: 50),
      child: Row(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          Container(
            child: Text(
              widget.inputParameter,
              textAlign: TextAlign.center,
              style: TextStyle(
                fontSize: 20.0,
              ),
            ),
          ),
          Expanded(
            child: Container(
              decoration: BoxDecoration(
                border: Border.all(
                  color: Colors.red,
                  width: 3,
                ),
                borderRadius: BorderRadius.only(
                  topLeft: Radius.circular(10),
                  bottomLeft: Radius.circular(10),
                ),
              ),
              child: TextField(
                controller: widget.textField, //  <-- The Controller
                autofocus: true,
                textAlign: TextAlign.center,
              ),
            ),
          ),
          Container(
            decoration: BoxDecoration(
              color: Colors.red,
              border: Border.all(
                color: Colors.red,
                width: 3,
              ),
              borderRadius: BorderRadius.only(
                topRight: Radius.circular(10),
                bottomRight: Radius.circular(10),
              ),
            ),
            child: Row(
              children: <Widget>[
                Container(
                  padding: EdgeInsets.all(5),
                  child: Center(
                   child: ValueListenableBuilder<Units>( //This work as a listener
                      valueListenable: widget.myUnit, //the object to listen, it needs to extend a ValueNotifier
                      builder: (context, unit, _) =>
                        Text(widget.myUnit.label,style: TextStyle(fontSize: 20, fontWeight: FontWeight.w500))
                /*
                 The builder gives me a value unit, that I can use when the ValueListenableBuilder rebuilds,
                 but that is the Units enum, which you don't want to display, so you ignore it and give widget.myUnit.label to the Text widget, it will rebuild only when Units change, but the label also getter also change with that value, so it's ok
                */
                  )
                  ),
                ),
                Container(
                    constraints: BoxConstraints(maxHeight: 50, maxWidth: 60),
                    child: FlatButton(
                      highlightColor: Colors.transparent,
                      child: Column(
                        crossAxisAlignment: CrossAxisAlignment.center,
                        mainAxisAlignment: MainAxisAlignment.center,
                        children: <Widget>[
                          Icon(
                            Icons.loop,
                            size: 25,
                          ),
                        ],
                      ),
                      onPressed: () {
                        Units unit = widget.myUnit.unit;
                        widget.myUnit.unit = unit == Units.unit1 ? Units.unit2 : Units.unit1; //this will call the setter in MyUnit and rebuild the listeners
                      },
                    )),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

TriButton Code As you can see I tried to play with the value notifier but cant figure out how to get the title of the button selected. I cant figure out how to pull that info to the next screen.

import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'rect_button.dart';
import 'package:pocketpk/constants.dart';

enum Option {
  one,
  two,
  three,
}

class TriButton extends StatefulWidget {
  TriButton(
      {this.title1, this.title2, this.title3, this.triWidth, this.onChanged});

  final String title1;
  final String title2;
  final String title3;
  final Constraints triWidth;
  ValueChanged<Option> onChanged;

  @override
  _TriButtonState createState() => _TriButtonState();
}

class _TriButtonState extends State<TriButton> {
  Option selectedOption;

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Container(
        constraints: widget.triWidth,
        child: Row(
          crossAxisAlignment: CrossAxisAlignment.center,
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Expanded(
              child: ValueListenableBuilder<Option>(
                valueListenable: widget.onChanged,
                builder: (context, option, _) => RectButton(
                  buttonChild: Text(
                    widget.title1,
                    style: TextStyle(color: Colors.white),
                  ),
                  onPress: () {
                    setState(() {
                      selectedOption = Option.one;
                    });
                  },
                  bgColor: selectedOption == Option.one
                      ? kActiveButtonColor
                      : kInactiveButtonColor,
                ),
              ),
            ),
            Expanded(
              child: ValueListenableBuilder<Option>(
                valueListenable: widget.onChanged,
                builder: (context, option, _) => RectButton(
                  buttonChild: Text(
                    widget.title2,
                    style: TextStyle(color: Colors.white),
                  ),
                  onPress: () {
                    setState(() {
                      selectedOption = Option.two;
                    });
                  },
                  bgColor: selectedOption == Option.two
                      ? kActiveButtonColor
                      : kInactiveButtonColor,
                ),
              ),
            ),
            Expanded(
              child: ValueListenableBuilder<Option>(
                valueListenable: widget.onChanged,
                builder: (context, option, _) => RectButton(
                  buttonChild: Text(
                    widget.title3,
                    style: TextStyle(color: Colors.white),
                  ),
                  onPress: () {
                    setState(() {
                      selectedOption = Option.three;
                    });
                  },
                  bgColor: selectedOption == Option.three
                      ? kActiveButtonColor
                      : kInactiveButtonColor,
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

Notifier

import 'package:flutter/material.dart';
import 'package:pocketpk/widgets/tri_button.dart';

class MyButton extends ValueNotifier<Option> {
  final String _title1;
  final String _title2;
  final String _title3;

  MyButton(
      {Option option = Option.one,
      String title1 = 'A',
      String title2 = 'B',
      String title3 = 'C'})
      : _title1 = title1,
        _title2 = title2,
        _title3 = title3,
        super(option);

  //You can add a get method to retrieve the title based on the option selected with a switch
  String get title {
    switch (value) {
      case Option.one:
        return _title1;
      case Option.two:
        return _title2;
      case Option.three:
        return _title3;
      default:
        return _title1; //or a default String, but to be honest this will never be used
    }
  }

  Option get option => value;
  set option(Option newOption) => value = newOption;
}

UPDATE

import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'rect_button.dart';
import 'package:pocketpk/constants.dart';

enum Option {
  one,
  two,
  three,
}

class Parent extends StatelessWidget{
  ValueNotifier<Option> myButton = MyButton();

  @override
  Widget build(BuildContext context){
    return ValueListenableBuilder<Option>(
       valueListenable: myButton,
       builder: (context, button, _) => TriButton(
           title1: button.title1, //take the underscores of the names in the MyButton class to make them public
           title2: button.title2,
           title3: button.title3,
           triWidth: BoxConstraints(), //I don't know this value
           onChanged: (newOption) => button.option = newOption,
         )
     );
  }
}

class TriButton extends StatefulWidget {
  TriButton(
      {this.title1, this.title2, this.title3, this.triWidth, this.onChanged});

  final String title1;
  final String title2;
  final String title3;
  final Constraints triWidth;
  ValueChanged<Option> onChanged;

  @override
  _TriButtonState createState() => _TriButtonState();
}

class _TriButtonState extends State<TriButton> {

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Container(
        constraints: widget.triWidth,
        child: Row(
          crossAxisAlignment: CrossAxisAlignment.center,
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Expanded(
              child: RectButton(
                buttonChild: Text(
                  widget.title1,
                  style: TextStyle(color: Colors.white),
                ),
                onPress: () {
                  widget.onChanged(Option.one);
                },
                bgColor: selectedOption == Option.one
                  ? kActiveButtonColor
                  : kInactiveButtonColor,
              ),
            ),
            Expanded(
              child: RectButton(
                buttonChild: Text(
                  widget.title2,
                  style: TextStyle(color: Colors.white),
                ),
                onPress: () {
                  widget.onChanged(Option.two);
                },
                bgColor: selectedOption == Option.two
                  ? kActiveButtonColor
                  : kInactiveButtonColor,
              ),
            ),
            Expanded(
              child: RectButton(
                buttonChild: Text(
                  widget.title3,
                  style: TextStyle(color: Colors.white),
                ),
                onPress: () {
                  widget.onChanged(Option.three);
                },
                bgColor: selectedOption == Option.three
                  ? kActiveButtonColor
                  : kInactiveButtonColor,
              ),
            ),
          ],
        ),
      ),
    );
  }
}

Upvotes: 1

Alex Collette
Alex Collette

Reputation: 1784

You should create a function in the InputRow widget that grabs the data from the fields and returns them. Then, when you create the InputRow, give it a key. Finally, when you want to get the values from the InputRow, just call key.yourFunction() and store the result.

class InputRow extends StatefulWidget {
  InputRow({this.inputParameter, this.unit1, this.unit2, Key key}) : super(key: key););
  final String inputParameter;
  final String unit1;
  final String unit2;

  @override
  _InputRowState createState() => _InputRowState();
}

Create key: GlobalKey<_InputRowState> key = new GlobalKey();

Pass key to InputRow:

InputRow(
  unit1: 'cm',
  unit2: 'inches',
  inputParameter: 'height',
  key: key,
),

Get parameters from InputRow: var data = key.yourFunction();

Upvotes: 0

Related Questions