0

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(),
                  ),
                ),
              );
            },
          ),
        ],
      ),
    );
  }
}

5 Answers5

1

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,
              ),
            ),
          ],
        ),
      ),
    );
  }
}

EdwynZN
  • 4,895
  • 2
  • 12
  • 15
  • EdwynZN, thanks for your help! That was the solution I needed. I also need to past the selectedUnit to the calculator too. Any idea how I can do that since it lays in the InputRow Widget? – Peter Moran Jun 15 '20 at 00:25
  • 1
    I updated my answer, I don't know with what kind of state management are you more familiar, but in this simple case copying the idea of how a TextEditingController works should be fine – EdwynZN Jun 15 '20 at 06:03
  • Dude, you're amazing! Thank you so much! Do you have any recommended references I could read to learn more about this stuff? I understand ~50% from what I've learned but the other 50% I would never have even thought of. Thanks again! – Peter Moran Jun 16 '20 at 02:08
  • To be honest practice and check videos of the kind of stuff you want to implement in projects (thats the best way to learn). the Flutter docs for widgets is a great tool to know what has been done and what you can do from scratch – EdwynZN Jun 16 '20 at 04:59
  • EdwynZN, Thanks again! I have one more request if you don't mind continuing to help me :). I am trying to adjust a calculation based on which units are selected (i.e. if weightUnit = 'lbs' the function would take weight/2.2 but if the weightUnit = 'kg' then weight would be used in the calculation as is. I tried using both the IF/Else statements as well as ternary operators and couldn't get it to work correctly. Any thoughts? Thanks again for all your help! I updated my code above to should you everything – Peter Moran Jun 17 '20 at 01:52
  • Sorry I forgot to add the code whee I created weightUnit and heightUnit, did you created it like this: MyUnit weightUnit = MyUnit(label1: 'kg', label2: 'lbs'); and in the onTap method a double weight = weightUnit.unit == Units.unit1 ? double.parse(weightController.text) : double.parse(weightController.text) / 2.2; – EdwynZN Jun 17 '20 at 02:07
  • I had something close to that but when I try your code the weightUnit.unit doesn't exist. I tried weightUnit.label and that also didn't work. I keep getting "equality operator == invocation with references of unrelated types. – Peter Moran Jun 17 '20 at 02:32
  • perhaps the problem is I'm using heightUnit and weightUnit in the onTap and there is another one in the widget, so you should change it's names or use this.heightUnit when calling the ValueListener, I updated that part of the code, check it and maybe that helps better understand the logic – EdwynZN Jun 17 '20 at 02:58
  • EdwynZN, mind helping me with something similar? Now I am trying to pass the title of a button to another screen. I updated my code above to include my Tributton that I am trying to extract the title from. – Peter Moran Jul 02 '20 at 00:08
  • Sorry I read your code but couldn't understand where is the ValueNotifier you mention, do you change it by calling the callback ValueChanged? If you want some part of the widget tree to react to the change of the ValueNoitifier you should wrap it in a ValueListenableBuilder, maybe showing me the claas of your ValueNotifier or what are you trying to change in the code with some comments – EdwynZN Jul 02 '20 at 01:33
  • EdwynZN. I updated the code. Ive been trying to figure out how to build the notifier because I have more than two values so I cant do a ternary operator. Any ideas? – Peter Moran Jul 08 '20 at 00:17
  • I updated your code, you mean passing the title corresponding to the option selected? are you passing the ValueNotifier to the next screen and then using it in the ValueListenableBuilder? – EdwynZN Jul 08 '20 at 02:00
  • EdwynZN thats my goal! I am hoping to take the option selected (aka the title of the button that is selected) and be able to pass that to the page that the widget resides on. I plan to eventually pass that information to another page to preform different tasks (i.e. multiply by 2 or divide by 0.15). I didn't make it to using the ValueListenableBuilder because I couldn't figure out the notifier piece. Now that you helped clarify my notifier code, all I should have to do is replace the widget.onChanged(title#) with the notifylisterners and wrap that in a ValueListenableBuilder, correct? – Peter Moran Jul 08 '20 at 02:09
  • I think that ValueChanged should be of type Option because that's the type of value the ValueNotifier is, ValueChanged – EdwynZN Jul 08 '20 at 02:18
  • So would I pass the ValueNotifier/ListenableBuilder into the TriButton widget or the screen that houses a Tributton widget since I am trying to inform that screen of the selected button within the Tributton widget? Also, should I be calling ``` widget.onChanged(widget.title1);``` under setState for each button or is this where I would be using the ValueListenableBuilder? This stuff is so confusing to me and I have no idea why. I do think once I get this figured out Ill have a much better understanding of the flow. – Peter Moran Jul 09 '20 at 00:48
  • ValueNotifier is the class that holds the logic, ValueListenableBuilder is the widget that listens to change of that class and then rebuilds when tehere is a change widget.onChanged(widget.title1) should be used to update the value of the ChangeNotifier (there is no need of setState) – EdwynZN Jul 09 '20 at 04:38
  • Okay. I think I am really close! Only error I am experiencing is that I am getting "argument type 'void Function(Option)' cant be assigned to the parameter type 'ValueListenable – Peter Moran Jul 10 '20 at 01:13
  • The parent widget that created TriButton should have the instance of the ValueNotifier and when you create Tributton pass it like this in the constructor `TriButton(onChanged: (newOption) => myButton.option = newOption)` where myButton is the instance of the ValueNotifier – EdwynZN Jul 10 '20 at 04:07
  • I am still experiencing the same error as mentioned in my previous comment. I updated my code. I am utilizing the code you posted above when I call the TriButton on my screen. I am also experiencing an error with your code because it is saying myButton does not exist. – Peter Moran Jul 11 '20 at 01:49
  • ValueChanged is not a type of ValueNotifier, don't let the name confuse you, that's what the error is trying to warn you. You can use/create a ValueNotifier and a ValueListenable before creating the Tributton in the Widget tree and update TriButton when onChanged triggers a new option value, or you can save a simple class with no notifier and use setState as your example, ValueNotifier is more useful when trying to update multiple places of the widget tree simultaneously and you can't just not trigger setState in different places – EdwynZN Jul 11 '20 at 06:08
  • Are you saying that I don't need to build the ValueListenable the way it currently is? Like I should have it where it only updates the title and not the entire RectButton? I tried to use the one you previously helped with as a guide but I do not see where I created the ValueNotifier except the final MyUnit myUnit on the InputRow. When I try to do that with MyButton I get an error on my screen that says "the argument type dynamic Function (dynamic) cant be assigned to MyButton when I pass the TriButton the updated: myButton: (newOption) => myButton.option = newOption. Sorry I am so confused :( – Peter Moran Jul 12 '20 at 00:41
  • https://youtu.be/s-ZG-jS5QHQ check the video example, you will see they use it when there are a lot of places to change the same value, maybe if you create a gist or another question with a cleaner full code I can help you, right now, there is a lot in this that I am also confused what were you trying to achieve – EdwynZN Jul 12 '20 at 01:38
  • Thanks for the link! Ive actually taken notes on that video and others but still confused. I just played with my code some and got it to work without an error! The only issue is when I try to print the title of the selected button, I only get "MyButton#66f66(Option.one)" despite what is selected. I think it has something to do with the MyButton class. Do you mind reviewing it real quick and seeing if you notice anything off? If not, ill keep playing with the code and maybe resort to creating a glist or question. Thanks again for everything! – Peter Moran Jul 12 '20 at 02:42
  • Updated code. I also tried to explain and show exactly what I want and what is currently happening when I use the TriButton widget – Peter Moran Jul 12 '20 at 11:47
1

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

EdwynZN
  • 4,895
  • 2
  • 12
  • 15
  • OMG Thank you so much! Seriously you helped me so much and your explanations are amazing. Thank you Thank you Thank you! – Peter Moran Jul 13 '20 at 02:05
  • EdwynZN, sorry I am back :(. Ive been trying to figure out a way to pass the title of the selected button to my calculator model and then utilize that information to perform a different calculation (i.e. if 'Male" multiply by 1 but if 'Female' multiply by 0.85). The only thing to work is to perform it on my input screen and pass it to the calculator like an input, which is repetitive and clutter some since use this code multiple times. Ive tried to use future, async, and await functionally but keep getting 'instance of Future' as my output. Any thoughts? I posted my code below. – Peter Moran Aug 02 '20 at 02:28
0

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();

Alex Collette
  • 1,664
  • 13
  • 26
0

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);
            },
          )
        ],
      ),
    );
  }
}

  • sorry I couldn't understand your problem but it seems you're struggling more with Dart logic than UI Flutter, you said you tried using future but all your values are sync (you already have them?) and you cannot do the math in your calculator model but you can do it in a constructor or factory constructor as far as I understand, do you want to pass the model to the result screen just to show it or want to change stuff there? – EdwynZN Aug 03 '20 at 01:56
  • Let me try explaining again. Lets say I pick the "male" button. I am hoping to pass "male" to the calculator model to execute a command (i.e. if (gender == "male"){return weight * 1.2} else {return weight}). That way I can perform all of my math I would like the app to do on the calculator model and I can pass it to my results page. Currently I cant figure out how to wait for the button titles to be passed to the calculator. As a result, despite picking "male" in my example above, I am currently getting the else outputs (in this case return weight). Does that make sense? – Peter Moran Aug 03 '20 at 23:55
0
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);
  }
}
EdwynZN
  • 4,895
  • 2
  • 12
  • 15
  • I need to create a default constructor class for the factory, correct? In that default class I also need to list all of the parameters as well? (i.e. Calculator({height, weight, age, etc}); ) Thanks again for all your help! – Peter Moran Aug 06 '20 at 00:10
  • yeah just like your Calculator class example, I just added the factory constructor – EdwynZN Aug 06 '20 at 00:20
  • How to I actually go about calling the calculation? Currently I get an error with the way the code is set up saying "The method 'calculate' isn't defined for the class 'Calculator'" If I re create my String calculate() function from my calculator screen I don't receive that error but it cant access the parameters inside the factory. – Peter Moran Aug 06 '20 at 00:38
  • the other methods cannot access the parameter inside the factory, a factory helps you control what you need in the object (calculator in this case), do some logic and then expects to return that object, the methods inside your calculator class should only work with the parameter you pass and the inner one of the Calculator class, what exactly do you want to change in your calculate method? – EdwynZN Aug 06 '20 at 00:47
  • I don't want to change anything in my method but rather be able to call the calculator on my input screen and run the logic to get the idealCalculate value. Currently I get the error method above because it says I don't have calculate defined in my Calculator class. Thats why I was trying to create the String calculate() so that I could call that on my input screen but was unable to access the parameters in the Calculator hence my confusion – Peter Moran Aug 06 '20 at 00:51
  • thats weird, I ran the calculate method of your class just fine after defining the var `calc` – EdwynZN Aug 06 '20 at 01:27
  • Maybe I am missing something but on the 'Result: calc.calulate(),' on the input screen I cant find where the calculate is defined. Is it missing from the code you ran as well? – Peter Moran Aug 06 '20 at 01:38
  • yeah that is just part of your code of the calculator model, I din't move that part – EdwynZN Aug 06 '20 at 02:00
  • I just updated my calculator code. Could you please review it and tell me what I am messing up? I have tried manipulating it but have had no luck. Currently, the String calculate(), calculateIdeal(), and calculateAdjust() can not access the parameters in my calculator constructor at the very top – Peter Moran Aug 06 '20 at 02:11
  • I ran an example with some random data for the Calculator and could use the calculate method without proble, I still don't understand what's going on, are you sure you're referrring correctly to the method – EdwynZN Aug 06 '20 at 02:54
  • EdwynZN, I found my error was with syntax! This code works great! Thanks so much. I do have one question after studying up on factories. Why do I need to do the "set idealWeight(String title)" ? Isn't that already specified as a parameter for the factory? – Peter Moran Aug 12 '20 at 01:37
  • that was more of an example of how to use setters to change inner variable according to one change, in this case when you change gender Female and Male the equations and variables changes, so you won't need to create another Calculator model – EdwynZN Aug 12 '20 at 05:42