I have a situtation where i would like to access to text of a text controller, from the Widget part of a statefull widget.
So what i would like to do is is write a method getText() that returns the current text in my _textController.
I know how to do this from the other way around. If i need to get data from my widget from the State part of my widget is use "widget.", but i dont know how to do this the other way around.
class MyTextWidget extends StatefulWidget {
String getText() {
// how can i access the _textController.text from here?
}
#override
_MyTextWidgetState createState() => _MyTextWidgetState();
}
class _MyTextWidgetState extends State<MyTextWidget> {
final TextEditingController _textController = TextEditingController();
_MyTextWidgetState();
#override
void dispose() {
_textController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(2.0),
child: TextField(
controller: _textController,
key: ValueKey('MyTextWidgetinput_Key'),
maxLines: null,
autofocus: true,
keyboardType: TextInputType.multiline,
decoration: InputDecoration(
border: OutlineInputBorder(
borderRadius: const BorderRadius.all(Radius.circular(15.0))),
),
));
}
}
Simply move it up and access it in your state class using widget._textController:
class MyTextWidget extends StatefulWidget {
final TextEditingController _textController = TextEditingController();
String getText() {
_textController.text // do something with it
// how can i access the _textController.text from here?
}
#override
_MyTextWidgetState createState() => _MyTextWidgetState();
}
class _MyTextWidgetState extends State<MyTextWidget> {
_MyTextWidgetState();
#override
void dispose() {
widget._textController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(2.0),
child: TextField(
controller: widget._textController,
key: ValueKey('MyTextWidgetinput_Key'),
maxLines: null,
autofocus: true,
keyboardType: TextInputType.multiline,
decoration: InputDecoration(
border: OutlineInputBorder(
borderRadius: const BorderRadius.all(Radius.circular(15.0))),
),
));
}
}
Related
I want to make my flutter project highly manageable, apply clean code and maintain DRY concept strictly. There are a lot of input elements in any flutter project. So I want to make this element as a separate widget so that if I want to change in future then I will change in one place. Here is my approach:
import 'package:flutter/material.dart';
import '../utility/validatation.dart';
class RegistrationPage extends StatefulWidget {
static const String routeName = '/registrationPage';
#override
State<RegistrationPage> createState() => _RegistrationPageState();
}
class _RegistrationPageState extends State<RegistrationPage> {
final _formKey = GlobalKey<FormState>();
final TextEditingController nameInput = TextEditingController();
final TextEditingController businessName = TextEditingController();
final TextEditingController productTypeId = TextEditingController();
#override
Widget build(BuildContext context) {
return Scaffold(
body: new Form(
key: _formKey,
autovalidateMode: AutovalidateMode.onUserInteraction,
child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [
Container(
height: 70,
margin: EdgeInsets.only(bottom: 50),
child: Image(image: AssetImage('assets/logo.png')),
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 30.0),
child: TextInput(inputController: nameInput, label: 'আপনার নাম'),
),
ElevatedButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
_register(context);
}
},
child: Text('Next'),
)
]),
),
);
}
void _register(BuildContext context) {}
}
class TextInput extends StatelessWidget {
const TextInput({
Key? key,
required this.inputController,
required this.label,
}) : super(key: key);
final TextEditingController inputController;
final String label;
#override
Widget build(BuildContext context) {
return TextFormField(
controller: inputController,
keyboardType: TextInputType.text,
decoration: const InputDecoration(
border: UnderlineInputBorder(),
prefixIcon: Icon(Icons.phone),
labelText: label,
),
validator: (value) {
return Validation.required(value);
},
);
}
}
But I got this error:
What is wrong in my code? Is there any problem in my approach or should I stop thinking to refactor my code as I do? Please also suggest if there is any smarter way to make code more clean and manageable.
Oh I see so you have this
class TextInput extends StatelessWidget {
const TextInput({
Key? key,
required this.inputController,
required this.label,
}) : super(key: key);
final TextEditingController inputController;
final String label;
#override
Widget build(BuildContext context) {
return TextFormField(
controller: inputController,
keyboardType: TextInputType.text,
// Notice the const here right?
// So the idea is that decoration objects could rebuild to either change one thing or the other, so 'label' here cannot be a constant
//So to solve this InputDecoration should not have const.
decoration: const InputDecoration(
border: UnderlineInputBorder(),
prefixIcon: Icon(Icons.phone),
labelText: label,
),
validator: (value) {
return Validation.required(value);
},
);
}
}
Since you are using a variable in InputDecoration, you should not declare InputDecoration with const keyword.
In my searchPage.dart I have two statefulWidget , SearchScreen and TopSearchWidget. SearchScreen has a TextField and when the users start to type, it is supposed to populate from another widget TopSearchWidget but I have no idea how to pass the argument/data from one widget to another.
Here, is my code
class SearchPage extends StatefulWidget {
const SearchPage({Key key}) : super(key: key);
#override
State<StatefulWidget> createState() => _SearchPageState();
}
class _SearchPageState extends State<SearchPage> {
TextEditingController dropOffTextEditingController = TextEditingController();
);
#override
void initState() {
super.initState();
dropOffTextEditingController.addListener(textOnChanged);
}
void textOnChanged() {
controller.setSearchableText(dropOffTextEditingController.text);
}
#override
void dispose() {
dropOffTextEditingController.removeListener(textOnChanged);
super.dispose();
}
#override
Widget build(BuildContext context) {
......
Row(
children: [
SizedBox(width: 18.0),
Expanded(
child: Container(
decoration: BoxDecoration(
color: Colors.grey[400],
borderRadius: BorderRadius.circular(5.0),
),
child: Padding(
padding: EdgeInsets.all(3.0),
child: TextField(
controller: dropOffTextEditingController,
onEditingComplete: () async {
FocusScope.of(context).requestFocus(new FocusNode());
},
decoration: InputDecoration(
hintText: "Drop off Location",
fillColor: Colors.grey[400],
filled: true,
border: InputBorder.none,
isDense: true,
contentPadding: EdgeInsets.only(left: 11.0, top: 8.0, bottom: 8.0),
),
),
),
))
],
),
SizedBox(height: 10.0),
topWidgetPicker: TopSearchWidget(),
and receive the data from the TextField so it can start searching..
class TopSearchWidget extends StatefulWidget {
#override
State<StatefulWidget> createState() => _TopSearchWidgetState();
}
class _TopSearchWidgetState extends State<TopSearchWidget> {
#override
void initState() {
super.initState();
controller = ***** What should go here *****?
controller.searchableText.addListener(onSearchableTextChanged);
}
void onSearchableTextChanged() async {
final v = controller.searchableText.value;
.............
}
Can you pass the controller from your SearchPage down to the TopSearchWidget?
class _SearchPageState extends State<SearchPage> {
final dropOffTextEditingController = TextEditingController();
#override
Widget build(BuildContext context) {
// ...
topWidgetPicker: TopSearchWidget(dropOffTextEditingController),
// ...
}
}
class TopSearchWidget extends StatefulWidget {
final TextEditingController controller;
TopSearchWidget(this.controller);
#override
State<StatefulWidget> createState() => _TopSearchWidgetState();
}
class _TopSearchWidgetState extends State<TopSearchWidget> {
#override
void initState() {
super.initState();
widget.controller.searchableText.addListener(onSearchableTextChanged);
}
void onSearchableTextChanged() async {
final v = widget.controller.searchableText.value;
.............
}
}
I'm developing a app with a register, login and reset password screen. In all this screens the user must enter his e-mail address. Now i will not implement the e-mail address textfield for every single screen. So i will implement a email textfield widget for every screen like the code below.
import 'package:flutter/material.dart';
import 'package:email_validator/email_validator.dart';
class EMailTextFormField extends StatefulWidget {
#override
_EMailTextFormFieldState createState() => _EMailTextFormFieldState();
}
class _EMailTextFormFieldState extends State<EMailTextFormField> {
final _email = TextEditingController();
#override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 3.0),
child: TextFormField(
decoration: InputDecoration(
icon: Icon(Icons.email, size: IconTheme.of(context).size, color: IconTheme.of(context).color),
labelText: 'E-Mail...',
counterText: '',
),
keyboardType: TextInputType.emailAddress,
controller: _email,
validator: _validateEmail,
maxLength: 70,
),
);
}
String _validateEmail(String email) {
// validate E-Mail function...
}
}
My problem is now that i can't use the TextEditingController (_email) outside this widget in the different screens (register, login and reset password) like this as a example:
final FirebaseUser user = (await _auth.createUserWithEmailAndPassword(
email: _email.text.toString(), password: _password.text.toString())).user;
The error is "Undefined name _email" because the _email TextEditingController is in the EMailTextFormField widget, but how can i give the value of the _email field from EMailTextFormField widget to the other screens (register, login and reset password)?
Can anyone help me i found so far no solution.
You can do this using onSaved callback.
EMailTextFormField:
class EMailTextFormField extends StatefulWidget {
final void Function(String email) onSaved;
const EMailTextFormField({Key key, this.onSaved}) : super(key: key);
#override
EMailTextFormFieldState createState() => EMailTextFormFieldState();
}
class EMailTextFormFieldState extends State<EMailTextFormField> {
final _email = TextEditingController();
#override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 3.0),
child: TextFormField(
decoration: InputDecoration(
icon: Icon(Icons.email, size: IconTheme.of(context).size, color: IconTheme.of(context).color),
labelText: 'E-Mail...',
counterText: '',
),
keyboardType: TextInputType.emailAddress,
controller: _email,
validator: _validateEmail,
maxLength: 70,
onSaved: widget.onSaved, //callback
),
);
}
String _validateEmail(String email) {
// validate E-Mail function...
}
}
Page where you will use EMailTextFormField:
class EmailPage extends StatefulWidget {
EmailPage({Key key}) : super(key: key);
#override
_EmailPageState createState() => _EmailPageState();
}
class _EmailPageState extends State<EmailPage> {
String _email;
final _formKey = GlobalKey<FormState>();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Email Page'),
),
body: Form(
key: _formKey,
child: Column(
children: <Widget>[
EMailTextFormField(
onSaved: (String email) => _email = email,
),
RaisedButton(
child: Text('Go'),
onPressed: (){
if (_formKey.currentState.validate()) {
_formKey.currentState.save();
print(_email);
}
},
)
],
),
),
);
}
}
In my widget tree, I have several TextField widgets. All have the same decoration but different onChanged actions:
Widget _buildTextField() {
return TextField (
decoration: InputDecoraction(
border: OutlineInputBorder()
),
onChanged: (text) {
doSth();
}
);
}
Now I want to reduce the code duplication and was trying to extract the TextField with the duplicated decoration field into a CustomTextField. I read that with Flutter composition is over inheritance, so I tried to compose it this way:
class CustomTextField extends StatefulWidget {
#override
_CustomTextFieldState createState() => _CustomTextFieldState();
}
class _CustomTextFieldState extends State<CustomTextField> {
#override
Widget build(BuildContext context) {
return Container(
child: TextField(
decoration: InputDecoration( // <-- the decoration field
border: OutlineInputBorder(),
),
),
);
}
}
At the other side I did:
Widget _buildTextField() {
return CustomTextField ( // <-- new CustomTextField without decoration
onChanged: (text) { // <-- Problem: "Parameter is not defined"
doSth();
}
);
}
But now the onChanged call is not accepted. So, what is the correct way to extract own widgets and handle the child fields?
You need to register a callback function
class CustomTextField extends StatefulWidget {
final Function onChange;
const CustomTextField({Key key, this.onChange}) : super(key: key);
#override
_CustomTextFieldState createState() => _CustomTextFieldState();
}
class _CustomTextFieldState extends State<CustomTextField> {
#override
Widget build(BuildContext context) {
return Container(
child: TextField(
decoration: InputDecoration(
// <-- the decoration field
border: OutlineInputBorder(),
),
onChanged: widget.onChange,
),
);
}
}
and then
child: CustomTextField(
onChange: (item) {
print(item);
},
),
Please help for my 2 questions.
1st Question. Why text value disappear?[FIXED] by #Praneeth
I added gif
please click
I used this way my code,
In my widget I called UsernameTextField class.
Widget looks like this,
Widget build > WillPopScope > Scaffold > Form > ListView > children>
Container (below container) I added key: _scaffoldKey, and key: formKey, also.
Container(
padding: EdgeInsets.all(16.0),
margin: EdgeInsets.only(top: 30.0),
child: UsernameTextField(),
),
UsernameTextField()
class UsernameTextField extends StatefulWidget{
final usernameController = TextEditingController();
#override
State<StatefulWidget> createState() {
return UsernameTextFieldState(usernameController);
}
}
class UsernameTextFieldState extends State<UsernameTextField>{
final usernameController;
UsernameTextFieldState(this.usernameController);
#override
Widget build(BuildContext context) {
return AppTextField(
decoration: InputDecoration(
contentPadding: const EdgeInsets.all(20.0),
labelText: AppTranslations.of(context)
.text("loginpage_username"),
),
myController: usernameController,
textInputType: TextInputType.emailAddress
);
}
}
AppTextField() class, I used this class for my every TextField Widget
class AppTextField extends StatelessWidget {
final InputDecoration decoration;
final 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
);}}
2nd Question. How to get textField value?
In my button onPressed() method I called, I called validation method, But result is null
usernameValidation(){
String username = UsernameTextField().usernameController.text;
print(username);
}
First convert UsernameTextField class to a Stateful one by extending as a StatefulWidget instead of StatelessWidget.
Then you can get value from usernameController.text
UPDATE
class UsernameTextField extends StatefulWidget{
final usernameController = TextEditingController();
UsernameTextField(this.usernameController)
#override
State<StatefulWidget> createState() {
return UsernameTextFieldState(usernameController);
}
}
class UsernameTextFieldState extends State<UsernameTextField>{
#override
Widget build(BuildContext context) {
return AppTextField(
decoration: InputDecoration(
contentPadding: const EdgeInsets.all(20.0),
labelText: AppTranslations.of(context)
.text("loginpage_username"),
),
myController: widget.usernameController,
textInputType: TextInputType.emailAddress
);
}
}