Pretty much what I describe in the title. I have a pile of TextFormFields populated from Firebase when the app launches.
The user should be able to update these, and when they are done, click a submit button to update the database. The database code all works, however there is some bug which works as follows:
TextFormField1: "New Text Entered"
TextFormField2: "Some more text"
TextFormField3: "Another update here"
Now we get to a point where we need to dismiss the keyboard, so that we can see the submit button underneath. As soon as you click the little down arrow to dismiss the keyboard, all the changes above revert back to their original state.
Anyone seen this?
I am prepopulating the data in these fields at runtime, and you can edit and update the text, and it all works fine... except if you minimise the keyboard.
Please tell me that Flutter isn't doing something fundamentally stupid like reloading the widget underneath from scratch every time you ask the keyboard to go away...... It sort of feels like it is.
Yes. It happens to me all the time. It is because the screen rebuilds when the bottom insets (due to keyboard) changes.
Enclose the TextFormField(s) inside a Form and give it a global key.
Use a local variable to store the value of the TextFormField. Update it in onChanged method.
All done!
I shall attach a code for easiness.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: LoginScreen(),
);
}
}
// Login Screen
class LoginScreen extends StatefulWidget {
#override
_LoginScreenState createState() => _LoginScreenState();
static GlobalKey<FormState> _loginScreenFormKey = GlobalKey<FormState>();
}
class _LoginScreenState extends State<LoginScreen> {
String username;
String password;
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
body: Form(
key: LoginScreen._loginScreenFormKey,
child: Column(
children: [
TextFormField(
decoration: InputDecoration(
hintText: 'Enter username',
),
onChanged: (value) {
setState(() {
username = value;
});
},
),
TextFormField(
decoration: InputDecoration(
hintText: 'Enter username',
),
onChanged: (value) {
setState(() {
password = value;
});
},
obscureText: true,
),
RaisedButton(
onPressed: () {
LoginScreen._loginScreenFormKey.currentState.save();
},
child: Text('submit'),
),
],
),
),
),
);
}
}
This is my solution: move the TextEditingController variable from the inside of the "build" method to the outside of the "build" method. Ref in pic The solution
The class that includes those TextFormFields should extends State of StatefulWidget, the local state will be cleared if the dismiss of keyboard causes those fields re-render, hence you need StatefulWidget to save the local state so that it won't be re-rendered
Convert you StatelessWidget to StatefulWidget.
Related
The application first displays the With text: Entered text screen. And there is a button when clicked on which the user gets to another screen where he needs to enter text. It is necessary for me that when the user has entered the text, when returning back to the first screen, this text is displayed. How can this be done?
My code:
import 'package:flutter/material.dart';
class TextScreen extends StatefulWidget {
const TextScreen({Key? key}) : super(key: key);
#override
State<TextScreen> createState() => _TextScreenState();
}
class _TextScreenState extends State<TextScreen> {
final textController = TextEditingController();
#override
void dispose() {
textController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Enter data'),
),
body: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
TextField(
controller: textController,
decoration: InputDecoration(labelText: 'Message'),
),
const SizedBox(
height: 20,
),
],
)),
);
}
}
You can add a result when navigating back
Navigator.of(context).pop("test");
And the you can use the result in the previos screen
Navigator.push(
context,
MaterialPageRoute(builder: (context) => TextScreen()),
).then((result) => { ... });
There are several ways to do this. The main question is if you have a specific submit action, such as a button, to confirm using the new text or if the newly entered text should always be used, also when the user uses the back button or back swipe gesture to go back to the previous screen.
Only through submit action
If you want to consider the back button as a 'cancel' operation, and only want to update the value with a specific button, you can return the new value with the Navigator: Navigator.of(context).pop(updatedValue). Where the page was pushed, you can await this result: final updatedValue = await Navigator.of(context).push(editPageRoute);
If you always want the value to update
In this case, you'll need to update this new text in a component that lives above the Navigator which is likely provided by your MaterialApp (or other WidgetApp).
To do so, you can wrap the Navigator in another widget by using the builder function of the MaterialApp. This widget can be obtained through the widget tree and is available in both pages. You can use InheritedWidget or provider to obtain this widget.
You could also keep a component outside of your widget tree that holds this text. Using get_it might be a solution for that, but riverpod would probably allow you to do so as well.
I'm new to Flutter and curious how I can display the value of TextField in a Text widget without pressing a button. To be clearer with my question: When I type "hello" in the TextField, the text should also be "hello".
You need to listen change from TextField and update it in a global variable that can access from another widget (Text)
Example:
import 'package:flutter/material.dart';
void main() => runApp(MaterialApp(home: App()));
class App extends StatefulWidget {
#override
AppState createState() => AppState();
}
class AppState extends State<App> {
String text = ''; // variable stored your text
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
TextField(
onChanged: (value) => setState(() => text = value), // change `text` every text input change
decoration: const InputDecoration(labelText: 'input your text'),
),
Text('your text: $text'), // display your text
],
),
);
}
}
The Flutter documentation has become pretty solid. The essential widgets like TextField have detailed explanations on their proper usage with code example/demo, and even videos. Just type "flutter [the widget name]" in Google.
There's a button on my home page which when clicked should redirect me to another page and click on textformfield on that page,I dont know how to do that
just use autofocus: true on another page
class Home extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: TextButton(
onPressed: () {
Navigator.push(context, MaterialPageRoute(
builder: (context) {
return NextPage();
},
));
},
child: Text("Click")),
),
);
}
}
class NextPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: TextField(
autofocus: true,
),
);
}
}
There is multiple ways to do this depending on your use case
1- if you want to focus on the text form field every time you enter the page you change autofocus property in the textField to true,
TextField(
autofocus:true,
)
2- if you want to trigger focus on a textfield by manually you can use FocusNode object, this focus node will be attached to your text field.
First you need to initialize the object.
FocusNode myFocusNode;
#override
void initState() {
super.initState();
myFocusNode = FocusNode();
}
#override
void dispose() {
// Clean up the focus node when the Form is disposed.
myFocusNode.dispose();
super.dispose();
}
then Attach the focus node object to your text field
TextField(
focusNode: myFocusNode,
);
Now you can use this focus node in a function to focus on this text
// focus on textfield (same as text field pressed)
myFocusNode.requestFocus()
// unfocus on textfield (same as pressing done on textfield or pressing the back button)
myFocusNode.unfocus()
you can pass a flag to the new page you are going to, which will trigger the focus function
I am learning Flutter and I am looking what might be the pro's / con's of some specific way of solving this. I have a simple widget which is used as a ModalBottomSheet. It will contain a form with multiple text fields to submit some data - which is returned in the callback. Example:
class SimpleBottomPopupSheet extends StatelessWidget {
final Function onSubmit;
const BottomPopupSheet({this.onSubmit});
#override
Widget build(BuildContext context) {
String text;
return Column(children: [
TextField(
textAlign: TextAlign.center,
onChanged: (value) {
text = value;
},
),
ElevatedButton(
onPressed: () {
// Do something with the text
onSubmit(text);
Navigator.pop(context);
},
child: Text("SUBMIT"),
)
]);
}
}
This seems to work, but I am wondering if this is a correct way to use it (there will be more than just one text field). It feels like it should be a stateful widget as it is keeping the state of the input.
Any best practices around this use case?
The other comments are correct. This should be a StatefulWidget as any rebuilds of the widget will cause the "text" parameter to be reset. A rather naive and basic example of how to convert your StatelessWidget into a StatefulWidget is below:
class SimpleBottomPopupSheet extends StatefulWidget {
final Function onSubmit;
const SimpleBottomPopupSheet({this.onSubmit});
#override
State<SimpleBottomPopupSheet> createState() => _SimpleBottomPopupSheetState();
}
class _SimpleBottomPopupSheetState extends State<SimpleBottomPopupSheet> {
String _text;
#override
Widget build(BuildContext context) {
return Column(children: [
TextField(
textAlign: TextAlign.center,
onChanged: (value) {
_text = value;
},
),
ElevatedButton(
onPressed: () {
// Do something with the text
widget.onSubmit(_text);
Navigator.pop(context);
},
child: const Text("SUBMIT"),
)
]);
}
}
Your widget should be a StatefulWidget. What you wrote won't work because each time the user types something the text variable will be wiped out (and other nasty things :))
You should have a look at Flutter Form tutorial: https://flutter.dev/docs/cookbook/forms/validation!
You should use StatelessWidget only when the widget does not update its values! Otherwise, you should use a StatefulWidget or more complex state management solution. Again you can find many guides on flutter.dev!
(But using a method onSubmit to return the data to the previous view/widget is a good idea)
I am getting the error The method 'setState' isn't defined for the class MyApp error in Flutter in the onSubmitted of the TextField
Code:
Widget build(BuildContext context) {
String phoneNo;
return new MaterialApp(
title: 'SchoolTrack',
theme: new ThemeData(
primaryColor: Colors.grey[50],
),
home: new Scaffold(
appBar: null,
backgroundColor: Colors.cyan[100],
body: new Container(
padding: const EdgeInsets.all(32.0),
child: new Center(
child: new TextField(
autofocus: true,
autocorrect: false,
decoration: new InputDecoration(
hintText: 'Type the phone no',
suffixIcon: new Icon(Icons.send),
suffixStyle: new TextStyle(
color: Colors.cyan[300],
)
),
onSubmitted: (String input) {
setState(() {
phoneNo = input;
});
},
),
),
),
)
);
}
I assume you are trying to setState in a stateless widget, which is immutable(unable to change).
Use a stateful widget to do so, like this:
class MainPage extends StatefulWidget{
HomePage createState()=> HomePage();
}
class HomePage extends State<MainPage>{
//Your code here
}
place
setState
inside
StatefullWidget
:) that will solve the problem
You have to call that function within a stateful widget
setState is only available inside a StatefulWidget class/subclass. You need to convert your StatelessWidget to a StatefulWidget. Simply:
Click on the StatelessWidget class and use option + return (or cmd + . if you use VS Code on macOS),
Be Sure you typed right
setState( () {} ) ;
As mentioned above, the issue stem from StatelessWidget. For people who use text editors for coding the easiest way is like following:
Replace this:
class YourClassName extends StatelessWidget {
With this:
class YourClassName extends StatefulWidget {
#override
_YourClassNameState createState() => _YourClassNameState();
}
class _YourClassNameState extends State<YourClassName> {
All other things will be the same and now you can call setState inside your class:
setState(() {
whatever you want to update goes here
});
However, it is more practical to put your setState inside your own function and trigger it from anywhere easily:
void funtionName(passing parameters) {
setState(() {
....
});
}
Now call your function easily from anywhere like: funtionName(parameter).
Keep the cursor above the StatelessWidget and press Alt + insert and click on convert to StatefulWidget, to quickly covert your StatelessWidget to StatefulWidget.
If you are using a lambda and getting 'setstate isn't referenced' be sure to type it right with the brackets:
setState(() => _counter++);
instead of:
setState() => _counter++;
setState{} is only available inside a Stateful Widget class/subclass. You need to convert your Stateless Widget to a StatefulWidget.