I made a custom input widget by wrapping Textfield in some widgets and so on. Now I can't reach the onChanged property directly from the custom widget. I tried making a property in my custom widget but couldn't implement it properly. I googled passing variables between widgets and it seems a hard thing to do. Any simple solution?
class Input extends StatelessWidget {
final String text;
final TextInputType type;
final int maxLength;
final bool password;
final String label;
final IconData icon;
final double padding;
final Function onChanged;
final ColorSwatch color;
Input(
{this.type = TextInputType.text,
#required this.text,
this.maxLength,
this.icon,
this.padding = 0.0,
this.password = false,
#required this.onChanged,
this.label,
this.color});
final String value = '';
#override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.all(padding),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Padding(
padding: EdgeInsets.all(3.0),
child: Text(
text + ":",
style: TextStyle(fontSize: 15.0, color: color),
),
),
Container(
padding: EdgeInsets.all(3.0),
width: 300.0,
child: TextField(
obscureText: password,
decoration: InputDecoration(
labelText: label,
icon: Icon(icon),
),
maxLength: maxLength,
keyboardType: type,
textInputAction: TextInputAction.next,
onChanged: onChanged,
),
),
],
),
);
}
}
You need to provide onChanged and other events in your custom widget, even if you are just providing these to the underlying TextField. In other words, you need to pass the onChanged function down through your custom widget.
For example:
MyAwesomeTextField extends StatelessWidget {
/// Callback for being notified of changes to the text field
/// This should have a proper type, I'm just using Function for simplicity
final Function onChanged;
// Make the onChanged property settable through the constructor
MyAwesomeTextField({this.onChanged});
Widget build(BuildContext context) {
// Construct your widget tree, and pass your onChanged function through
return TextField(onChanged: this.onChanged);
}
}
Then when you're using it, your custom widget will have an onChanged event:
...
MyCustomWidget(onChanged: (value) => print(value) )
...
Related
Hello everybody working on a project and get this code from a repo and have some types errors and I cant understand them because I cant have the knowledge to solve and I didnt find nothing on google about these errors.
The problem is the #require this.#property and error as null value. I cant understand the problem, can explain me the problem?
Home Widget
class Home extends StatelessWidget {
const Home({
Key? key,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: [
Container(
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
AppColors.backgroundFadedColor,
AppColors.backgroundColor,
],
stops: [0.0, 1],
),
),
),
SafeArea(
child: _TodoListContent(
todos: fakeData,
),
),
const Align(
alignment: Alignment.bottomRight,
child: AddTodoButton(),
)
],
),
);
}
}
class _TodoListContent extends StatelessWidget {
const _TodoListContent({
Key? key,
#required this.todos,
}) : super(key: key);
final List<Todo> todos;
#override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: todos.length,
padding: const EdgeInsets.all(16),
itemBuilder: (context, index) {
final _todo = todos[index];
return _TodoCard(todo: _todo);
},
);
}
}
class _TodoCard extends StatelessWidget {
const _TodoCard({
Key? key,
#required this.todo,
}) : super(key: key);
final Todo todo;
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
Navigator.of(context).push(
HeroDialogRoute(
builder: (context) => Center(
child: _TodoPopupCard(todo: todo),
),
),
);
},
child: Hero(
tag: todo.id,
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: Material(
color: AppColors.cardColor,
borderRadius: BorderRadius.circular(12),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: <Widget>[
_TodoTitle(title: todo.description),
const SizedBox(
height: 8,
),
if (todo.items.length != 0) ...[
const Divider(),
_TodoItemsBox(items: todo.items),
]
],
),
),
),
),
),
);
}
}
class _TodoTitle extends StatelessWidget {
const _TodoTitle({
Key? key,
#required this.title,
}) : super(key: key);
final String title;
#override
Widget build(BuildContext context) {
return Text(
title,
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 16),
);
}
}
class _TodoPopupCard extends StatelessWidget {
const _TodoPopupCard({Key key, this.todo}) : super(key: key);
final Todo todo;
#override
Widget build(BuildContext context) {
return Hero(
tag: todo.id,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Material(
borderRadius: BorderRadius.circular(16),
color: AppColors.cardColor,
child: SizedBox(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
_TodoTitle(title: todo.description),
const SizedBox(
height: 8,
),
if (todo.items.length != 0) ...[
const Divider(),
_TodoItemsBox(items: todo.items),
],
Container(
margin: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.black12,
borderRadius: BorderRadius.circular(8),
),
child: const TextField(
maxLines: 8,
cursorColor: Colors.white,
decoration: InputDecoration(
contentPadding: EdgeInsets.all(8),
hintText: 'Write a note...',
border: InputBorder.none),
),
),
],
),
),
),
),
),
),
);
}
}
class _TodoItemsBox extends StatelessWidget {
const _TodoItemsBox({
Key? key,
#required this.items,
}) : super(key: key);
final List<Item> items;
#override
Widget build(BuildContext context) {
return Column(
children: [
for (final item in items) _TodoItemTile(item: item),
],
);
}
}
class _TodoItemTile extends StatefulWidget {
const _TodoItemTile({
Key? key,
#required this.item,
}) : super(key: key);
final Item item;
#override
_TodoItemTileState createState() => _TodoItemTileState();
}
class _TodoItemTileState extends State<_TodoItemTile> {
void _onChanged(bool val) {
setState(() {
widget.item.completed = val;
});
}
#override
Widget build(BuildContext context) {
return ListTile(
leading: Checkbox(
onChanged: _onChanged,
value: widget.item.completed,
),
title: Text(widget.item.description),
);
}
}
On classes properties #required this.# error: The parameter '#' can't have a value of 'null' because of its type, but the implicit default value is 'null'. Try adding either an explicit non-'null' default value or the 'required' modifier.
Models file
import 'package:meta/meta.dart';
class Todo {
const Todo({
#required this.id,
#required this.description,
this.items,
});
final String id;
final String description;
final List<Item> items;
}
class Item {
Item({
#required this.id,
this.description = '',
this.completed = false,
});
final String id;
final String description;
bool completed;
}
On code
Todo
#required this.id,
#required this.description,
this.items,
and
Item
#required this.id,
error: The parameter '#' can't have a value of 'null' because of its type, but the implicit default value is 'null'. Try adding either an explicit non-'null' default value or the 'required' modifier.
TLDR: change #required => required
You are working with "null safety" enabled. This is a good thing, and helps avoid bugs by catching them at compile time.
In Todo, you have a field final String id;. With null safety enabled, null is not a valid value for a String (i.e. String id = null; is a compile time error).
This means that you can safely call methods on id without worrying about it being null, but it also means you must give it a value.
Consider:
final todo = Todo();
print(todo.id); // what happens here?
If the compiler allowed your code, this would print "null". But id is a String, not a String? (a nullable string), so this can't be allowed.
The main issue you are facing is the use of #required rather than just required. #required is a metadata annotation, that allows development tools (e.g. your IDE) to give helpful warnings.
On the other hand, required is a language keyword (when null safety is enabled). If you have a non-null named parameter, you must either:
mark it as required so it is a compile time error if you fail to provide it
give it a default value, so it will be non-null even if you don't provide it.
I'm trying to make a calculator app using flutter where instead of taking input through the keyboard I want to take input through some buttons. The issue comes when I press a button but it does not display the corresponding data in the Text widget above.
All my classes are stateless except for the first MyApp class, which is Stateful.
I tried by creating a general variable outside all the classes and using that to transfer text from the button class to the display class but that did not work.
The general variable is "_calcText"
class DisplayAnswer extends StatelessWidget {
final String _text;
DisplayAnswer(this._text);
#override
Widget build(BuildContext context) {
return Expanded(
flex: 2,
child: Material(
color: Colors.greenAccent,
child: Ink(
padding: EdgeInsets.all(10.0),
child: Center(
child: Container(
constraints: BoxConstraints.expand(),
decoration: BoxDecoration(border: Border.all(color: Colors.black, width: 5.0), color: Colors.white),
child: Text(_text,style: TextStyle(fontSize: 50.0), textAlign: TextAlign.center,),
),
),
),
),
);
}
}
class NumButtons extends StatelessWidget {
final String _number;
NumButtons(this._number);
#override
Widget build(BuildContext context) {
return FlatButton(
onPressed: () {
_calcText = _calcText + _number;
print(_calcText);
DisplayAnswer(_calcText);
} ,
child: Text(_number.toString(), style: TextStyle(fontSize: 30.0),),
color: Colors.white
);
}
}
I want to display the value of _calcText in the Text widget of DisplayAnswer. I want _calcText to also change as other buttons are clicked, ie; if 2 is clicked Text should only display 2, if 5 is clicked after that it should display 25
The full code is here:
https://drive.google.com/open?id=1C4MLAkjowloicbjBP_uV8BfpPzhz4Yxf
Use Statefull widget insted of StatelessWidget.
Call the setState() method on onPressed function, after adition operation. It will build your widget with a new value.
On DisplayAnswer, you have to make a function to increment the value, than pass this function as parameter to NumButtons.
Pass a callback Function to NumButtons, like:
class NumButtons extends StatelessWidget {
final String _number;
final Function callback;
...
I'm trying to create an app with block pattern and flutter_block library. It is working, but now I want to reduce the code.
I have a lot of:
dart
Padding(
padding:
EdgeInsets.symmetric(horizontal: 10.0, vertical: 5.0),
child: TextField(
inputFormatters: [
BlacklistingTextInputFormatter(RegExp("[a-zA-Z]"))
],
decoration: InputDecoration(
labelText: 'label text'),
keyboardType: TextInputType.number,
controller: _service,
onChanged: (value) =>
{_prefsBloc.dispatch(SetServicePrefs(value))},
),
),
I'm transforming that in a widget:
dart
class SettingTextField extends StatelessWidget {
final String text;
final String labelText;
SettingTextField({this.text, this.labelText});
#override
Widget build(BuildContext context) {
final PrefsBloc _prefsBloc = BlocProvider.of<PrefsBloc>(context);
final TextEditingController _controller = TextEditingController();
_controller.text = this.text;
if (this.text != null) {
_controller.selection = TextSelection.collapsed(offset: this.text.length);
}
return Padding(
padding: EdgeInsets.symmetric(horizontal: 10.0, vertical: 5.0),
child: TextField(
inputFormatters: [BlacklistingTextInputFormatter(RegExp("[a-zA-Z]"))],
decoration: InputDecoration(labelText: this.labelText),
keyboardType: TextInputType.number,
controller: _controller,
onChanged: (value) {}
);
}
}
For each field, I have a different event to dispatch to block like:
_prefsBloc.dispatch(SetServicePrefs(value))
But I really don't know how to pass the type SetServicePrefs to my widget and use it in the onChanged function.
How do I solve this problem?
You can add additional callback field to SettingTextField:
class SettingTextField extends StatelessWidget {
final String text;
final String labelText;
final Function(PrefsBloc bloc, value) onChanged;
...
TextField(
inputFormatters: [BlacklistingTextInputFormatter(RegExp("[a-zA-Z]"))],
decoration: InputDecoration(labelText: this.labelText),
keyboardType: TextInputType.number,
controller: _controller,
onChanged: (value) => onChanged(_prefsBloc, value)
)
...
}
Then call SettingTextField:
SettingTextField(
...
onChanged: (bloc, value) => bloc.dispatch(SomeEventType(value))
...
)
I have a widget with a TextField that I'm initializing from a StreamBuilder, trying to use the bloc pattern. A Contact model is coming in through the stream. This is working to initially populate the TextField. My question is about reading the value after the user updates the TextField and then presses the Save button. How do I read the value from the TextField. I've included a simple example of what I'm trying to do.
void getTextValues() {
//???
}
#override
Widget build(BuildContext context) {
return StreamBuilder<Contact>(
stream: bloc.getContact,
builder: (context, contact) {
return Column(
children: <Widget>[
TextField(
controller: TextEditingController(text: contact.data.name),
),
new RaisedButton(
padding: const EdgeInsets.all(8.0),
textColor: Colors.white,
color: Colors.blue,
onPressed: getTextValues,
child: new Text("Save"),
),
],
);
},
);
}
I think I could declare a TextEditingController and assign that to the controller property but I don't see a way to give that an initial value from the StreamBuilder. Am I missing something there?
TextEditingController nameController = TextEditingController();
....
controller: nameController,
I think I could declare a TextEditingController and assign that to the
controller property but I don't see a way to give that an initial
value from the StreamBuilder.
There is a way. Change your builder code to this:
builder: (context, contact) {
nameController.value = TextEditingValue(text: contact.data.name);
return Column(
children: <Widget>[
TextField(
controller: nameController,
),
new RaisedButton(
padding: const EdgeInsets.all(8.0),
textColor: Colors.white,
color: Colors.blue,
onPressed: getTextValues,
child: new Text("Save"),
),
],
);
},
To assign TextEditingController a default value, use
TextEditingController _controller = TextEditingController(text: "Default value");
And to retrieve the value from a controller you can use
_controller.value
I want to build a form where I have multiple TextField widgets, and want to have a button that composes and e-mail when pressed, by passing the data gathered from these fields.
For this, I started building an InheritedWidget to contain TextField-s, and based on the action passed in the constructor - functionality not yet included in the code below - it would return a different text from via toString method override.
As I understood, an InheritedWidget holds it's value as long as it is part of the current Widget tree (so, for example, if I navigate from the form it gets destroyed and the value is lost).
Here is how I built my TextForm using InheritedWidget:
class TextInheritedWidget extends InheritedWidget {
const TextInheritedWidget({
Key key,
this.text,
Widget child}) : super(key: key, child: child);
final String text;
#override
bool updateShouldNotify(TextInheritedWidget old) {
return text != old.text;
}
static TextInheritedWidget of(BuildContext context) {
return context.inheritFromWidgetOfExactType(TextInheritedWidget);
}
}
class TextInputWidget extends StatefulWidget {
#override
createState() => new TextInputWidgetState();
}
class TextInputWidgetState extends State<TextInputWidget> {
String text;
TextEditingController textInputController = new TextEditingController();
#override
Widget build(BuildContext context) {
return new TextInheritedWidget(
text: text,
child: new TextField(
controller: textInputController,
decoration: new InputDecoration(
hintText: adoptionHintText
),
onChanged: (text) {
setState(() {
this.text = textInputController.text;
});
},
),
);
}
#override
String toString({DiagnosticLevel minLevel: DiagnosticLevel.debug}) {
// TODO: implement toString
return 'Név: ' + text;
}
}
And here is the button that launches the e-mail sending:
TextInputWidget nameInputWidget = new TextInputWidget();
TextInheritedWidget inherited = new TextInheritedWidget();
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('Örökbefogadás'),
),
body: new Container(
padding: const EdgeInsets.all(5.0),
child: new ListView(
children: <Widget>[
new Text('Név:', style: infoText16BlackBold,),
nameInputWidget,
new FlatButton(onPressed: () {
launchAdoptionEmail(nameInputWidget.toString(), 'kutya');
},
child: new Text('Jelentkezem'))
],
),
),
);
}
My problem is that the nameInputWidget.toString() simply returns TextInputWidget (class name) and I can't seem to find a way to access the TextInputWidgetState.toString() method.
I know that TextInheritedWidget holds the text value properly, but I'm not sure how I could access that via my nameInputWidget object.
Shouldn't the TextInputWidget be able to access the data via the context the InheritedWidget uses to determine which Widget to update and store the value of?
This is not possible. Only children of an InheritedWidget can access it's properties
The solution would be to have your InheritedWidget somewhere above your Button. But that imply you'd have to refactor to take this into account.
Following Rémi's remarks, I came up with a working solution, albeit I'm pretty sure it is not the best and not to be followed on a massive scale, but should work fine for a couple of fields.
The solution comes by handling all TextField widgets inside one single State, alongside the e-mail composition.
In order to achieve a relatively clean code, we can use a custom function that build an input field with the appropriate data label, which accepts two input parameters: a String and a TextEditingController.
The label is also used to determine which variable the setState() method will pass the newly submitted text.
Widget buildTextInputRow(var label, TextEditingController textEditingController) {
return new ListView(
shrinkWrap: true,
children: <Widget>[
new Row(
children: <Widget>[
new Expanded(
child: new Container(
padding: const EdgeInsets.only(left: 5.0, top: 2.0, right: 5.0 ),
child: new Text(label, style: infoText16BlackBold)),
),
],
),
new Row(
children: <Widget>[
new Expanded(
child: new Container(
padding: const EdgeInsets.only(left: 5.0, right: 5.0),
child: new TextField(
controller: textEditingController,
decoration: new InputDecoration(hintText: adoptionHintText),
onChanged: (String str) {
setState(() {
switch(label) {
case 'Név':
tempName = 'Név: ' + textEditingController.text + '\r\n';
break;
case 'Kor':
tempAge = 'Kor: ' + textEditingController.text + '\r\n';
break;
case 'Cím':
tempAddress = 'Cím: ' + textEditingController.text + '\r\n';
break;
default:
break;
}
});
}
)),
),
],
)
],
);
}
The problem is obviously that you will need a new TextEditingController and a new String to store every new input you want the user to enter:
TextEditingController nameInputController = new TextEditingController();
var tempName;
TextEditingController ageInputController = new TextEditingController();
var tempAge;
TextEditingController addressInputController = new TextEditingController();
var tempAddress;
This will result in a lot of extra lines if you have a lot of fields, and you will also have to update the composeEmail() method accordingly, and the more fields you have, you will be more likely to forget a couple.
var emailBody;
composeEmail(){
emailBody = tempName + tempAge + tempAddress;
return emailBody;
}
Finally, it is time to build the form:
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('Örökbefogadás'),
),
body: new ListView(
children: <Widget>[
buildTextInputRow('Név', nameInputController),
buildTextInputRow('Kor', ageInputController),
buildTextInputRow('Cím', addressInputController),
new FlatButton(onPressed: () { print(composeEmail()); }, child: new Text('test'))
],
),
);
}
For convenience, I just printed the e-mail body to the console while testing
I/flutter ( 9637): Név: Zoli
I/flutter ( 9637): Kor: 28
I/flutter ( 9637): Cím: Budapest
All this is handled in a single State.