Reputation: 23
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.
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
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
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
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
Upvotes: 1
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
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