Disable button while typing on Input Flutter - flutter

I want disable a button while I'm typing on input.
But the code below that I 've wrote doesn't work because the button is disabled only when I "confirm" input with keyboard, but I want disabled input while I'm typing on input.
TextEditingController myController = TextEditingController();
bool isValid = false;
#override
Widget build(BuildContext context) {
Column(
children: <Widget>[
TextField(
controller: myController,
onChanged: (value){
setState(() {
isValid = (value.isEmpty || double.tryParse(value) == null) ? false : true;
});
},
decoration: InputDecoration(
border: InputBorder.none,
hintText: 'Enter a search term'
),
),
RaisedButton(
disabledColor: Colors.grey,
child: Text("${AppLocalizations.of(context).translate("test")}"),
onPressed: isValid ? () { print("test") }:null,
),
],
)
}

You can Also use myController.addListener()
To check result just copy paste below code in DartPad
When you enter number in TextField the button will enable
SAMPLE CODE
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
TextEditingController myController = TextEditingController();
bool isValid = false;
#override
void dispose() {
// Clean up your controller when the Widget is disposed
myController.dispose();
super.dispose();
}
#override
void initState() {
// TODO: implement initState
super.initState();
myController.text = '';
myController.addListener((){
print("Get Value: ${myController.text}");
setState(() {
isValid = (myController.text.isEmpty || double.tryParse(myController.text) == null)
? false
: true;
});
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Column(
children: <Widget>[
TextField(
controller: myController,
onChanged: (value) {
setState(() {
});
},
decoration: InputDecoration(
border: InputBorder.none, hintText: 'Enter a search term'),
),
RaisedButton(
disabledColor: Colors.grey,
child: Text("Click Me"),
onPressed: isValid
? () {
print("test");
}
: null,
),
],
),
);
}
}

Use FocusNode
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: DemoPage(),
debugShowCheckedModeBanner: false,
theme: ThemeData(primaryColor: Colors.white),
);
}
}
class DemoPage extends StatefulWidget {
#override
_DemoPageState createState() => _DemoPageState();
}
class _DemoPageState extends State<DemoPage> {
TextEditingController textField1Ctrl;
TextEditingController textField2Ctrl;
FocusNode focusNode1;
FocusNode focusNode2;
#override
void initState() {
textField1Ctrl = TextEditingController();
textField2Ctrl = TextEditingController();
focusNode1 = FocusNode()..addListener(_rebuildOnFocusChange);
focusNode2 = FocusNode()..addListener(_rebuildOnFocusChange);
super.initState();
}
void _rebuildOnFocusChange() => setState(() {});
void _onButton1Pressed() {}
void _onButton2Pressed() {}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text("Disable Button When Text Field has focus"),
Row(
children: <Widget>[
Expanded(
child: TextField(
controller: textField1Ctrl,
focusNode: focusNode1,
),
),
RaisedButton(
child: Text("Button 1"),
onPressed: focusNode1.hasFocus ? null : _onButton1Pressed,
)
],
),
const SizedBox(height: 40.0),
Text("Disable Button When TextField is Empty or has focus"),
Row(
children: <Widget>[
Expanded(
child: TextField(
controller: textField2Ctrl,
focusNode: focusNode2,
),
),
RaisedButton(
child: Text("Button 2"),
onPressed: focusNode2.hasFocus || textField2Ctrl.text.isEmpty
? null
: _onButton2Pressed,
)
],
),
],
),
),
);
}
}
Demo: DartPad

Related

Get the user input number and compare that with a simple database on flutter

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

Unable to reflect updated parent state in showModalBottomSheet

I am relatively new to Flutter and while I really like it I'm struggling to find a way to have state values in the parent be updated in showModalBottomSheet. I think I understand the issue to be that the values aren't reflecting in showModalBottomSheet when they change in the parent because showModalBottomSheet doesn't get rebuilt when the state updates.
I am storing title and content in the parent because I was also hoping to use it for editing as well as creating todos. I figured the showModalBottomSheet could be shared for both. I am attaching a picture on the simulator. What I am expecting is that when title changes (i.e. is no longer an empty string) then the Add To Do button should become enabled but it currently stays disabled unless I close the modal and re-open it.
Any help or insight would be greatly appreciated. Below is the code in my main.dart file which has showModalBottomSheet and has the state values that need to be passed down. NewToDo contains the text fields in the modal that capture the values and updates the state in main accordingly.
** EDIT **
I have seen this link but it doesn't really explain how to pass state from a parent widget down to a showBottomModalSheet widget, it just shows how to manage state within a showBottomModalSheet widget. My goal is to have the state change from within main to be able to be picked within showBottomModalSheet.
main.dart
import 'package:flutter/material.dart';
import './todoitem.dart';
import './todolist.dart';
import 'classes/todo.dart';
import './newtodo.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'To Do Homie',
theme: ThemeData(
primarySwatch: Colors.deepPurple,
),
home: const MyHomePage(title: "It's To Do's My Guy"),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({
Key? key,
required this.title,
}) : super(key: key);
final String title;
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
String content = '';
String title = '';
int maxId = 0;
ToDo? _todo;
final titleController = TextEditingController();
final contentController = TextEditingController();
List<ToDo> _todos = [];
void _addTodo(){
final todo = ToDo (
title: title,
id: maxId,
isDone: false,
content: content
);
if (_todo != null){
setState(() {
_todos[_todos.indexOf(_todo!)] = todo;
});
} else {
setState(() {
_todos.add(todo);
});
}
setState(() {
content = '';
maxId = maxId++;
title = '';
_todo = null;
});
contentController.text = '';
titleController.text = '';
}
#override
void initState() {
super.initState();
titleController.addListener(_handleTitleChange);
contentController.addListener(_handleContentChange);
futureAlbum = fetchAlbum();
}
void _handleTitleChange() {
setState(() {
title = titleController.text;
});
}
void _handleContentChange() {
setState(() {
content = contentController.text;
});
}
void _editTodo(ToDo todoitem){
setState(() {
_todo = todoitem;
content = todoitem.content;
title = todoitem.title;
});
contentController.text = todoitem.content;
titleController.text = todoitem.title;
}
void _deleteToDo(ToDo todoitem){
setState(() {
_todos = List.from(_todos)..removeAt(_todos.indexOf(todoitem));
});
}
void _clear(){
contentController.text = '';
titleController.text = '';
setState(() {
content = '';
title = '';
_todo = null;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: SingleChildScrollView(
child: Center(
child: Container(
alignment: Alignment.topCenter,
child: ToDoList(_todos, _editTodo, _deleteToDo)
),
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
showModalBottomSheet<void>(
context: context,
builder: (BuildContext context) {
print(context);
return Container(child:NewToDo(titleController, contentController, _addTodo, _clear, _todo),);
});
},
child: const Icon(Icons.add),
backgroundColor: Colors.deepPurple,
),
);
}
}
NewToDo.dart
import 'package:flutter/material.dart';
import './classes/todo.dart';
class NewToDo extends StatelessWidget {
final Function _addTodo;
final Function _clear;
final ToDo? _todo;
final TextEditingController titleController;
final TextEditingController contentController;
const NewToDo(this.titleController, this.contentController, this._addTodo, this._clear, this._todo, {Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return
Column(children: [
TextField(
decoration: const InputDecoration(
labelText: 'Title',
),
controller: titleController,
autofocus: true,
),
TextField(
decoration: const InputDecoration(
labelText: 'Details',
),
controller: contentController,
autofocus: true,
),
ButtonBar(
alignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: titleController.text.isNotEmpty ? () => _addTodo() : null,
child: Text(_todo != null ? 'Edit To Do' : 'Add To Do'),
style: ButtonStyle(
backgroundColor: titleController.text.isNotEmpty ? MaterialStateProperty.all<Color>(Colors.deepPurple) : null,
overlayColor: MaterialStateProperty.all<Color>(Colors.purple),
),
),
Visibility (
visible: titleController.text.isNotEmpty || contentController.text.isNotEmpty,
child: ElevatedButton(
onPressed: () => _clear(),
child: const Text('Clear'),
)),
])
],
);
}
}
TextControllers are listenable. You can just wrap your Column in two ValueListenables (one for each controller) and that will tell that widget to update whenever their values are updated.
#override
Widget build(BuildContext context) {
return ValueListenableBuilder(
valueListenable: contentController,
builder: (context, _content, child) {
return ValueListenableBuilder(
valueListenable: titleController,
builder: (context, _title, child) {
return Column(
children: [
TextField(
decoration: const InputDecoration(
labelText: 'Title',
),
controller: titleController,
autofocus: true,
),
TextField(
decoration: const InputDecoration(
labelText: 'Details',
),
controller: contentController,
autofocus: true,
),
ButtonBar(
alignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed:
titleController.text.isNotEmpty ? () => _addTodo() : null,
child: Text(_todo != null ? 'Edit To Do' : 'Add To Do'),
style: ButtonStyle(
backgroundColor: titleController.text.isNotEmpty
? MaterialStateProperty.all<Color>(Colors.deepPurple)
: null,
overlayColor: MaterialStateProperty.all<Color>(Colors.purple),
),
),
Visibility(
visible: titleController.text.isNotEmpty ||
contentController.text.isNotEmpty,
child: ElevatedButton(
onPressed: () => _clear(),
child: const Text('Clear'),
),
),
],
)
],
);
},
);
},
);
Another more general alternative I can think of is to use Provider (or, if you're familiar enough, regular InheritedWidgets) and the pattern suggested in its readme:
class Example extends StatefulWidget {
const Example({Key key, this.child}) : super(key: key);
final Widget child;
#override
ExampleState createState() => ExampleState();
}
class ExampleState extends State<Example> {
int _count;
void increment() {
setState(() {
_count++;
});
}
#override
Widget build(BuildContext context) {
return Provider.value(
value: _count,
child: Provider.value(
value: this,
child: widget.child,
),
);
}
}
where it suggests reading the count like this:
return Text(context.watch<int>().toString());
Except I'm guessing you can just provide the whole state of the widget to descenents by replacing _count with this to refer to the whole stateful widget. Don't know if this is recommended though.
ValueListenables would be my first choice and then maybe hooks to simplify their use though.

How to add multiple textfields and get the values from it in flutter

I'm trying to add multiple textformfileds on click of add more Button and trying to access the values of all fields on form submit.
I'm not getting the values of dynamic added fields.
I follow this url to dynamically added textformfields.
And here is my code.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Dynamic TextFormFields',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyForm(),
debugShowCheckedModeBanner: false,
);
}
}
class MyForm extends StatefulWidget {
#override
_MyFormState createState() => _MyFormState();
}
class _MyFormState extends State<MyForm> {
final _formKey = GlobalKey<FormState>();
TextEditingController _nameController;
TextEditingController _name1Controller;
static List<String> friendsList = [null];
static List<String> friendsList1 = [null];
#override
void initState() {
super.initState();
_nameController = TextEditingController();
_name1Controller = TextEditingController();
}
#override
void dispose() {
_nameController.dispose();
_name1Controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.grey[200],
appBar: AppBar(
title: Text('Dynamic TextFormFields'),
),
body: Form(
key: _formKey,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// name textfield
Padding(
padding: const EdgeInsets.only(right: 32.0),
child: TextFormField(
controller: _nameController,
decoration: InputDecoration(hintText: 'Enter your name'),
validator: (v) {
if (v.trim().isEmpty) return 'Please enter something';
return null;
},
),
),
SizedBox(
height: 20,
),
Text(
'Add Friends',
style: TextStyle(fontWeight: FontWeight.w700, fontSize: 16),
),
..._getFriends(),
SizedBox(
height: 40,
),
FlatButton(
onPressed: () {
if (_formKey.currentState.validate()) {
_formKey.currentState.save();
print(_MyFormState.friendsList);
print(_MyFormState.friendsList1);
}
},
child: Text('Submit'),
color: Colors.green,
),
],
),
),
),
);
}
/// get firends text-fields
List<Widget> _getFriends() {
List<Widget> friendsTextFields = [];
for (int i = 0; i < friendsList.length; i++) {
friendsTextFields.addAll(
[
Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: Row(
children: [
Expanded(child: FriendTextFields(i)),
Expanded(child: FriendTextFields1(i)),
SizedBox(
width: 16,
),
// we need add button at last friends row
_addRemoveButton(i == friendsList.length - 1, i),
],
),
)
],
);
}
return friendsTextFields;
}
/// add / remove button
Widget _addRemoveButton(bool add, int index) {
return InkWell(
onTap: () {
if (add) {
// add new text-fields at the top of all friends textfields
friendsList.insert(0, null);
} else
friendsList.removeAt(index);
setState(() {});
},
child: Container(
width: 30,
height: 30,
decoration: BoxDecoration(
color: (add) ? Colors.green : Colors.red,
borderRadius: BorderRadius.circular(20),
),
child: Icon(
(add) ? Icons.add : Icons.remove,
color: Colors.white,
),
),
);
}
}
class FriendTextFields extends StatefulWidget {
final int index;
FriendTextFields(this.index);
#override
_FriendTextFieldsState createState() => _FriendTextFieldsState();
}
class _FriendTextFieldsState extends State<FriendTextFields> {
TextEditingController _nameController;
#override
void initState() {
super.initState();
_nameController = TextEditingController();
}
#override
void dispose() {
_nameController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
_nameController.text = _MyFormState.friendsList[widget.index] ?? '';
});
return TextFormField(
controller: _nameController,
onChanged: (v) => _MyFormState.friendsList[widget.index] = v,
decoration: InputDecoration(hintText: 'textbox 1'),
validator: (v) {
if (v.trim().isEmpty) return 'Please enter something';
return null;
},
);
}
}
//////////////////////////////////////////////////////////////////
class FriendTextFields1 extends StatefulWidget {
final int index;
FriendTextFields1(this.index);
#override
_FriendTextFields1State createState() => _FriendTextFields1State();
}
class _FriendTextFields1State extends State<FriendTextFields1> {
TextEditingController _name1Controller;
#override
void initState() {
super.initState();
_name1Controller = TextEditingController();
}
#override
void dispose() {
_name1Controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
_name1Controller.text = _MyFormState.friendsList1[widget.index] ?? '';
});
return TextFormField(
controller: _name1Controller,
onChanged: (v) => {
_MyFormState.friendsList1[widget.index] = v,
},
decoration: InputDecoration(hintText: 'textbox 2'),
validator: (v) {
if (v.trim().isEmpty) return 'Please enter something';
return null;
},
);
}
}
You can create a list of TextEditingController just like a list the list of TextFields you have created.
Then when you add a new textField to the list, add a new controller to the controllerList too.
Then you can easily fetch the data from any textfield in the list using its index something like:
String secondTextFieldText = controllerList[1].text;

My setState does not refresh after receiving user input. I'm trying to build a chat page

`final myController = TextEditingController();
#override
void dispose() {
myController.dispose();
}`
This creates a controller for the text input
`Widget textField(String hint, TextInputType type) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: CupertinoTextField(
minLines: 1,
maxLines: null,
controller: myController,
textCapitalization: TextCapitalization.sentences,
placeholder: hint,
placeholderStyle: TextStyle(color: hintcolor),
keyboardType: type,
style: TextStyle(color: secondarycolor),
),
);
}`
This creates the text field with the controller
`Expanded(child: textField('Type a message', TextInputType.multiline)),
IconButton(
icon: Icon(
Icons.send,
color: profilesecondarycolor,
),
onPressed: () {
messagesTo.add(myController.text);
Future.delayed(Duration(seconds: 1), () {
return setState(() {
messages = messagesTo.map((content) {
Card(child: Text(content));
}).toList();
});
});
})`
This is the button that is supposed to add the text to the list
`body: ListView(children: messagesTo == null ? chats : messages),`
This is supposed to show the list
`List<Widget> messages;
List<String> messagesTo;
List<Widget> chats = [Text('Welcome')];`
Defining the terms
It just shows welcome and when i click the button, nothing happens... please help
You can copy paste run full code below
Some parts of your code need to change, you can check full code for detail
code snippet
List<Widget> messages = [];
List<String> messagesTo = [];
Widget chats = Text('Welcome');
onPressed: () {
messagesTo.add(myController.text);
Future.delayed(Duration(seconds: 1), () {
setState(() {
messages = [];
messagesTo.forEach((content) =>
messages.add(Card(child: Text(content))));
});
});
}
...
messages.length == 0
? chats
: Container(
height: 200,
child: ListView(shrinkWrap: true, children: messages)),
working demo
full code
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final myController = TextEditingController();
List<Widget> messages = [];
List<String> messagesTo = [];
Widget chats = Text('Welcome');
Widget textField(String hint, TextInputType type) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: CupertinoTextField(
minLines: 1,
maxLines: null,
controller: myController,
textCapitalization: TextCapitalization.sentences,
placeholder: hint,
placeholderStyle: TextStyle(color: Colors.blue),
keyboardType: type,
style: TextStyle(color: Colors.red),
),
);
}
#override
void dispose() {
myController.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: true,
//resizeToAvoidBottomPadding: true,
appBar: AppBar(
title: Text(widget.title),
),
body: SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
messages.length == 0
? chats
: Container(
height: 200,
child: ListView(shrinkWrap: true, children: messages)),
Container(
height: 50,
child: textField('Type a message', TextInputType.multiline)),
IconButton(
icon: Icon(
Icons.send,
color: Colors.yellow,
),
onPressed: () {
messagesTo.add(myController.text);
Future.delayed(Duration(seconds: 1), () {
setState(() {
messages = [];
messagesTo.forEach((content) =>
messages.add(Card(child: Text(content))));
});
});
}),
],
),
),
);
}
}

break a form into multiple widget and interact with those widget in flutter

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