I am making an app where a user can add multiple stateful widgets in a list view inside a home stateful widget.
Example: list of ingredients.
Every ingredient widget has a TextFormField.
A user can add as many ingredients as wish to and eventually the data entered will be updated using a POST request from the home stateful widget.
Question: How do i access the TextFormField controller of every added ingredient widget from the home state.
Also is this the best way to approcah this or is there a better way?
class home extends StatefulWidget {
const home({Key? key}) : super(key: key);
#override
_homeState createState() => _homeState();
}
class _homeState extends State<home> {
List<Widget> listRecipe = [];
addIngredient(){
listRecipe.add(new Ingredient());
}
Future<http.Response> postData(){
return http.post(
Uri.parse(recipeData),
headers: <String, String>{
'Authorization':'token $token',
'Content-Type': 'application/json; charset=UTF-8',
},
body: jsonEncode(<String, String>{
"name":"something",
"ingredients":'{"ingredient1": "value", "ingredient2": "value", ...... }'
}),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
Container(
child: Wrap(
children:[ ListView(
children:listRecipe,
),
],
),
),
] ),
bottomNavigationBar: Row(
children:
[FloatingActionButton(
onPressed: addIngredient,
),
FloatingActionButton(onPressed: postData)
]));
}
}
class Ingredient extends StatefulWidget {
const Ingredient({Key? key}) : super(key: key);
#override
_IngredientState createState() => _IngredientState();
}
class _IngredientState extends State<Ingredient> {
final _typeController = TextEditingController();
#override
Widget build(BuildContext context) {
return Container(
child: Padding(
padding: EdgeInsets.only(left: 22, top: 20, right: 15),
child: Column(
children: [
Container(
width: MediaQuery.of(context).size.width,
child: Padding(
padding: const EdgeInsets.only(left: 5.0),
child: Text(
"Process",
style: TextStyle(
fontFamily: 'Nunito',
color: Colors.grey.shade500,
fontSize: 12,
),
textAlign: TextAlign.left,
),
),
),
Container(
height: 45,
child: TextFormField(
keyboardType: TextInputType.text,
maxLines: null,
controller: _typeController,
style: TextStyle(fontSize: 15, fontFamily: 'Nunito'),
decoration: InputDecoration(
hintText: 'Enter Process',
hintStyle: TextStyle(color: Colors.grey.shade400),
contentPadding: EdgeInsets.only(left: 5),
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(color: Colors.green)),
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(color: Colors.green, width: 2),
),
),
),
),
],
),
),
);;
}
}
tl:dr
Add final String property to your StatefulWidget
in initState assign value of property to TextEditingController:
textEditingController.text = widget.stringValue
override didUpdateWidget and also set TextEditingControllerValue textEditingController.text = widget.stringValue
I would suggest to add a model for your Ingredient, e.g:
class Ingredient {
String? data;
Ingredient({this.data});
}
Rename the Ingredient Widget to e.g. IngredientWidget. The type of your listReceipe should stay List<Ingredient>:
List<Ingredient> listRecipe = [];
addIngredient() {
setState(() {
listRecipe.add(Ingredient());
});
}
Change the body of your Home Widget:
body: Container(
child: ListView.builder(
itemCount: listRecipe.length,
itemBuilder: (context, index) =>
IngredientWidget(listRecipe[index]))),
Now change your IngredientWidget like this:
class IngredientWidget extends StatefulWidget {
const IngredientWidget(this.ingredient, {Key? key}) : super(key: key);
final Ingredient ingredient;
#override
_IngredientWidgetState createState() => _IngredientWidgetState();
}
class _IngredientWidgetState extends State<IngredientWidget> {
final _typeController = TextEditingController();
#override
void didUpdateWidget(covariant IngredientWidget oldWidget) {
super.didUpdateWidget(oldWidget);
_typeController.text = widget.ingredient.data ?? '';
}
#override
void initState() {
super.initState();
_typeController.text = widget.ingredient.data ?? '';
}
#override
Widget build(BuildContext context) {
// stays the same
}
}
You can pass a Value to your Ingredient widget:
Ingredient(data: 'Hello World')
So you can also update an Ingredient in listReceipe with
setState(() {
listRecipe[index] = Ingredient(data: anyStringValue);
});
and your list should update accordingly.
Related
When creating a DropDownButton through a class, the dropdown list itself does not open. Tell me, what is my mistake?
The button is in the "inactive" state. That is, when I click on it, I do not have a list with data
Yes, I'm sure the data is there.
I would not create a class for the button, but I have about 10 of them, and in order not to have a lot of repetitive code, I need to use the class
I can provide button code -
class DropDownButtons extends StatelessWidget {
final dynamic valueDropDown;
final String hintDropDown;
final List<DropdownMenuItem<dynamic>>? itemsDropDown;
final void Function(dynamic)? saveState;
const DropDownButtons({
Key? key,
required this.valueDropDown,
required this.hintDropDown,
required this.itemsDropDown,
required this.saveState
}) : super (key: key);
#override
Widget build(BuildContext context) {
return Container(
width: 270,
margin: EdgeInsets.only(top: 7),
child: Align(
alignment: Alignment.center,
child: DropdownButtonFormField(
hint: Text('hintDropDown'),
value: valueDropDown,
icon: const Icon(Icons.keyboard_arrow_down),
items: itemsDropDown,
onChanged: saveState,
)
)
);
}
}
And also, the display code -
DropDownButtons(valueDropDown: LoginClass.countryValue, hintDropDown: 'Text', itemsDropDown: LoginClass.countryDropDown, saveState: saveStateCountry()),
And of course, saving the stat -
saveStateCountry() {
(dynamic newValue) {
setState(() {
LoginClass.countryValue = newValue!;
print(LoginClass.countryValue);
});
};
}
minimum reproductive code -
class CreatedApplications extends StatelessWidget {
const CreatedApplications({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key}) : super(key: key);
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
saveStateCountry() {
(dynamic newValue) {
setState(() {
LoginClass.countryValue = newValue!;
print(LoginClass.countryValue);
});
};
}
#override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: false,
body: SingleChildScrollView(
padding: EdgeInsets.all(15),
child: Column(children: [
Column(
children: <Widget>[
Align(
alignment: Alignment.center,
child: FractionallySizedBox(
widthFactor: 1,
alignment: Alignment.centerLeft,
child: Text('${now}'))),
],
),
SizedBox(
height: 12,
),
// const DropDown(),
DropDownButtons(valueDropDown: LoginClass.countryValue, hintDropDown: 'Выберите страну', itemsDropDown: LoginClass.countryDropDown, saveState: saveStateCountry()),
],
),
),
]
)
)
);
}
}
class DropDownButtons extends StatelessWidget {
final dynamic valueDropDown;
final String hintDropDown;
final List<DropdownMenuItem<dynamic>>? itemsDropDown;
final Function saveState;
const DropDownButtons({
Key? key,
required this.valueDropDown,
required this.hintDropDown,
required this.itemsDropDown,
required this.saveState
}) : super (key: key);
#override
Widget build(BuildContext context) {
return Container(
width: 270,
margin: EdgeInsets.only(top: 7),
child: Align(
alignment: Alignment.center,
child: DropdownButtonFormField(
decoration: InputDecoration(
border: OutlineInputBorder(
borderRadius: const BorderRadius.all(
const Radius.circular(25.0),
),
),
fillColor: Colors.lightBlueAccent.withOpacity(0.2),
filled: true
),
hint: Text(hintDropDown),
value: valueDropDown,
icon: const Icon(Icons.keyboard_arrow_down),
items: itemsDropDown,
onChanged: (dynamic newValue) {
saveState;
}
)
)
);
}
}
To organize the code in a Form, I put the "Dropdownbutton" code in a separate class, and I call this class for each dropdown list. Indeed in this case, I have two drop down lists. At the level of the onChange() function, I assign the selected value to the selectedVal variable. So my question is how to access this value and pass it through the form after clicking on the submit button. Can anyone tell me how to solve this problem using Flutter or Getx?!
Thank you in advance.
Class of Form:
class InscriptionView extends StatefulWidget {
const InscriptionView({Key? key}) : super(key: key);
#override
State<StatefulWidget> createState() {
return InscriptionViewState();
}
}
class InscriptionViewState extends State<InscriptionView> {
GlobalKey<FormState> formstate2 = GlobalKey();
String? selectedValGender;
String? selectedDiploma;
inscription() async {
print(selectedValGender);
print(selectedDiploma);
//Code
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
padding: const EdgeInsets.all(20),
child: Form(
key: formstate2,
child: Column(children: [
const CustomDropDownButton(
hint: "Gender",
listeItems: ["male", "female"],
icon: Icon(Icons.person),
),
const SizedBox(height: 20),
const CustomDropDownButton(
hint: "Diploma",
listeItems: ["Bachelor", "Master"],
icon: Icon(Icons.school_outlined),
),
const SizedBox(height: 20),
Center(
child: InkWell(
child: const Text("Page de connexion"),
onTap: () async {
await inscription();
},
))
]),
)));
}
}
Class of customise DropDownButton:
class CustomDropDownButton extends StatefulWidget {
final List<String>? listeItems;
final String? hint;
final Widget? icon;
String? selectedGender;
String? selectedDiploma;
const CustomDropDownButton(
{Key? key,
this.listeItems,
this.hint = '',
this.icon})
: super(key: key);
#override
State<CustomDropDownButton> createState() => _CustomDropDownButtonState();
}
class _CustomDropDownButtonState extends State<CustomDropDownButton> {
#override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(horizontal: 20),
decoration: BoxDecoration(
color: Colors.white, borderRadius: BorderRadius.circular(50)),
child: DropdownButton<String>(
dropdownColor: const Color.fromARGB(255, 192, 196, 220),
borderRadius: BorderRadius.circular(20),
isExpanded: true,
hint: Text(widget.hint!),
icon: widget.icon,
value: CustomDropDownButton.selectedGender,
items: widget.listeItems!
.map((e) => DropdownMenuItem(
value: e,
child: Text(e),
))
.toList(),
onChanged: (val) {
setState(() {
selectedGender = val!;
});
}),
);
}
}
currently learning flutter and trying to do a simple game.
I have list with prices and product name.
My idea is to display a random image, and someone tries to guess the price.
Currently I'm stuck on the comparing the input price with the price on the list.
This is what I currently have
class MyCustomForm extends StatefulWidget {
const MyCustomForm({Key? key}) : super(key: key);
#override
_MyCustomFormState createState() => _MyCustomFormState();
}
class _MyCustomFormState extends State<MyCustomForm> {
final myController = TextEditingController();
#override
void initState() {
super.initState();
#override
void dispose() {
myController.dispose();
super.dispose();
}
}
#override
Widget build(BuildContext context) {
int randomIndex = Random().nextInt(products.length);
return Scaffold(
appBar: AppBar(
title: const Text(
"Guess the Price!",
style: TextStyle(fontFamily: "Pacifico"),
),
),
backgroundColor: Colors.white,
body: InkWell(
child: Container(
padding: const EdgeInsets.all(40),
child: Column(
children: <Widget>[
Spacer(),
Container(
child: Text(
'${products[randomIndex].productName}',
style: TextStyle(
fontSize: 30,
),
)),
Container(
child: TextFormField(
controller: myController,
keyboardType: TextInputType.number,
inputFormatters: <TextInputFormatter>[
FilteringTextInputFormatter.allow(RegExp(r'[0-9]')),
],
decoration: InputDecoration(
hintText: "The price is ${products[randomIndex].productPrice}", //debuging
))),
Spacer(),
Container(
child: Text((() {
if ({products[randomIndex].productPrice} == {myController.text}) {
return "The price is correct!";
}
return "The price is wrong!";
})()),
),
],
),
),
),
);
}
}
What should I add to do the work? Should I add a listener, so when the text changes, he auto updates the value of the myController.text , or should I go through other ways?
Sorry if this is a newbie error, but currently searching for solutions!
First, you need to move randomIndex outside of the build method otherwise it would always change when the state changes and you can't compare it to user input.
I am not sure exactly what you want but I think this will give you a hint how to do it.
class MyCustomForm extends StatefulWidget {
const MyCustomForm({Key? key}) : super(key: key);
#override
_MyCustomFormState createState() => _MyCustomFormState();
}
class _MyCustomFormState extends State<MyCustomForm> {
final myController = TextEditingController();
String guessText = "";
#override
void initState() {
super.initState();
}
#override
void dispose() {
myController.dispose();
super.dispose();
}
int randomIndex = Random().nextInt(products.length);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text(
"Guess the Price!",
style: TextStyle(fontFamily: "Pacifico"),
),
),
backgroundColor: Colors.white,
body: InkWell(
child: Container(
padding: const EdgeInsets.all(40),
child: Column(
children: <Widget>[
Spacer(),
Container(
child: Text(
'${products[randomIndex].productName}',
style: TextStyle(
fontSize: 30,
),
)),
Container(
child: TextFormField(
controller: myController,
onChanged: (value) {
if (products[randomIndex].productPrice == value) {
guessText = "The price is correct!";
setState(() {
});
}
},
keyboardType: TextInputType.number,
inputFormatters: <TextInputFormatter>[
FilteringTextInputFormatter.allow(RegExp(r'[0-9]')),
],
decoration: InputDecoration(
hintText: "The price is ${products[randomIndex].productPrice}", //debuging
))),
Spacer(),
Container(
child: Text(guessText),
),
],
),
),
),
);
}
}
Below is a working solution. There were quite a few changes, so have a look.
You need to have a button that gives a user the chance to move to the next product (or random). Also, you call product[..].productPrice--this is unnecessary because you should call product[..].price.
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class Product {
final String name;
final int price;
Product(this.name, this.price);
}
class MyCustomForm extends StatefulWidget {
const MyCustomForm({Key? key}) : super(key: key);
#override
_MyCustomFormState createState() => _MyCustomFormState();
}
class _MyCustomFormState extends State<MyCustomForm> {
final myController = TextEditingController();
List<Product> products = [
Product('p1', 5),
Product('p2', 10),
];
int productIndex = 0;
int enteredPrice = 0;
#override
void initState() {
super.initState();
#override
void dispose() {
myController.dispose();
super.dispose();
}
}
#override
Widget build(BuildContext context) {
int randomIndex = Random().nextInt(products.length);
return Scaffold(
appBar: AppBar(
title: const Text(
"Guess the Price!",
style: TextStyle(fontFamily: "Pacifico"),
),
),
backgroundColor: Colors.white,
body: InkWell(
child: Container(
padding: const EdgeInsets.all(40),
child: Column(
children: <Widget>[
const Spacer(),
Text(
products[productIndex].name,
style: const TextStyle(
fontSize: 30,
),
),
TextFormField(
controller: myController,
onChanged: (_) {
enteredPrice = int.parse(myController.text);
setState(() {});
},
keyboardType: TextInputType.number,
inputFormatters: <TextInputFormatter>[
FilteringTextInputFormatter.allow(
RegExp(r'[0-9]'),
),
],
),
const Spacer(),
Text(
(() {
return checkEnteredPrice(0);
})(),
style: const TextStyle(fontSize: 20),
),
const Spacer(),
ElevatedButton(
onPressed: () {
productIndex++;
setState(() {});
},
child: const Text('Next Product'))
],
),
),
),
);
}
String checkEnteredPrice(int productIndex) {
if (products[productIndex].price == enteredPrice) {
return "The price is correct!";
} else {
return "The price is wrong!";
}
}
}
I wrote an example here, check the comment for descrition. Check images for demo result.
For your code, i have some advice:
do not (){}(), use ? : or something else instead, it look too long and make me confuse.
recomend to separate your logic and your render, more specific is put Random in build that cause randomIndex changes everytime widget rendered. put them in some function that caller able.
import 'package:flutter/material.dart';
import 'dart:math';
void main() => runApp(MyApp());
const products = <Map<String, dynamic>>[
{'name': 'HotDog', 'price': 5},
{'name': 'Televison', 'price': 699},
{'name': 'Carrot', 'price': 10},
];
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(primarySwatch: Colors.blue),
home: const App(),
);
}
}
class App extends StatefulWidget {
const App({Key? key}) : super(key: key);
#override
_AppState createState() => _AppState();
}
class _AppState extends State<App> {
final controller = TextEditingController();
int? randomIndex; // stored random index product
String? answer; // stored your answer
bool get submitAnswered =>
answer != null; // getter check user was answerd yet?
int? get correctPrice => randomIndex != null
? products[randomIndex!]['price']
: null; // getter to get correct answer
// function to generate randomIndex, reset your answer
void getRandomIndex() {
int? newIndex;
do {
newIndex = Random().nextInt(products.length);
} while (newIndex == randomIndex);
setState(() {
randomIndex = newIndex;
answer = null;
controller.text = '';
});
}
// function to submit your answer
void onSubmitted(String value) {
setState(() {
answer = value;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text(
"Guess the Price!",
style: TextStyle(fontFamily: "Pacifico"),
),
),
backgroundColor: Colors.white,
body: Container(
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// Button that generate new randomIndex
ElevatedButton(
onPressed: getRandomIndex,
child: const Text('Get random product'),
),
// Only render when have randomIndex
if (randomIndex != null) ...[
// Display name of prouct
Center(
child: Text(products[randomIndex!]['name']),
),
// Only render when not answered yet, show text input and submit button
if (!submitAnswered) ...[
TextField(
controller: controller,
onSubmitted: onSubmitted,
decoration: const InputDecoration(
hintText: 'your price',
),
),
ElevatedButton(
onPressed: () => onSubmitted(controller.text),
child: const Text('Submit your price'),
),
] else
// Only render when answered, showing your result, correct or not
Center(
child: Text(int.tryParse(answer!) == correctPrice
? 'Correct price ($correctPrice)'
: 'Wrong price, your price is $answer and correct price is $correctPrice'),
),
],
],
)),
);
}
}
questionController.text always returns me a null value. rather than Whatever I insert in textformfield.
class _AddQuestionState extends State<AddQuestion> {
TextEditingController questionController = TextEditingController();
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
TextFormField(
controller: questionController,
decoration: InputDecoration(
hintText: 'description',
hintStyle: TextStyle(
color: Colors.grey
),
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(20.0)),
),
),),
AddUser(questionController.text),]
}}
When I called this class and print the output, it returns me null.
Using AddUser(questionController.text);
I printed the output using print(question) but it returns me empty string.
class AddUser extends StatelessWidget {
final String question;
AddUser(this.question);
#override
Widget build(BuildContext context) {
// Create a CollectionReference called users that references the firestore collection
CollectionReference users = FirebaseFirestore.instance.collection('Questions').doc("cse").collection("CSE");
Future<void> addUser() {
print(question);
// Call the user's CollectionReference to add a new user
return users
.add({
'question': question, // John Doe
})
.then((value) => print("User Added"))
.catchError((error) => print("Failed to add user: $error"));
}
What happen is when it first build the screen your AddQuestion widget questionController is set to empty string which is passed to the AddUser() widget.
If [controller] is null, then a [TextEditingController] will be constructed automatically and its text will be initialized to [initialValue] or the empty string.
When you changed the value in your questionController, your AddUser() widget didnt know the changes. By adding setState it will rebuild the whole AddQuestion widget and passed the new value to your AddUser widget.
Try this example to have an undestanding
class MyWidget extends StatefulWidget {
const MyWidget({Key? key}) : super(key: key);
#override
State<MyWidget> createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
TextEditingController questionController = TextEditingController();
String? questionValue;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
children: [
TextFormField(
controller: questionController,
decoration: InputDecoration(
hintText: 'description',
hintStyle: TextStyle(color: Colors.grey),
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(20.0)),
),
),
),
SizedBox(height: 20),
TextButton(
onPressed: () {
print("questionController: ${questionController.text}");
setState(() {
questionValue = questionController.text;
});
},
child: Text("SETSTATE BUTTON"),
),
SizedBox(height: 20),
Text("questionValue: $questionValue"),
SizedBox(height: 20),
AddUser(questionController.text),
],
),
),
);
}
}
class AddUser extends StatelessWidget {
final String question;
AddUser(this.question);
#override
Widget build(BuildContext context) {
return Text("question: $question");
}
}
i have a form which i decided to break into multiple widget for code re- usability. the problem i am having i dont know how to interact with each components. for example, if the main form declare a variable, how do i access that variable in the custom textfield widget which is store in a different dart file.
below is the code i have
form dart file (main.dart)
import 'package:flutter/material.dart';
import 'package:finsec/widget/row_text_input.dart';
import 'package:finsec/widget/text_form_field.dart';
import 'package:finsec/widget/save_button.dart';
import 'package:finsec/utils/strings.dart';
import 'package:finsec/utils/dimens.dart';
import 'package:finsec/utils/colors.dart';
import 'package:finsec/widget/column_text_input.dart';
void main() {
runApp(MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Simple Interest Calculator App',
home: ThirdFragment(),
theme: ThemeData(
brightness: Brightness.dark,
primaryColor: Colors.indigo,
accentColor: Colors.indigoAccent),
));
}
class ThirdFragment extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return _ThirdFragmentState();
}
}
class _ThirdFragmentState extends State<ThirdFragment> {
var _formKey = GlobalKey<FormState>();
var _currentItemSelected = '';
bool isError = false;
bool isButtonPressed = false;
#override
void initState() {
super.initState();
}
TextEditingController amountController = TextEditingController();
TextEditingController frequencyController = TextEditingController();
#override
Widget build(BuildContext context) {
TextStyle textStyle = Theme.of(context).textTheme.title;
return Scaffold(
appBar: AppBar(
title: Text('Simple Interest Calculator'),
),
body: Form(
key: _formKey,
child: SingleChildScrollView(
child: Column (children: [
Padding(
padding: EdgeInsets.only(top: 10.0, bottom: 5.0, left: 15.0, right: 15.0),
child: CustomTextField(textInputType:TextInputType.number,
textController: amountController,
errorMessage:'Enter Income Amount',
labelText:'Income Amount for testing'),
),
RowTextInput(inputName: 'Frequency:',
textInputType: TextInputType.number,
textController: frequencyController,
errorMessage: 'Choose Income Frequency',
labelText: 'Income Amount for testing'
),
RowTextInput(inputName: 'Date Paid:',
textInputType: TextInputType.number,
textController: datePaidController,
errorMessage: 'Pick Income Payment Date',
labelText: 'Income Amount for testing'
),
SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
MaterialButton(
height: margin_40dp,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(margin_5dp)),
minWidth: (MediaQuery.of(context).size.width * .9) / 2,
color: Theme.of(context).primaryColor,
textColor: white,
child: new Text(save),
onPressed: () => {
setState(() {
if (_formKey.currentState.validate()) {
// amountController.text.isEmpty ? amountController.text='Value require' : amountController.text='';
//this.displayResult = _calculateTotalReturns();
}
})
},
splashColor: blueGrey,
),
MaterialButton(
height: margin_40dp,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(margin_5dp)),
minWidth: (MediaQuery.of(context).size.width * .9) / 2,
color: Theme.of(context).primaryColor,
textColor: white,
child: new Text(save_and_continue),
onPressed: () => {},
splashColor: blueGrey,
)
])
]
),
),
}
RowTextInput is a different dart file that contains this code. RowTextInput.dart
import 'package:flutter/material.dart';
import 'package:finsec/utils/hex_color.dart';
class CustomTextField extends StatelessWidget {
CustomTextField({
this.textInputType,
this.textController ,
this.errorMessage,
this.labelText,
});
TextInputType textInputType;
TextEditingController textController;
String errorMessage, labelText;
#override
Widget build(BuildContext context) {
bool isError = false;
return Container(
child: TextFormField(
keyboardType: textInputType,
style: Theme
.of(context)
.textTheme
.title,
controller: textController,
validator: (String value) {
if (value.isEmpty) {
return errorMessage;
}
},
decoration: InputDecoration(
labelStyle: TextStyle(
color: Colors.grey,
fontSize: 16.0
),
contentPadding: EdgeInsets.fromLTRB(10.0, 10.0, 10.0, 10.0), //size of textfield
errorStyle: TextStyle(
color: Colors.red,
fontSize: 15.0
),
border: OutlineInputBorder(
borderSide: BorderSide(width:5.0),
borderRadius: BorderRadius.circular(5.0)
)
)
),
);
}
}
i want to access isError and isButtonPressed variables located in main.dart from RowTextInput.dart and be able to assign values. main.dart should then be able to see those values assign in RowTextInput.dart file.
also,i want to move the MaterialButton button in its own widget file (button.dart) but then i dont know how this dart file will interact with the main.dart file when button is click or to check values of isError and IS button pressed. basically, i am breaking the form into different components (textfield and button) and store them in their own separate file. but i want all the files main.dart, rowintputtext, button.dart(new) to be able to see values of variables in main.dart and change the values. is this possible? is there an easier way?
thanks in advance
If you think about it. In Flutter the Button and RawMaterialButton are already in other files. And the manage to do exactly what you want.
You should create a File mycustomButtons.dart.
In the file you should create a class that will build your Buttons...
But it must has two parameters in it's constructor actionSave actionSaveAndContinue.
You will then create two functions in your main something like:
void _save() {
setState(() {
if (_formKey.currentState.validate()) {
// amountController.text.isEmpty ? amountController.text='Value require' : amountController.text='';
//this.displayResult = _calculateTotalReturns();
}
})
}
Then you should pass your created functions as parameters:
MyCustomButtons(actionSave: _save, actionSaveAndContinue: _saveAndContinue)
So the button will have all needed information to update your main.dart variables.
The textField is pretty much the same. But you will need pass a validation function and a TextEditingController.
You can see the font of RawnMaterialButton, TextFormField to see how they receive (and pass) data from one class to an other.
I was also looking for breaking a form into multiple classes. This is that I did :
Form
Pass the onSaved function at the form level.
final _formKey = GlobalKey<FormState>();
#override
Widget build(BuildContext context) {
return Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
_CustomFormField(
onSaved: (value) => _myModelForm.field1 = value),
),
_CustomFormField2(
onSaved: (value) => _myModelForm.field2 = value),
)
),
RaisedButton(
onPressed: () {
// Validate will return true if the form is valid, or false if
// the form is invalid.
if (_formKey.currentState.validate()) {
// Process data.
_formKey.currentState.save();
// Observe if your model form is updated
print(myModelForm.field1);
print(myModelForm.field2)
}
},
child: Text('Submit'),
),
],
),
);
}
_CustomFormField1
The onSaved function will be passed as argument. This class can be either in the same file than the form or in another dedicated file.
class _CustomFormField1 extends StatelessWidget {
final FormFieldSetter<String> onSaved;
//maybe other properties...
_CustomFormField1({
#required this.onSaved,
});
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 10.0),
child: TextFormField(
// You can keep your validator here
validator: (value) {
if (value.isEmpty) {
return 'Please enter some text';
}
return null;
},
onSaved: onSaved,
),
);
}
}
Like onSaved, you can do the same way for focusNode, onFieldSubmitted, validator if needed in
I hope it will help you and others
There's probably a more elegant way to do it but I am currently experimenting with Singletons. See the code below:
import 'package:flutter/material.dart';
import 'package:get_it/get_it.dart';
import 'dart:async';
class AppModel {
TextEditingController nameController;
TextEditingController surnameController;
StreamController<String> fullnameStreamController;
AppModel() {
nameController = TextEditingController();
surnameController = TextEditingController();
fullnameStreamController = StreamController.broadcast();
}
update() {
String fullname;
if (nameController.text != null && surnameController.text != null) {
fullname = nameController.text + ' ' + surnameController.text;
} else {
fullname = 'Please enter both names';
}
fullnameStreamController.add(fullname);
}
}
GetIt getIt = new GetIt();
final appModel = getIt.get<AppModel>();
void main() {
getIt.registerSingleton<AppModel>(AppModel());
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(title: 'Singleton Demo'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
String text;
update() {
setState(() {
});
}
#override
void initState() {
text = 'waiting for input';
appModel.fullnameStreamController.stream.listen((data) {
text = data;
update();
});
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Container(
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
decoration: BoxDecoration(color: Colors.amberAccent),
child: Column(
children: <Widget> [
Card(
color: Colors.white,
child: Text('Name'),
),
Card(
color: Colors.yellow,
child: NameTextField()
),
Divider(),
Card(
color: Colors.white,
child: Text('Surname'),
),
Card(
color: Colors.yellow,
child: SurnameTextField()
),
OkButton(),
Card(
color: Colors.white,
child: Text('Full name'),
),
Card(
color: Colors.orange,
child: FullnameText(text),
),
],
),
),
);
}
}
class NameTextField extends StatefulWidget {
NameTextField({Key key}) : super(key: key);
_NameTextFieldState createState() => _NameTextFieldState();
}
class _NameTextFieldState extends State<NameTextField> {
#override
Widget build(BuildContext context) {
return Container(
child: TextField(
controller: appModel.nameController,
),
);
}
}
class SurnameTextField extends StatefulWidget {
SurnameTextField({Key key}) : super(key: key);
_SurnameTextFieldState createState() => _SurnameTextFieldState();
}
class _SurnameTextFieldState extends State<SurnameTextField> {
#override
Widget build(BuildContext context) {
return Container(
child: TextField(
controller: appModel.surnameController,
),
);
}
}
class FullnameText extends StatefulWidget {
FullnameText(this.text,{Key key}) : super(key: key);
final String text;
_FullnameTextState createState() => _FullnameTextState();
}
class _FullnameTextState extends State<FullnameText> {
#override
Widget build(BuildContext context) {
return Container(
child: Text(widget.text),
);
}
}
class OkButton extends StatefulWidget {
OkButton({Key key}) : super(key: key);
_OkButtonState createState() => _OkButtonState();
}
class _OkButtonState extends State<OkButton> {
#override
Widget build(BuildContext context) {
return Container(
color: Colors.white10,
child: RaisedButton(
color: Colors.white,
child: Icon(Icons.check),
onPressed: () {appModel.update();},
),
);
}
}
Check how I use the three controllers in the update function of the AppModel class.
CustomTextFields must extends parent(widget where is form) in this case it is ThirdFragment
class CustomTextField extends ThirdFragment{
CustomTextField({
this.textInputType,
this.textController,
this.errorMessage,
this.labelText,
});