I am trying to create a page with more than one TextField widgets in flutter. Number of textfields to display are determined at runtime. while creating those textfields a controller from the controller array is attached to each textfield.
Everything is working as expected. but doen't matter which textfield i click, i always get the callback of all the textfields in a single onChanged function.
Therefore, i am not able to detect that value of which textfield is changed.
Please refer to code for more insight.
In-Short, i just want to know how to get the instance of textfield controller which is currently active.
Could somebody please let me know what i am doing wrong.
Thanks
List<TextEditingController> _controllers = <TextEditingController>[];
int controllersAttached = -1;
#override
Widget build(BuildContext context) {
controllersAttached += 1;
for (int i = 0;i < fibProvider.quesTextList.length;i++) ...<dynamic>[
TextField(
autofocus: false,
cursorColor: const Color(0xFFD8D8D8),
maxLines: 1,
textAlign: TextAlign.center,
controller: _controllers[controllersAttached %fibProvider.numberOfBlanks],
onChanged: (String data) {
// here i can distinguish between the controllers depending upon the text
entered in them. but if there are 3 textfields and the same data is
entered in all the three then this code only returns the first
controller with the matched value.
final int index = _controllers.indexWhere(TextEditingController item) {
return data.compareTo(
item.text.toString()) == 0;
});
},
),
]
}
You can just pass i variable to onChanged callback inside collection-for:
class _MyHomePageState extends State<MyHomePage> {
final int _fields = 10;
List<TextEditingController> controllers;
#override
void initState() {
super.initState();
controllers = List.generate(_fields, (i) => TextEditingController());
}
#override
void dispose() {
controllers.forEach((c) => c.dispose());
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title)
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
for (int i = 0; i < _fields; i++)
TextField(
controller: controllers[i],
onChanged: (value) {
final controller = controllers[i];
print('Current field index is $i and new value is $value');
},
),
],
),
)
);
}
}
Related
I am experimenting with flutter and I have a very simple code as follows:
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class Test extends StatefulWidget {
#override
_TestState createState() => _TestState();
}
class _TestState extends State<Test> {
static double d = 0;
static TextEditingController editingController = TextEditingController();
#override
void initState() {
editingController.addListener(() {
setState(() {});
});
super.initState();
}
#override
void dispose() {
editingController.dispose();
super.dispose();
}
Calc calc = Calc(d: d, e: editingController);
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Padding(
padding: const EdgeInsets.all(25),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
TextField(
keyboardType: TextInputType.number,
onChanged: (value) {
setState(() {
if (value.isNotEmpty) {
d = double.parse(value);
} else if (value.isEmpty) {
d = 0;
}
});
},
inputFormatters: [
FilteringTextInputFormatter.allow(
RegExp(r'(^(\d{1,})\.?(\d{0,2}))'),
),
],
),
TextField(
keyboardType: TextInputType.number,
controller: editingController,
inputFormatters: [
FilteringTextInputFormatter.allow(
RegExp(r'(^(\d{1,})\.?(\d{0,2}))'),
),
],
),
Text(
'First Text Field Value + 2 = \n${calc.dString()}',
style: TextStyle(fontSize: 30, color: Colors.purpleAccent),
),
Text(
calc.eString(),
style: TextStyle(fontSize: 30, color: Colors.deepOrangeAccent),
),
],
),
),
),
);
}
}
class Calc {
final double d;
final TextEditingController e;
Calc({this.d, this.e});
String dString() {
double result = d + 2;
return result.toStringAsFixed(0);
}
String eString() {
return e.text;
}
}
As we can see I am passing both the text fields' values into another class for some math and getting the results. These results are displayed using the Text widgets.
For the 1st TextField, I have used onChange method, and for the 2nd TextField, I have used TextEditingController.
I get return value for 2nd TextField from the Calc class, but not for the 1st TextField!
I think I am missing something basic and I did not find any solution so far. Can you please help me what am I missing here.
1st of all, you are creating just a single object of Calc,
yes as you can see your 2nd textField update perfectly because it's using TextEditingController but for the 1st one, it just call once and become 2 because of dString(), while on 1st run d becomes 0 passed on Calc.
if you want to use Calc to update text, you can simply put it inside build method like this , i dont suggest it, you can use callBackMethod to handle this or use another TextEditingController.
Hope you get it now
#override
Widget build(BuildContext context) {
Calc calc = Calc(d: d, e: editingController);
return Scaffold(
body: Center(
Your Calc Object is not being affect by setState() call. To run be able to get value of the calcobject, run it in you onChanged() function.
I have a Posts screen where list of posts are displayed. I want to add a textformfield at the bottom area of each post so that users can enter their comments on particular post. As we know that we cannot create a TextEditingController for multiple textformfields.
How can I create, show and use textformfield with the each post for entering users comments?
I am sure there are multiple ways to do this, but here is one fairly simple approach.
Create a Map where the key is a TextFormField and the value is a TextEditingController.
Map<TextFormField, TextEditingController> fields = new Map<TextFormField, TextEditingController>();
Create a new controller and text field for each post and add them to the Map.
fields[text] = controller;
When you want to get the values, you can pass the text field into a method that gets the controller for that field from the Map.
Here is a very basic example of how this can work:
import 'package:flutter/material.dart';
class DynamicTextFieldsWithControllers extends StatefulWidget {
#override
State<StatefulWidget> createState() => DynamicTextFieldsWithControllersState();
}
class DynamicTextFieldsWithControllersState extends State<DynamicTextFieldsWithControllers> {
Map<TextFormField, TextEditingController> fields = new Map<TextFormField, TextEditingController>();
#override
Widget build(BuildContext context) {
List<Widget> widgets = [];
for(int i = 0; i < 5; i++) {
TextEditingController controller = new TextEditingController();
TextFormField text = TextFormField(
controller: controller,
);
fields[text] = controller;
widgets.add(Container(
margin: EdgeInsets.symmetric(horizontal: 8.0, vertical: 15.0),
child: Column(children: [
text,
FlatButton(
color: Colors.blue,
child: Text("Submit", style: TextStyle(color: Colors.white)),
onPressed: () {
getTextValue(text);
},
)
],)
));
}
return Scaffold(
body: Column(children: widgets),
);
}
getTextValue(TextFormField text) {
TextEditingController controller = fields[text];
String value = controller.text;
print(value);
return value;
}
}
I created a slider-based stepper form using TabBarView which validate the input before switching. It works, but when I go back, the state was reset. This behavior leads me to an empty form when I try to collect the data at the end of the tab.
I have googled for few hours and have been tried switching the current GetView<MyController> to the classic StatefulWidget with AutomaticKeepAliveMixin with no luck, so I revert it.
I'm a bit stuck, I wonder if there is any other way to achieve this, the GetX way, if possible.
visual explanation
`
create_account_form_slider.dart
class CreateAccountFormSlider extends GetView<CreateAccountController> {
#override
Widget build(BuildContext context) {
return Expanded(
child: TabBarView(
physics: const NeverScrollableScrollPhysics(),
controller: controller.tabController,
children: [
_buildEmailForm(),
_buildNameForm(),
_buildPasswordForm(),
],
),
);
}
Widget _buildEmailForm() {
return Form(
key: controller.emailFormKey,
child: Column(
children: [
Spacer(), // Necessary to push the input to the bottom constraint, Align class doesn't work.
Padding(
padding: const EdgeInsets.symmetric(horizontal: 20.0),
child: FormInput(
focusNode: controller.emailFocusNode,
margin: EdgeInsets.zero,
label: 'create_account_form_email'.tr,
hintText: 'janedoe#example.com',
textInputAction: TextInputAction.next,
keyboardType: TextInputType.emailAddress,
validator: controller.emailValidator,
onFieldSubmitted: (_) => controller.next(),
),
),
],
),
);
}
... each form has similar structure (almost identical), so i will not include it here
create_account_controller.dart
class CreateAccountController extends GetxController
with SingleGetTickerProviderMixin {
final tabIndex = 0.obs;
final emailFormKey = GlobalKey<FormState>();
FormState get emailForm => emailFormKey.currentState;
final emailFocusNode = FocusNode();
final email = ''.obs;
TabController tabController;
#override
void onInit() {
_initTabController();
super.onInit();
}
#override
void onClose() {
_disposeFocusNodes();
_disposeTabController();
super.onClose();
}
/// Initialize tab controller and add a listener.
void _initTabController() {
tabController = TabController(vsync: this, length: 3);
tabController.addListener(_tabListener);
}
/// Listen on tab change and update `tabIndex`
void _tabListener() => tabIndex(tabController.index);
/// Dispose tab controller and remove its listener.
void _disposeTabController() {
tabController.removeListener(_tabListener);
tabController.dispose();
}
/// Dispose all the focus nodes.
void _disposeFocusNodes() {
emailFocusNode.dispose();
}
/// Animate to the next slide.
void _nextSlide() => tabController.animateTo(tabIndex() + 1);
/// Animate to the next slide or submit if current tab is the last tab.
void next() {
if (tabIndex().isEqual(0) && emailForm.validate()) {
_nextSlide();
return focusScope.requestFocus(nameFocusNode);
}
...
}
/// A function that checks the validity of the given value.
///
/// When the email is empty, show required error message and when the email
/// is invalid, show the invalid message.
String emailValidator(String val) {
if (val.isEmpty) return 'create_account_form_error_email_required'.tr;
if (!val.isEmail) return 'create_account_form_error_email_invalid'.tr;
return null;
}
/// Submit data to the server.
void _submit() {
print('TODO: implement submit');
print(email());
}
}
I made it by saving the form and adding an initialValue on my custom FormInput widget then put the observable variable onto each related FormInput. No need to use keepalive mixin.
create_account_controller.dart
/// Animate to the next slide or submit if current tab is the last tab.
void next() {
if (tabIndex().isEqual(0) && emailForm.validate()) {
// save the form so the value persisted into the .obs variable
emailForm.save();
// slide to next form
_nextSlide();
// TODO: wouldn't it be nice if we use autofocus since we only have one input each form?
return focusScope.requestFocus(nameFocusNode);
}
...
}
create_account_form_slider.dart
Obx( // wrap the input inside an Obx to rebuild with the new value
() => Padding(
padding: const EdgeInsets.symmetric(horizontal: 20.0),
child: FormInput(
focusNode: controller.emailFocusNode,
label: 'create_account_form_email'.tr,
hintText: 'janedoe#example.com',
textInputAction: TextInputAction.next,
keyboardType: TextInputType.emailAddress,
validator: controller.emailValidator,
onFieldSubmitted: (_) => controller.next(),
initialValue: controller.email(), // use initial value to keep current value when user go back from the next slide
onSaved: controller.email, // persist current value into the .obs variable
),
),
),
FYI: The FormInput is just a regular TextInput, only decoration is modified. This should work with the regular flutter TextInput.
if you want to use AutomaticKeepAliveMixin in GetX like StatefulWidget. You can add the parameter 'permanent: true' in Get.put like this
Get.put<HomeController>(
HomeController(),
permanent: true,
);
Full code on HomeBinding like this
import 'package:get/get.dart';
import '../controllers/home_controller.dart';
class HomeBinding extends Bindings {
#override
void dependencies() {
Get.put<HomeController>(
HomeController(),
permanent: true,
);
}
}
Currently, when a user fills in a TextField the text is lost if they navigate away and then return. How can I get the text to stay in the field upon their return?
Here's the stateful widget I'm using;
class _EditPageState extends State<EditPage> {
final _formKey = GlobalKey<FormState>();
String audiotitle;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Form(
key: _formKey,
child: Container(
child: TextField(
decoration: new InputDecoration(
hintText: widget.oldaudiotitle,
),
keyboardType: TextInputType.text,
onChanged: (titleText) {
setState(() {
this.audiotitle = titleText;
});
},
),
),
),
);
}
}
What am I doing wrong here?
you have two ways :
store the Data of the text field and set the data in init method
(use sharedpreferences or any other database as per your requirements)
TextEditingController controller = TextEditingController();
#override
void initState() {
// TODO: implement initState
// retrive the Data
if(data != null) {
controller = new TextEditingController(text: data);
}
}
or if the first screen is navigating in the second Screen than just pop that screen
Navigator.pop(context);
i want to obtain text value from these dynamic TextFormField
class MyPets extends StatefulWidget {
#override
_MyPetsState createState() => _MyPetsState();
}
class _MyPetsState extends State<MyPets> {
List<Widget> _children = [];
int _count = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Title"),
actions: <Widget>[IconButton(icon: Icon(Icons.add), onPressed: _add)],
),
body: ListView(children: _children),
);
}
void _add() {
TextEditingController controller = TextEditingController();
controllers.add(controller); //adding the current controller to the list
for (int i = 0; i < controllers.length; i++) {
print(
controllers[i].text); //printing the values to show that it's working
}
_children = List.from(_children)
..add(
TextFormField(
controller: controller,
decoration: InputDecoration(
hintText: "This is TextField $_count",
icon: IconButton(icon: Icon(Icons.remove_circle), onPressed: rem),
),
),
);
setState(() => ++_count);
}
rem() {
setState(() {
_children.removeAt(_count);
controllers.removeAt(_count);
});
}
#override
void dispose() {
controllers.clear();
super.dispose();
}
}
problem is i don't know how to pass TextEditingController to this form-field in flutter
i got above solution from this stack overflow answer
basically i want textfields and when i tap a button textfield must increment
and i want the value from each textfield and also i should able to delete the respective fields
please help me
thank in advance
You can dynamically take the value of all these fields adding the controllers to a list:
class _MyPetsState extends State<MyPets> {
List<Widget> _children = [];
List<TextEditingController> controllers = []; //the controllers list
int _count = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Title"),
actions: <Widget>[IconButton(icon: Icon(Icons.add), onPressed: _add)],
),
body: ListView(children: _children),
);
}
void _add() {
TextEditingController controller = TextEditingController();
controllers.add(controller); //adding the current controller to the list
for(int i = 0; i < controllers.length; i++){
print(controllers[i].text); //printing the values to show that it's working
}
_children = List.from(_children)
..add(TextFormField(
controller: controller,
decoration: InputDecoration(hintText: "This is TextField $_count"),
));
setState(() => ++_count);
}
#override
void dispose() {
controllers.clear();
super.dispose();
}
}