Flutter PWA - Canvas going to change after dismiss keyboard - flutter

I am facing one strange issue in the flutter progressive web application. It's working well in safari and chrome browsers. It's creating an issue when we add it to the home screen. We are having only two text fields on the page and nothing else. After opening a keyboard and clicking on the done or return button then we tap outside the TextInput area multiple times(5 - 6 times) then the canvas(body) of the page reduces gradually. Please check the attached video for it. Video Link
Please check below code:
import 'package:demo_texfield_web/unfocser.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.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: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: TextfieldDemo(),
);
}
}
class TextfieldDemo extends StatefulWidget {
const TextfieldDemo({Key? key}) : super(key: key);
#override
State<TextfieldDemo> createState() => _TextfieldDemoState();
}
class _TextfieldDemoState extends State<TextfieldDemo> {
var txtxField1 = TextEditingController();
var txtxField2 = TextEditingController();
FocusNode _focusNode1 = new FocusNode();
FocusNode _focusNode2 = new FocusNode();
final _formKey = GlobalKey<FormState>();
int count = 0;
#override
void initState(){
super.initState();
_focusNode1.addListener(_focusNodeListener);
}
#override
void dispose(){
_focusNode1.removeListener(_focusNodeListener);
super.dispose();
}
Future<Null> _focusNodeListener() async {
if (_focusNode1.hasFocus){
print('TextField got the focus');
} else {
print('TextField lost the focus');
FocusScope.of(context).unfocus();
}
}
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
FocusScopeNode currentFocus = FocusScope.of(context);
if(!currentFocus.hasPrimaryFocus) {
currentFocus.focusedChild!.unfocus();
}
},
child: Scaffold(
body:Form(
key: _formKey,
child: Padding(
padding: const EdgeInsets.all(60.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Unfocuser(
child: TextFormField(
decoration: const InputDecoration(
border: UnderlineInputBorder(),
filled: true,
icon: Icon(Icons.person),
hintText: 'Enter your first name',
labelText: 'First name *',
),
onSaved: (String? value) {
//TODO
},
controller: txtxField1,
focusNode: _focusNode1,
),
),
const SizedBox(height: 25,),
Unfocuser(
child: TextFormField(
keyboardType: TextInputType.number,
decoration: const InputDecoration(
border: UnderlineInputBorder(),
filled: true,
icon: const Icon(Icons.person),
hintText: 'Enter your Last name',
labelText: 'Last name *',
),
onSaved: (String? value) {
//TODO
},
controller: txtxField2,
focusNode: _focusNode2,
),
),
],
),
),
),
),
);
}
}

Related

Clear TextField inside ListView Flutter

How onTap clear TextField in below example?
Note TextField is positioned inside trailing in ListView
trailing: FittedBox(
fit: BoxFit.fill,
child: SizedBox(
height:40, width:100,
child: TextField(
controller: TextEditingController(text: _docData[index].quantity.toString()),
decoration: InputDecoration(
suffix: InkWell(
onTap: () {
setState(() {
});
},
child: Icon(
Icons.clear,
),
)
),
),
),
),
Try this
Create a list of TextEditingController
List<TextEditingController> controllers = List.empty(growable: true);
Then inside ListView.builder, create TextEditingController one by one and add it to controllers list.
Then assign to TextField and clear like this
child: TextField(
controller: controllers[index],
decoration: InputDecoration(
suffix: InkWell(
onTap: () {
setState(() {
controllers[index].clear();
});
},
child: Icon(
Icons.clear,
),
)
),
),
When we call the function:
setState(() {
});
The whole widget rebuilds hence your TextEditingController is initialized again so the text inside is reset to default which is empty
Full Code
1)First Screen
import 'package:flutter/material.dart';
import 'home_page.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return const MaterialApp(
home: MyCustomForm(),
);
}
}
2)Second Screen
import 'package:flutter/material.dart';
class MyCustomForm extends StatefulWidget {
const MyCustomForm({Key? key}) : super(key: key);
#override
State<MyCustomForm> createState() => _MyCustomFormState();
}
class _MyCustomFormState extends State<MyCustomForm> {
TextEditingController _controller =TextEditingController();
#override
Widget build(BuildContext context) {
// question: delete textField inside the ListView
return Scaffold(
body: Container(
child: ListView(
children: [
TextField(
onChanged: (value){
_controller.text;
},
controller: _controller,
decoration: InputDecoration(
hintText: 'Enter Name',
suffix: InkWell(
onTap: (){
_controller.clear();
},
child:Icon(Icons.clear),
)
),
)
],
),
),
);
}
}

TextFormField obscureText in AlertDialog don't change

I want to change the obscureText mode of a TextFormField in an AlertDialog but it doesn't work
Clicking the IconButton does not change
the obscureText to TextFormField in AlertDialog
I want to change the obscureText mode of a TextFormField in an AlertDialog but it doesn't work
Clicking the IconButton does not change
the obscureText to TextFormField in AlertDialog
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
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> {
final username = TextEditingController(text: '');
final password = TextEditingController(text: '');
final formKey = GlobalKey<FormState>();
late bool obscure;
#override
void initState() {
obscure = true;
super.initState();
}
#override
void dispose() {
username.dispose();
password.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
actions: <Widget>[
IconButton(
onPressed: () {
showDialog(
context: context,
barrierDismissible: false,
builder: (_) {
return AlertDialog(
title: const Text('Connection'),
content: SizedBox(
height: 400,
width: 400,
child: Form(
key: formKey,
child: Column(
children: <Widget>[
TextFormField(
cursorColor: Colors.grey,
textInputAction: TextInputAction.next,
maxLines: 1,
controller: username,
validator: (value) {
return null;
},
decoration: const InputDecoration(
isDense: true,
prefixIcon: Icon(
Icons.person_outlined,
),
labelText: 'username',
),
),
const SizedBox(
height: 20.0,
),
TextFormField(
cursorColor: Colors.grey,
controller: password,
textInputAction: TextInputAction.next,
decoration: InputDecoration(
isDense: true,
prefixIcon: const Icon(Icons.key),
labelText: 'password',
suffixIcon: IconButton(
icon: const Icon(
Icons.remove_red_eye_outlined),
onPressed: () {
// CHAGE OBSCURE
setState(() {
obscure = !obscure;
if (kDebugMode) {
print(obscure); // OK
}
});
}),
),
obscureText: obscure, // NOT OK
obscuringCharacter: '*',
validator: (String? value) {
return null;
},
),
],
),
)),
actions: [
TextButton(
child: const Text('Send'),
onPressed: () {
if (formKey.currentState!.validate()) {
Navigator.of(context).pop();
}
},
),
],
);
});
},
icon: const Icon(Icons.person_outline_outlined),
),
],
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: const <Widget>[
Text(
'You have pushed the button this many times:',
),
],
),
),
);
}
}
For that you have to use StatefulBuilder to use setState inside Dialog and update Widgets only inside of it.
Reason: setState is having different context inside the AlertDialog, so If you want to maintain the state of AlertDialog you must have to use StatefulBuilder. It will maintain another state for your AlertDialog
Full Working Code:
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
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> {
final username = TextEditingController(text: '');
final password = TextEditingController(text: '');
final formKey = GlobalKey<FormState>();
late bool obscure;
#override
void initState() {
obscure = true;
super.initState();
}
#override
void dispose() {
username.dispose();
password.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
actions: <Widget>[
IconButton(
onPressed: () {
showDialog(
context: context,
barrierDismissible: false,
builder: (_) {
return StatefulBuilder(
builder: (context, setState)
{
return AlertDialog(
title: const Text('Connection'),
content: SizedBox(
height: 400,
width: 400,
child: Form(
key: formKey,
child: Column(
children: <Widget>[
TextFormField(
cursorColor: Colors.grey,
textInputAction: TextInputAction.next,
maxLines: 1,
controller: username,
validator: (value) {
return null;
},
decoration: const InputDecoration(
isDense: true,
prefixIcon: Icon(
Icons.person_outlined,
),
labelText: 'username',
),
),
const SizedBox(
height: 20.0,
),
TextFormField(
cursorColor: Colors.grey,
controller: password,
textInputAction: TextInputAction.next,
decoration: InputDecoration(
isDense: true,
prefixIcon: const Icon(Icons.key),
labelText: 'password',
suffixIcon: IconButton(
icon: const Icon(Icons.remove_red_eye_outlined),
onPressed: () {
// CHAGE OBSCURE
setState(() {
obscure = !obscure;
if (kDebugMode) {
print(obscure); // OK
}
});
}),
),
obscureText: obscure,
// NOT OK
obscuringCharacter: '*',
validator: (String? value) {
return null;
},
),
],
),
)),
actions: [
TextButton(
child: const Text('Send'),
onPressed: () {
if (formKey.currentState!.validate()) {
Navigator.of(context).pop();
}
},
),
],
);
});
});
},
icon: const Icon(Icons.person_outline_outlined),
),
],
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: const <Widget>[
Text(
'You have pushed the button this many times:',
),
],
),
),
);
}
}

Flutter TextFormField not immediately in focus on onFocusChange

I'd like to detect TextFormField with Focus(onFocusChange(bool)), but when the TextFormField is in focus, the field isn't activated with the cursor as expected. I need to press tab again for the cursor to appear on the TextFormField. Any idea why the TextFormField doesn't get highlighted immediately on tab/next?
Form(
child: Column(
children: <Widget>[
TextFormField(
textInputAction: TextInputAction.next,
),
Focus(
onFocusChange: (bool inFocus){
/// When tab is pressed, I expect the cursor to be on TextFormField
debugPrint('TextFormField 2 in focus? $inFocus');
},
child: TextFormField(),
),
],
),
),
My current workaround on this issue is using a listener on FocusNode.
TextFormField(
focusNode: _focusNode,
),
But this requires initialization and proper disposal.
final _focusNodePrice = FocusNode();
#override
void initState() {
super.initState();
_focusNode.addListener(_onFocusChange);
}
#override
dispose(){
super.dispose();
_focusNode.removeListener(_onFocusChange);
_focusNode.dispose();
}
/// Handles the quirks of TextFormField requirements
void _onFocusChange() {
debugPrint('TextFormField inFocus? ${_focusNode.hasFocus}');
}
Here's a minimal repro of the issue.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#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, required this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: FocusScope(
child: Form(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
TextFormField(
textInputAction: TextInputAction.next,
decoration: InputDecoration(
hintText: 'Hint goes here 1'),
),
Focus(
onFocusChange: (bool inFocus){
debugPrint('TextFormField 2 in focus? $inFocus');
},
child: TextFormField(
textInputAction: TextInputAction.next,
decoration: InputDecoration(
hintText: 'Hint goes here 2'),
),
),
],
),
),
),
),
),
);
}
}
When you press tab or next the first time, the focus goes on the focus widget, then you press again and it goes on it's child, so you can just remove the focus widget
edit: if you need the Focus widget anyways, you can create a FocusNode and requestFocus as shown in your edited example below
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#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, required this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
FocusNode node1 = FocusNode();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: FocusScope(
child: Form(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
TextFormField(
textInputAction: TextInputAction.next,
decoration: InputDecoration(hintText: 'Hint goes here 1'),
),
Focus(
onFocusChange: (bool inFocus) {
if (inFocus) FocusScope.of(context).requestFocus(node1);
debugPrint('TextFormField 2 in focus? $inFocus');
},
child: TextFormField(
focusNode: node1,
textInputAction: TextInputAction.next,
decoration: InputDecoration(hintText: 'Hint goes here 2'),
),
),
],
),
),
),
),
),
);
}
}
Basically, the better approach to my previous post would be to create a FocusNode for every field you want to check, and use if (node1.hasFocus) as a condition when needed.
So you don't need the Focus widget in this case
Maybe this code can help you
FocusNode _passFocus;
FocusNode _repeatPassFocus;
String _newPass
Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
TextFormField(
focusNode: _passFocus,
textInputAction: TextInputAction.next,
onFieldSubmitted: (value) {
fieldFocusChange(context, _passFocus, _repeatPassFocus);
},
initialValue: _newPass,
keyboardType: TextInputType.text,
onSaved: (val) => _newPass = val,
)
TextFormField(
focusNode: _repeatPassFocus,
textInputAction: TextInputAction.next,
initialValue: _repeatNewPass,
keyboardType: TextInputType.text,
onSaved: (val) => _repeatNewPass= val,
),
],
);
#override
void initState() {
_passFocus = FocusNode();
_repeatPassFocus = FocusNode();
super.initState();
}
void fieldFocusChange(
BuildContext context, FocusNode currentFocus, FocusNode nextFocus) {
currentFocus.unfocus();
FocusScope.of(context).requestFocus(nextFocus);
}
#override
void dispose() {
_passFocus.dispose();
_repeatPassFocus.dispose();
super.dispose();
}

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

textInputAction: TextInputAction.next failing to move user focus to next element

When I use TextInputAction and set it to next, the button does change on my simulator device however it doesn't do anything, User interaction is not moved into the next element.
Have opened a ticket on the FLutter `gitHUb page as well as in the chat rooms but no help so far and would like to keep the ball rolling on my training :)
https://github.com/flutter/flutter/issues/47749
There is bug in your code , you put _passwordFocusNode in email field and cause error
when _emailEditingComplete() , focus stay in email
void _emailEditingComplete() {
FocusScope.of(context).requestFocus(_passwordFocusNode);
}
return TextField(
controller: _passwordController,
focusNode: _emailFocusNode,
return TextField(
controller: _emailController,
focusNode: _passwordFocusNode,
onEditingComplete and onSubmitted both work correctly in this case
working demo for test
full test code
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#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> {
int _counter = 0;
final TextEditingController _emailController = TextEditingController();
final TextEditingController _passwordController = TextEditingController();
final FocusNode _emailFocusNode = FocusNode();
final FocusNode _passwordFocusNode = FocusNode();
void _incrementCounter() {
setState(() {
_counter++;
});
}
#override
void dispose() {
// Clean up the focus node when the Form is disposed.
_emailFocusNode.dispose();
_passwordFocusNode.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>[
TextField(
focusNode: _emailFocusNode,
decoration: InputDecoration(
labelText: 'Email',
hintText: 'test#test.com',
),
keyboardType: TextInputType.emailAddress,
textInputAction: TextInputAction.next,
onEditingComplete: () {
FocusScope.of(context).requestFocus(_passwordFocusNode);
},
onSubmitted: (val) {
//FocusScope.of(context).requestFocus(_passwordFocusNode);
},
),
TextField(
focusNode: _passwordFocusNode,
decoration: InputDecoration(
labelText: 'password',
),
keyboardType: TextInputType.emailAddress,
textInputAction: TextInputAction.done,
onSubmitted: (val) {},
),
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.display1,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
bug