Given a stateful widget, is somehow possible to call a method defined in the State class (the one which extends State<NameOfTheWidget>). Actually, I just want to rebuild the _State class, like calling setState() but from outside of the class. I know how to it from children to parents but not viceversa.
class Foo extends StatefulWidget{
State createState() => new _State();
//...bar() ??
}
class _State extends State<Foo>{
#override
Widget build(BuildContext context) {...}
void bar(){...}
}
EDIT: some real code
First, we hace the equivalent to the inner widget; it's a a customized text field. The point is that I want enable and disable it according to the boolean _activo variable.
import 'package:flutter/material.dart';
import 'package:bukit/widgets/ensure.dart';
class EntradaDatos extends StatelessWidget{
final String _titulo;
final String _hint;
TextEditingController _tec;
FocusNode _fn = new FocusNode();
final String Function(String s) _validador;
final TextInputType _tit;
bool _activo;
/*
* CONSTRUCTOR
*/
EntradaDatos(this._titulo, this._hint, this._validador, this._tit, this._activo){
_tec = new TextEditingController();
}
#override
Widget build(BuildContext context){
print('Construyendo');
return new EnsureVisibleWhenFocused(
focusNode: _fn,
child: new TextFormField(
enabled: _activo,
keyboardType: _tit,
validator: _validador,
autovalidate: true,
focusNode: _fn,
controller: _tec,
decoration: InputDecoration(
labelText: _titulo,
hintText: _hint
),
)
);
}
String getContenido(){
return _tec.text;
}
}
Then I have a concrete implementation of the previous text field, which just extends it:
import 'package:flutter/material.dart';
import 'package:bukit/widgets/entrada_datos.dart';
class EntradaMail extends EntradaDatos{
static String _hint = "nombre#dominio.es";
static String _validador(String s){
if(s.isEmpty){
return 'El campo es obligatorio';
}else{
if(!s.contains('#') || !s.contains('.') || s.contains(' ')){
return 'Introduce una dirección válida';
}else{
String nombre = s.substring(0, s.indexOf('#'));
String servidor = s.substring(s.indexOf('#')+1, s.lastIndexOf('.'));
String dominio = s.substring(s.lastIndexOf('.')+1);
if(nombre.length < 2 || servidor.length < 2 || dominio.length < 2){
return 'Introduce una dirección válida';
}
}
}
}
EntradaMail(String titulo, bool activo) : super(titulo, _hint, _validador, TextInputType.emailAddress, activo);
}
Finally, the equivalent of my outter widget. It's just a checkbox followed by the prevoius EntradaEmail widget. As far as I know, once the checkbox is pressed and the onChange call is made, the setState call should rebuild everything, but I've contrasted with debug messaged that the build method of the first inner widget is never called. My point is enabling and disabling the text field according to the checkbox.
class CampoEnvio extends StatefulWidget{
EntradaMail _mail;
EntradaMovil _movil;
String _tituloMail;
String _tituloMovil;
bool _usaMail = false;
bool _usaMovil = false;
CampoEnvio(this._tituloMail, this._tituloMovil){
_mail = new EntradaMail(_tituloMail, _usaMail);
_movil = new EntradaMovil(_tituloMovil, _usaMovil);
}
State createState() => _State(_mail, _movil, _usaMail, _usaMovil, _tituloMail, _tituloMovil);
}
class _State extends State<CampoEnvio>{
bool _usaMail;
bool _usaMovil;
String _tituloMail;
String _tituloMovil;
EntradaMail _mail;
EntradaMovil _movil;
_State(this._mail, this._movil, this._usaMail, this._usaMovil, this._tituloMail, this._tituloMovil);
#override
Widget build(BuildContext context){
return new Column(
children: <Widget>[
new ListTile(
leading: new SizedBox(
width: 70.0,
child: new Row(
children: <Widget>[
new Checkbox(
value: _usaMail,
activeColor: Colors.black,
onChanged: (value) {
setState(() {
_usaMail = value;
});
},
),
],
),
),
title: _mail,
),
//...
new Divider()
],
);
}
}
Yes, in theory it is possible using a GlobalKey, but not recommended!
class OuterWidget extends StatefulWidget {
#override
State<StatefulWidget> createState() => OuterWidgetState();
}
class OuterWidgetState extends State<OuterWidget> {
final _innerKey = GlobalKey<InnerWidgetState>();
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
InnerWidget(key: _innerKey),
RaisedButton(
child: Text('call foo'),
onPressed: () {
_innerKey.currentState.foo();
},
)
],
);
}
}
class InnerWidget extends StatefulWidget {
InnerWidget({Key key}) : super(key: key);
#override
State<StatefulWidget> createState() => InnerWidgetState();
}
class InnerWidgetState extends State<InnerWidget> {
String _value = 'not foo';
#override
Widget build(BuildContext context) {
return Text(_value);
}
void foo() {
setState(() {
_value = 'totally foo';
});
}
}
Better approach: Instead, what it would be a good idea to pull the state up:
class OuterWidget extends StatefulWidget {
#override
State<StatefulWidget> createState() => OuterWidgetState();
}
class OuterWidgetState extends State<OuterWidget> {
String _innerValue;
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
InnerWidget(value: _innerValue),
RaisedButton(
child: Text('call foo'),
onPressed: () {
setState(() {
_innerValue = 'totally foo';
});
},
)
],
);
}
}
class InnerWidget extends StatefulWidget {
InnerWidget({Key key, this.value}) : super(key: key);
final String value;
#override
State<StatefulWidget> createState() => InnerWidgetState();
}
class InnerWidgetState extends State<InnerWidget> {
#override
Widget build(BuildContext context) {
return Text(widget.value);
}
}
If you can, make the inner widget stateless:
class InnerWidget extends StatelessWidget {
InnerWidget({Key key, this.value}) : super(key: key);
final String value;
#override
Widget build(BuildContext context) {
return Text(value);
}
}
If your child is interactive (taps, checkbox...), you can define callbacks with VoidCallback or ValueChanged<T> (or your own typedef) to process the events in the parent widget.
Ok, now that you added the sample code, I will try to explain why your widget does not work, and I will try to explain what other improvements can be made.
First of all, you can improve the readability of your code by using named constructors for all of your widgets, like in my other answers (You can auto-generate them with Android Studio: Define some final fields, then press the lightbulb button to generate the constructor).
The next problem is that widgets which create a TextEditingController must always be stateful widgets! Otherwise the input made by the user will disappear after every build!
Usually you would pass in the TextEditingController from a parent widget (the widget that handles processes data when you submit it)
Also, it is discouraged to extend widgets. Instead, use composition, e.g.:
class EntradaMail extends StatelessWidget {
final String titulo;
// ...
Widget build(BuildContext context) {
return EntradaDatos(
titulo: titulo,
//...
)
}
}
Widget properties should always be public and final (never start with a _).
You are doing some strange things in CampoEnvio.
First of all, you are for some reason passing in all the properties of the widget to the State in createState. That has some consequences which you probably don't intend.
In general it is extremely rare that your State class has constructor parameters, and usually you would not pass properties from the stateful widget to the state.
The problem is that createState is only called once, it is not called again when you call initState in a parent widget. The state is kept until the widget is disposed.
That means your state constructor is only called once as well, and the fields in _State (of CampoEnvio) will stay the same all the time. Even when the parent is rebuilt and calls the constructor of CampoEnvio again, the old values in _State will not be replaced.
It's also very stange that you are creating widgets (EntradaMail and EntradaMovil) in the StatefulWidget.
The class that extends StatefulWidget should not do that! It is basically just a "bag" of properties.
Here is the complete fixed sample code, following the conventions explained above:
class EntradaDatos extends StatefulWidget {
EntradaDatos({Key key, this.titulo, this.hint, this.validador, this.tit, this.activo}) : super(key: key);
final String titulo;
final String hint;
final String Function(String s) validador;
final TextInputType tit;
final bool activo;
#override
State<StatefulWidget> createState() => _EntradaDatosState();
}
class _EntradaDatosState extends State<EntradaDatos> {
// FocusNode and TextEditingController must be the same for the whole lifetime of the widget
// => put into State
TextEditingController _tec;
FocusNode _fn;
#override
void initState() {
super.initState();
_tec = new TextEditingController();
_fn = new FocusNode();
}
#override
Widget build(BuildContext context) {
print('Construyendo');
return new EnsureVisibleWhenFocused(
focusNode: _fn,
child: new TextFormField(
enabled: widget.activo,
keyboardType: widget.tit,
validator: widget.validador,
autovalidate: true,
focusNode: _fn,
controller: _tec,
decoration: InputDecoration(labelText: widget.titulo, hintText: widget.hint),
));
}
String getContenido() {
return _tec.text;
}
}
class EntradaMail extends StatelessWidget {
static String _hint = "nombre#dominio.es";
static String _validador(String s) {
if (s.isEmpty) {
return 'El campo es obligatorio';
} else {
if (!s.contains('#') || !s.contains('.') || s.contains(' ')) {
return 'Introduce una dirección válida';
} else {
String nombre = s.substring(0, s.indexOf('#'));
String servidor = s.substring(s.indexOf('#') + 1, s.lastIndexOf('.'));
String dominio = s.substring(s.lastIndexOf('.') + 1);
if (nombre.length < 2 || servidor.length < 2 || dominio.length < 2) {
return 'Introduce una dirección válida';
}
}
}
}
EntradaMail({Key key, this.titulo, this.activo}) : super(key: key);
final String titulo;
final bool activo;
#override
Widget build(BuildContext context) {
// use composition instead of inheritance
return EntradaDatos(
titulo: titulo,
activo: activo,
validador: _validador,
hint: _hint,
tit: TextInputType.emailAddress,
);
}
}
class CampoEnvio extends StatefulWidget {
const CampoEnvio({Key key, this.tituloMail, this.tituloMovil}) : super(key: key);
final String tituloMail;
final String tituloMovil;
#override
State<StatefulWidget> createState() => new _CampoEnvioState();
}
class _CampoEnvioState extends State<CampoEnvio> {
// I guess these variables are modified here using setState
bool _usaMail;
bool _usaMovil;
#override
Widget build(BuildContext context) {
// just rebuild the widgets whenever build is called!
final mail = new EntradaMail(
titulo: widget.tituloMail,
activo: _usaMail,
);
final movil = new EntradaMovil(
titulo: widget.tituloMovil,
activo: _usaMovil,
);
return new Column(
children: <Widget>[
new ListTile(
leading: new SizedBox(
width: 70.0,
child: new Row(
children: <Widget>[
new Checkbox(
value: _usaMail,
activeColor: Colors.black,
onChanged: (value) {
setState(() {
_usaMail = value;
});
},
),
],
),
),
title: mail,
),
//...
new Divider()
],
);
}
}
It always helps to look at the official samples in the Flutter repositories!
Related
I'm wondering if I can pass a function as a validator. I tried, but got no results.
Widget Field(String changedValue, String label, bool isTextObscured) {
return TextFormField(
decoration: InputDecoration(labelText: label),
validator: checkFieldEmpty,
);
}
checkFieldEmpty(String fieldContent) {
if(fieldContent.isEmpty) {
return 'Ce champ est obligatoire.';
}
return null;
}
Perhaps the function return type should be String? so that it fits the validator prototype!
Widget Field(String changedValue, String label, bool isTextObscured) {
return TextFormField(
decoration: InputDecoration(labelText: label),
validator: checkFieldEmpty,
);
}
String? checkFieldEmpty(String? fieldContent) { //<-- add String? as a return type
if(fieldContent.isEmpty) {
return 'Ce champ est obligatoire.';
}
return null;
}
A more appropriate way of doing this in Flutter
Remember, flutter is a declarative language. That is, you build your app by composing a widget tree. Here, you are using a function to return a Widget. That is breaking this rule. Instead, you should declare your own custom widget that implements the TextField Widget. Here's how:
1. Declare your custom widget
// Declare your CustomTextField as a Stateless/Stateful Widget
class MyCustomTextField extends StatelessWidget {
// Declare your custom vars, including your validator function
final String? changedValue;
final String? label;
final bool? isTextObscured;
final String? Function(String?)? validator;
const MyCustomTextField({
Key? key,
this.changedValue,
this.label,
this.isTextObscured,
this.validator,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return TextFormField(
decoration: InputDecoration(labelText: label),
validator: validator,
);
}
}
2. Use your custom widget
Now you can use this custom widget as a child of any other widget:
class ParentWidget extends StatelessWidget {
const ParentWidget({Key? key}) : super(key: key);
// This is your custom validator function and can leave
// anywhere ;)
Stirng? customValidtaor(String? fieldContent) => fieldContent.isEmpty? 'Ce champ est obligatoire.': null
#override
Widget build(BuildContext context) {
return MyCustomTextField(
label: 'Some label'
// declare the validator here...
// valiator: (fieldContent) => fieldContent.isEmpty? 'Ce champ est obligatoire.': null
// or use your custom validator function
validator: customValidator,
);
}
}
By doing this, you are respecting Flutter's best practices by using Widget composition ;)
Add String as a return type for checkFieldEmpty() method, see below
instead this:
checkFieldEmpty(String fieldContent) {}
use this:
String checkFieldEmpty(String fieldContent) {}
Full Code Example:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
final appTitle = 'Form Validation Demo';
return MaterialApp(
title: appTitle,
home: Scaffold(
appBar: AppBar(title: Text(appTitle)),
body: MyCustomForm(),
),
);
}
}
// Create a Form widget.
class MyCustomForm extends StatefulWidget {
#override
MyCustomFormState createState() {
return MyCustomFormState();
}
}
// Create a corresponding State class.
// This class holds data related to the form.
class MyCustomFormState extends State<MyCustomForm> {
// Create a global key that uniquely identifies the Form widget
// and allows validation of the form.
//
// Note: This is a GlobalKey<FormState>,
// not a GlobalKey<MyCustomFormState>.
final _formKey = GlobalKey<FormState>();
Widget textField(String changedValue, String label, bool isTextObscured) {
return TextFormField(
decoration: InputDecoration(labelText: label),
validator: checkFieldEmpty,
);
}
String checkFieldEmpty(String fieldContent) {
if (fieldContent.isEmpty) return 'Ce champ est obligatoire.';
return null;
}
#override
Widget build(BuildContext context) {
// Build a Form widget using the _formKey created above.
return Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
textField('changedValue', 'Enter Password', false),
ElevatedButton(
onPressed: () {
// Validate returns true if the form is valid, or false otherwise.
if (_formKey.currentState.validate()) {
// If the form is valid,
// perform further actions here
}
},
child: Text('Submit'),
),
],
),
);
}
}
I am new to flutter, so please excuse my experience.
I have 2 classes, both stateful widgets.
One class contains the tiles for a listview.
Each tile class has a checkbox with a state bool for alternating true or false.
The other class (main) contains the body for creating the listview.
What I'd like to do is retrieve the value for the checkbox in the main class, and then update a counter for how many checkbboxes from the listview tiles have been checked, once a checkbox value is updated. I am wondering what the best practices are for doing this.
Tile class
class ListTile extends StatefulWidget {
#override
_ListTileState createState() => _ListTileState();
}
class _ListTileState extends State<ListTile> {
#override
Widget build(BuildContext context) {
bool selected = false;
return Container(
child: Row(
children: [Checkbox(value: selected, onChanged: (v) {
// Do something here
})],
),
);
}
}
Main Class
class OtherClass extends StatefulWidget {
#override
_OtherClassState createState() => _OtherClassState();
}
class _OtherClassState extends State<OtherClass> {
#override
Widget build(BuildContext context) {
return Container(
child: Column(
children: [
Text("Checkbox selected count <count here>"),
ListView.builder(itemBuilder: (context, index) {
// Do something to get the selected checkbox count from the listview
return ListTile();
}),
],
),
);
}
}
Hope this is you are waiting for
class OtherClass extends StatefulWidget {
#override
_OtherClassState createState() => _OtherClassState();
}
class _OtherClassState extends State<OtherClass> {
bool selected = false;
#override
void initState() {
super.initState();
}
var items = [
Animal("1", "Buffalo", false),
Animal("2", "Cow", false),
];
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("title")),
body: Container(
child: ListView.builder(
itemCount: items.length,
shrinkWrap: true,
itemBuilder: (ctx, i) {
return Row(
children: [
Text(items[i].name),
ListTile(
id: items[i].id,
index: i,
)
],
);
}),
));
}
}
ListTileClass
class ListTile extends StatefulWidget {
final String? id;
final int? index;
final bool? isSelected;
const ListTile ({Key? key, this.id, this.index, this.isSelected})
: super(key: key);
#override
_ListTileState createState() => _ListTileState();
}
class _ListTileState extends State<ListTile> {
bool? selected = false;
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return Container(
width: 20,
child: Checkbox(
value: selected,
onChanged: (bool? value) {
setState(() {
selected = value;
});
}));
}
}
I'd recommend using a design pattern such as BLoC or using the Provider package. I personally use the Provider Package. There are plenty of tutorials on youtube which can help get you started.
Lets assume a class "SpecialButton" and its State-Class "SpecialButtonState"
class SpecialButton extends StatefulWidget {
bool active = false;
SpecialButton({Key key}) : super(key: key);
#override
SpecialButtonState createState() => SpecialButtonState();
}
class SpecialButtonState extends State<SpecialButton> {
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return Container(
decoration:
BoxDecoration(color: this.widget.active ? COLOR_1 : COLOR_2),
child: null);
}
}
In the parent widget, I manage a couple of these buttons. Therefore, I want to assign a state to them. The solution I tried was to introduce a flag "active" in the SpecialButton class which I can easily set to either true or false from the parent widget. I can then use this in the build function of the state class to colorize the button. Unfortunately, this does not work completely as it does not update the button immediately (it needs some kind of state update e.g. by hovering over the element).
My second idea was to introduce this flag as a propper state of the SpecialButtonState class
class SpecialButton extends StatefulWidget {
SpecialButton({Key key}) : super(key: key);
#override
SpecialButtonState createState() => SpecialButtonState();
}
class SpecialButtonState extends State<SpecialButton> {
bool active;
#override
void initState() {
super.initState();
this.active = false;
}
activate() {
this.setState(() {
active = true;
});
}
deactivate() {
this.setState(() {
active = false;
});
}
#override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(color: this.active ? COLOR_1 : COLOR_2),
child: null);
}
}
As far as I understood, this would be the correct way to work with flutter but it seems that I can't access the functions "activate" or "deactivate" from either the SpecialButton Class or the Parent Class containing the widget.
So my question is: How can I (directly or indirectly through functions) modify a State from the corresponding StatefulWidget Class or the Parent Widget containing it?
There are already some similar questions about this on here on Stack Overflow where I could find hints both to use or not to use global keys for such behavior which i found misleading. Also, due to the rapid ongoing development of flutter, they are probably outdated so I ask this (similar) question again in relation to this exact use case.
EDIT: I forgot to mention that it is crucial that this flag will be changed after creation therefore It will be changed multiple times during its livetime. This requires the widget to redraw.
It is not neсessary to use stateful widget for SpecialButton is you case. You can handle active flag with stateless widget and keys. Example code:
class SomeParent extends StatefulWidget {
const SomeParent({Key key}) : super(key: key);
#override
State<SomeParent> createState() => SomeParentState();
}
class SomeParentState extends State<SomeParent> {
bool _button1IsActive = false;
bool _button2IsActive = false;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
SpecialButton(
key: UniqueKey(),
active: _button1IsActive,
),
SizedBox(height: 8),
SpecialButton(
key: UniqueKey(),
active: _button2IsActive,
),
SizedBox(height: 16),
TextButton(
child: Text('Toggle button 1'),
onPressed: () {
setState(() {
_button1IsActive = !_button1IsActive;
});
},
),
SizedBox(height: 8),
TextButton(
child: Text('Toggle button 2'),
onPressed: () {
setState(() {
_button2IsActive = !_button2IsActive;
});
},
),
],
),
);
}
}
class SpecialButton extends StatelessWidget {
final bool active;
const SpecialButton({Key key, this.active = false}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
height: 40,
width: 40,
decoration: BoxDecoration(color: active ? Colors.red : Colors.blue),
);
}
}
SomeParent is my fantasy, just for example. Don't know what your parent is.
Keys are significant here. They tell widget tree when specific widgets with the same type (such as SpecialButton) should be rebuild.
Please try this approach, it should work.
As nvoigt says, your buttons could even be stateless widget , but their parent should be statefull and you should provide them with the corresponding value. e.g.:
import 'package:flutter/material.dart';
class Parent extends StatefulWidget {
#override
_ParentState createState() => _ParentState();
}
class _ParentState extends State<Parent> {
bool isEnabled = false;
#override
Widget build(BuildContext context) {
return Column(
children: [
StateLessButton1(isEnabled: isEnabled),
StateLessButton1(isEnabled: !isEnabled),
FloatingActionButton(onPressed: (){
setState(() {
isEnabled = !isEnabled;
});
})
],
);
}
}
Now it just depends on when you want to change that value. If you want to change it inside your buttons, I would recommend you to use a class with ChangeNotifier and a function inside it that changes the value. Otherwise I would recommend not to separate your tree into multiple files
I have a list of choice widget and want to pass the selected choice to another widget.
Here is the list of choice widget
class ChoiceChipWidget extends StatefulWidget {
final List<String> reportList;
final Function(String item) onChoiceSelected;
ChoiceChipWidget(this.reportList, this.onChoiceSelected);
#override
_ChoiceChipWidgetState createState() => new _ChoiceChipWidgetState();
}
class _ChoiceChipWidgetState extends State<ChoiceChipWidget> {
String selectedChoice = "";
_buildChoiceList() {
List<Widget> choices = List();
widget.reportList.forEach((item) {
choices.add(Container(
child: ChoiceChip(
label: Text(item),
selected: selectedChoice == item,
onSelected: (selected) {
setState(() {
selectedChoice = item;
widget.onChoiceSelected(item);
print(selectedChoice); //DATA THAT NEEDS TO BE PASSED
});
},
),
));
});
return choices;
}
#override
Widget build(BuildContext context) {
return Wrap(
children: _buildChoiceList(),
);
}
}
I need to pass it to this widget
class AddCashPage extends StatefulWidget {
#override
_AddCashPageState createState() => _AddCashPageState();
}
class _AddCashPageState extends State<AddCashPage> {
void createTodo() async {
if (_formKey.currentState.validate()) {
_formKey.currentState.save();
String repetition = //DATA NEEDS TO GO HERE;
final addCash = AddCash(repetition);
setState(() {
id = addCash.id;
});
}
}
#override
Widget build(BuildContext context) {
return Container(
child: Form(
key: _formKey,
child: Column(
children: <Widget>[
Row(
children: <Widget>[
ChoiceChipWidget(chipList, (item) {
selectedItem = item;
}),
],
),
RaisedButton(
child: Text("Update Cash Flow"),
onPressed: createTodo,
),
],
),
),
);
}
}
I tried making a constructor inside AddCashPage
like this
class AddCashPage extends StatefulWidget {
final ChoiceChipWidget choiceChipWidget;
AddCashPage({Key key, #required this.choiceChipWidget}) : super(key: key);
#override
_AddCashPageState createState() => _AddCashPageState();
}
I think you just missed to call setState() in here:
ChoiceChipWidget(chipList, (item) {
selectedItem = item;
}),
Like this:
ChoiceChipWidget(chipList, (item) {
setState(() => selectedItem = item);
}),
Then you could do this:
AddCash(selectedItem)
Make sure to declare the selectedItem variable inside _AddCashPageState, I don't see it on your code.
Your choice widget passes the data to the AddCashPage via the constructor you created, but you're missing something. You need to pass the data that AddCashPage has to its state (_AddCashState) so that you can use it there. Basically, you need to create one more constructor.
class AddCashPage extends StatefulWidget {
final ChoiceChipWidget choiceChipWidget;
AddCashPage({Key key, #required this.choiceChipWidget}) : super(key: key);
#override
_AddCashPageState createState() => _AddCashPageState(choiceChipWidget: choiceChipWidget);
}
And in _AddCashPageState:
class _AddCashPageState extends State<AddCashPage> {
final choiceChipWidget;
_AddCashPageState({Key key, #required this.choiceChipWidget});
}
To use your passed data inside _AddCashPageState class you can use widget property of the corresponding state of the related Stateful class.
For Ex : To use choice chip widget in your class you can use it like widget.ChoiceChipWidget
Any properties/methods provided in AddCashPage class can be accessed in its State class _AddCashPageState() using widget.ChoiceChipWidget property;
You can use this widget property inside methods only like, initState(), build(), dispose() etc.
I tried to get TextFormField value. But result is null
main page,
children:[
UrlTextField(),
UsernameTextField(),
UrlButton()
]
UrlTextField(), same like UsernameTextField()
class UrlTextField extends StatelessWidget {
final myController = TextEditingController();
#override
Widget build(BuildContext context) {
return AppTextField(
decoration:
InputDecoration(prefixText: "https://", labelText: "Enter your URL"),
myController: myController,
textInputType: TextInputType.url,
);}}
AppTextField() It's a common class, I used this class everywhere
class AppTextField extends StatelessWidget {
final InputDecoration decoration;
var myController = TextEditingController();
final TextInputType textInputType;
AppTextField({
this.decoration,
this.myController,
this.textInputType
});
#override
Widget build(BuildContext context) {
return TextFormField(
controller: myController,
keyboardType: textInputType,
textAlign: TextAlign.left,
decoration: decoration
);}}
I need to get Url and Username value when click button or any other area,
class UrlButton extends StatelessWidget {
#override
Widget build(BuildContext context) {
return AppButton(
onPressed: () {
String url = UrlTextField().myController.text;
String username = UsernameTextField().myController.text;
print('url is $text');
});}}
AppButton() This class also common
class AppButton extends StatelessWidget {
final VoidCallback onPressed;
AppButton({
this.buttonTextStyle
});
#override
Widget build(BuildContext context) {
return RaisedButton(
child: Text(...),
onPressed: onPressed);}}
You are trying to retrieve text from a controller which has just been instantiated in the onPressed of the button so there can't be any text so far! To solve this problem you need some way of State Management to access and change an existing widget, in your case the UrlTextField widget. I will give you an example of how you could solve this quickly:
Main page:
class MainPage extends StatefulWidget {
...
#override
createState() => _MainPageState();
}
class _MainPageState extends State<MainPage> {
UrlTextField _urlTextField = UrlTextField();
...
children:[
_urlTextField,
UsernameTextField(),
UrlButton(_urlTextField)
]
Now we instantiated a UrlTextField which can be referenced to and can be passed to another widget like your UrlButton:
class UrlButton extends StatelessWidget {
final UrlTextField urlTextField;
UrlButton(this.urlTextField);
#override
Widget build(BuildContext context) {
return AppButton(
onPressed: () {
String url = this.urlTextField.myController.text;
String username = UsernameTextField().myController.text;
print('url is $text');
}
);
}
}
On this way you instantiated one UrlTextField and used it in your main page where a user can fill in some input and passed it down to UrlButton where you can access its controller and therefore its text.
I would recommend you to look more into the topic State Management since there are a lot of ways to handle such a case. I can recommend you to take a look on Provider which is very easy to use and convenient to access certain data.
what is the value of text in print('url is $text'); isn't it supposed to be like this print('url is $url');
I think this what you are trying to do.... But there's many loop holes brother..
One thing to remember.. You need separate controllers for each TextField you can't declare one as myController and assign it to all. They'll all have the same value.
class StackHelp extends StatefulWidget {
#override
_StackHelp createState() => _StackHelp();
}
class _StackHelp extends State<StackHelp> {
final TextEditingController myController = TextEditingController();
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: new Scaffold(
body: Column(children: <Widget>[
UrlTextField(myController),
// UsernameTextField(),
UrlButton(myController)
])),
);
}
}
class UrlTextField extends StatelessWidget {
final TextEditingController myController;
UrlTextField(this.myController);
#override
Widget build(BuildContext context) {
return AppTextField(
decoration:
InputDecoration(prefixText: "https://", labelText: "Enter your URL"),
myController: myController,
textInputType: TextInputType.url,
);
}
}
class AppTextField extends StatelessWidget {
final InputDecoration decoration;
final TextEditingController myController;
final TextInputType textInputType;
AppTextField({this.decoration, this.myController, this.textInputType});
#override
Widget build(BuildContext context) {
return TextFormField(
controller: myController,
keyboardType: textInputType,
textAlign: TextAlign.left,
decoration: decoration);
}
}
class UrlButton extends StatelessWidget {
final TextEditingController myController;
UrlButton(this.myController);
#override
Widget build(BuildContext context) {
void onPressed() {
String url = this.myController.text;
// String username = UsernameTextField().myController.text;
print('url is $url');
}
return AppButton(onPressed);
}
}
class AppButton extends StatelessWidget {
final VoidCallback onPressed;
AppButton(this.onPressed);
#override
Widget build(BuildContext context) {
return RaisedButton(child: Text('Test'), onPressed: onPressed);
}
}