Show a text field dialog without being covered by keyboard? - dart

I'm trying to create a SimpleDialog that allows the user to enter their name. But when it is displayed the dialog is half hidden by the on-screen keyboard:
How can I get the Dialog to be fully visible?
Edit: I find it strange that the homepage widget (FocusVisibilityDemo) recognises the reduced height and therefore adjusts the position of the 'Push Me' button to remain in the center. Unfortunately the dialog doesn't behave the same way.
Here is my code:
import 'package:flutter/material.dart';
class FocusVisibilityDemo extends StatefulWidget {
#override
_FocusVisibilityDemoState createState() => new _FocusVisibilityDemoState();
}
class _FocusVisibilityDemoState extends State<FocusVisibilityDemo> {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(title: new Text('Text Dialog Demo')),
body: new Center(
child: new RaisedButton(
onPressed: _showDialog,
child: new Text("Push Me"),
),
),
);
}
_showDialog() async {
await showDialog<String>(
context: context,
child: new AlertDialog(
contentPadding: const EdgeInsets.all(16.0),
content: new Row(
children: <Widget>[
new Expanded(
child: new TextField(
autofocus: true,
decoration: new InputDecoration(
labelText: 'Full Name', hintText: 'eg. John Smith'),
),
)
],
),
actions: <Widget>[
new FlatButton(
child: const Text('CANCEL'),
onPressed: () {
Navigator.pop(context);
}),
new FlatButton(
child: const Text('OPEN'),
onPressed: () {
Navigator.pop(context);
})
],
),
);
}
}
void main() {
runApp(new MaterialApp(home: new FocusVisibilityDemo()));
}

If your use case is to add multiple TextFields inside your Dialog so your main Form does not get crowded, I think it is better if you build something more customizable than AlertDialog and SimpleDialog as they are used for simple activities (confirmations, radios..etc).
Otherwise, why do you want to use a Dialog for a single TextField ?
When we add multiple TextFields we should be careful about our design choices since other people will interact with this view to fill in the data, in this case I prefer to use fullscreenDialog property of PageRoute class. I am not sure if SimpleDialog can be suitable for that in Flutter.
Here is a quick example on how to use a FullScreenDialog, I hope this help and you should be able to modify it the way you want:
import 'package:flutter/material.dart';
void main() {
runApp(new MaterialApp(home: new MyApp(),));
}
class MyApp extends StatefulWidget {
#override
MyAppState createState() => new MyAppState();
}
class MyAppState extends State<MyApp> {
FullScreenDialog _myDialog = new FullScreenDialog();
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text("Fill this form"),
),
body: new Column(
children: <Widget>[
new TextField(controller: new TextEditingController(
text: "Add a single text field"),),
new Card(child: new ListTile(
title: new Text("Click to add your top 3 amazing skills"),
subtitle: new Text(
"${_myDialog._skillOne} ${_myDialog._skillTwo} ${_myDialog
._skillThree}"),
onTap: () {
Navigator.push(context, new MaterialPageRoute(
builder: (BuildContext context) => _myDialog,
fullscreenDialog: true,
));
},
),
),
],
)
);
}
}
class FullScreenDialog extends StatefulWidget {
String _skillOne = "You have";
String _skillTwo = "not Added";
String _skillThree = "any skills yet";
#override
FullScreenDialogState createState() => new FullScreenDialogState();
}
class FullScreenDialogState extends State<FullScreenDialog> {
TextEditingController _skillOneController = new TextEditingController();
TextEditingController _skillTwoController = new TextEditingController();
TextEditingController _skillThreeController = new TextEditingController();
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text("Add your top 3 skills"),
),
body: new Padding(child: new ListView(
children: <Widget>[
new TextField(controller: _skillOneController,),
new TextField(controller: _skillTwoController,),
new TextField(controller: _skillThreeController,),
new Row(
children: <Widget>[
new Expanded(child: new RaisedButton(onPressed: () {
widget._skillThree = _skillThreeController.text;
widget._skillTwo = _skillTwoController.text;
widget._skillOne = _skillOneController.text;
Navigator.pop(context);
}, child: new Text("Save"),))
],
)
],
), padding: const EdgeInsets.symmetric(horizontal: 20.0),)
);
}
}
EDIT
After doing some research, it seems that this is a bug in the current Flutter version, the temporary fix is also documented in this issue.
import 'package:flutter/material.dart';
void main() {
runApp(new MaterialApp(home: new FocusVisibilityDemo()));
}
class FocusVisibilityDemo extends StatefulWidget {
#override
_FocusVisibilityDemoState createState() => new _FocusVisibilityDemoState();
}
class _FocusVisibilityDemoState extends State<FocusVisibilityDemo> {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(title: new Text('Text Dialog Demo')),
body: new Center(
child: new RaisedButton(
onPressed: _showDialog,
child: new Text("Push Me"),
),
),
);
}
_showDialog() async {
await showDialog<String>(
context: context,
child: new _SystemPadding(child: new AlertDialog(
contentPadding: const EdgeInsets.all(16.0),
content: new Row(
children: <Widget>[
new Expanded(
child: new TextField(
autofocus: true,
decoration: new InputDecoration(
labelText: 'Full Name', hintText: 'eg. John Smith'),
),
)
],
),
actions: <Widget>[
new FlatButton(
child: const Text('CANCEL'),
onPressed: () {
Navigator.pop(context);
}),
new FlatButton(
child: const Text('OPEN'),
onPressed: () {
Navigator.pop(context);
})
],
),),
);
}
}
class _SystemPadding extends StatelessWidget {
final Widget child;
_SystemPadding({Key key, this.child}) : super(key: key);
#override
Widget build(BuildContext context) {
var mediaQuery = MediaQuery.of(context);
return new AnimatedContainer(
padding: mediaQuery.viewInsets,
duration: const Duration(milliseconds: 300),
child: child);
}
}

Related

Using tabbar with animated list results in duplicate global key error

I am trying to implement Flutter's Tab Bar with 3 tabs and an AnimatedList inside those tabs. I want to use the same list and filter the list according to each tab (past tasks, today's tasks, and future tasks), however during my implementation of the tab bar together with the animatedlist I am getting an error regarding a duplicate global key in the widget tree. https://pastebin.com/iAW6DH9m . What would be the best way to deal with this error? Thank you for any help.
edit: I tried using this method to fix this. Multiple widgets used the same GlobalKey while it did fix my error I was then unable to access "currentstate" method on the key to be able to add more items to the list. I then tried a similar method using using GlobalKey and it resulted in a similar error of duplicate global keys.
This is my tab bar implementation
import 'package:flutter/material.dart';
import 'search_widget.dart';
import 'animatedlist_widget.dart';
class Dashboard extends StatefulWidget {
#override
_DashboardState createState() => _DashboardState();
}
class _DashboardState extends State<Dashboard> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: new AppBar(
centerTitle: true,
actions: <Widget>[
new IconButton(icon: new Icon(Icons.grid_on), onPressed: null)
],
title: new Text('Dashboard'),
elevation: 0,
),
floatingActionButton: new FloatingActionButton(
onPressed: () {
_onFabPress(context);
},
child: new Icon(Icons.add)),
body: Scaffold(
appBar: new SearchWidget(
onPressed: () => print('implement search'),
icon: Icons.search,
title: 'Search',
preferredSize: Size.fromHeight(50.0),
),
body: DefaultTabController(
length: 3,
child: Scaffold(
appBar: PreferredSize(
preferredSize: Size.fromHeight(kToolbarHeight),
child: Container(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: new TabBar(
unselectedLabelColor: Colors.black45,
labelColor: Colors.white,
indicator: CustomTabIndicator(),
tabs: <Widget>[
new Tab(text: "Past"),
new Tab(text: "Today"),
new Tab(text: "Future")
]),
),
),
),
body: new TabBarView(
children: <Widget>[
AnimatedTaskList(),
AnimatedTaskList(),
AnimatedTaskList()
],
)
),
),
),
);
}
void _onFabPress(context) {
AnimatedTaskList().addUser();
}
/*showModalBottomSheet(
context: context,
builder: (BuildContext bc) {
return Container(
child: new Wrap(children: <Widget>[
new TextField(
decoration: InputDecoration(
border: OutlineInputBorder(),
hintText: 'Enter Task Title')),
new TextField(
decoration: InputDecoration(
border: OutlineInputBorder(),
hintText: 'Enter Task Details',
)),
]));
});
}*/
}
class CustomTabIndicator extends Decoration {
#override
BoxPainter createBoxPainter([onChanged]) {
// TODO: implement createBoxPainter
return new _CustomPainter(this, onChanged);
}
}
class _CustomPainter extends BoxPainter {
final CustomTabIndicator decoration;
_CustomPainter(this.decoration, VoidCallback onChanged)
: assert(decoration != null),
super(onChanged);
#override
void paint(Canvas canvas, Offset offset, ImageConfiguration configuration) {
// TODO: implement paint
assert(configuration != null);
assert(configuration.size != null);
final indicatorHeight = 30.0;
final Rect rect = Offset(
offset.dx, (configuration.size.height / 2) - indicatorHeight / 2) &
Size(configuration.size.width, indicatorHeight);
final Paint paint = Paint();
paint.color = Colors.blueAccent;
paint.style = PaintingStyle.fill;
canvas.drawRRect(RRect.fromRectAndRadius(rect, Radius.circular(30)), paint);
}
}
This is my animatedlist class:
import 'package:flutter/material.dart';
final GlobalKey<AnimatedListState> _listKey = GlobalKey();
class AnimatedTaskList extends StatefulWidget {
void addUser() {
int index = listData.length;
listData.add(
TaskModel(
taskTitle: "Grocery Shopping",
taskDetails: "Costco",
),
);
_listKey.currentState
.insertItem(index, duration: Duration(milliseconds: 500));
}
#override
_AnimatedTaskListState createState() => _AnimatedTaskListState();
}
class _AnimatedTaskListState extends State<AnimatedTaskList> {
#override
Widget build(BuildContext context) {
// TODO: implement build
return Scaffold(
body: SafeArea(
child: AnimatedList(
key: _listKey,
initialItemCount: listData.length,
itemBuilder:
(BuildContext context, int index, Animation animation) {
return Card(
child: FadeTransition(
opacity: animation,
child: ListTile(
title: Text(listData[index].taskTitle),
subtitle: Text(listData[index].taskDetails),
onLongPress: () {
//todo delete user
},
)));
})),
);
}
}
class TaskModel {
TaskModel({this.taskTitle, this.taskDetails});
String taskTitle;
String taskDetails;
}
List<TaskModel> listData = [
TaskModel(
taskTitle: "Linear Algebra",
taskDetails: "Chapter 4",
),
TaskModel(
taskTitle: "Physics",
taskDetails: "Chapter 9",
),
TaskModel(
taskTitle: "Software Construction",
taskDetails: "Architecture",
),
];
I fixed my issue by moving
final GlobalKey<AnimatedListState> _listKey = GlobalKey();
into my _AnimatedTaskListState class, and adding a constructor and private key to my AnimatedTaskList class
final GlobalKey<AnimatedListState> _key;
AnimatedTaskList(this._key);
#override
_AnimatedTaskListState createState() => _AnimatedTaskListState(_key);
then in my tab bar implementation I changed it to reflect my new constructor
AnimatedTaskList(GlobalKey<AnimatedListState>(debugLabel: "key 1"));
AnimatedTaskList(GlobalKey<AnimatedListState>(debugLabel: "key 2"));
AnimatedTaskList(GlobalKey<AnimatedListState>(debugLabel: "key 3"));

Make cards with texts and buttons dynamically

I'm making Notes app. I made cards with text and buttons dynamically (Create by clicking the button). But I have problem with Changing Text on CURRENT card. For example, I have 3 cards with own texts and buttons and I want to change text on 2nd card but text is changing on the 3rd card. How can I solve this problem?
3 cards with texts and buttons
Change Text Page
In the past, I've tried making list to collect texts, but i dont know how to identify current card.
full main.dart
import 'package:flutter/material.dart';
import './changeTextPage.dart';
int count = 0;
String titlecard = '';
String textcard = '';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Notes',
theme: ThemeData(
primarySwatch: Colors.deepPurple
),
home: HomePage(title: 'Notes',),
);
}
}
class HomePage extends StatefulWidget {
HomePage({Key key, this.title}) : super(key: key);
final title;
#override
HomePageState createState() => HomePageState();
}
class HomePageState extends State<HomePage> {
#override
Widget build(BuildContext context) {
List<Widget> cards = new List.generate(count, (int i) => new MyCard());
return Scaffold(
appBar: AppBar(
title: Text('Notes'),
),
body: LayoutBuilder(
builder: (context, constraint) {
return Column(
children: <Widget>[
Container(
height: 650.0,
child: new ListView(
children: cards,
scrollDirection: Axis.vertical,
),
),
],
);
}
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
setState(() {
Navigator.push(context, MaterialPageRoute(
builder: (context) => changeText())
);
});
},
),
);
}
}
class MyCard extends StatefulWidget {
#override
myCard createState() => myCard();
}
class myCard extends State<MyCard> {
int myCount = count;
void click() {
setState(() {
Navigator.push(context, MaterialPageRoute(
builder: (context) => setNewText())
);
});
}
#override
Widget build(BuildContext context) {
return Center(
child: Card(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
ListTile(
leading: Icon(Icons.album),
title: Text(titlecard),
subtitle: Text(textcard),
),
ButtonTheme.bar( // make buttons use the appropriate styles for cards
child: ButtonBar(
children: <Widget>[
FlatButton(
child: const Text('Change Text'),
onPressed: click,
),
FlatButton(
child: const Text('LISTEN'),
onPressed: () { /* ... */ },
),
],
),
),
],
),
),
);
}
}
class setNewText extends StatefulWidget {
#override
SetNewText createState() => SetNewText();
}
class SetNewText extends State<setNewText> {
final titleController = TextEditingController();
final textController = TextEditingController();
final formkey = GlobalKey<FormState>();
void _submit() {
setState(() {
if (formkey.currentState.validate()) {
formkey.currentState.save();
Navigator.pop(context);
titlecard = titleController.text;
textcard = textController.text;
}
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Change Title'),
),
body: Column(
children: <Widget>[
Card(
child: Padding(
padding: EdgeInsets.all(2.0),
child: Form(
key: formkey,
child: Column(
children: <Widget>[
TextFormField(
controller: titleController,
decoration: InputDecoration(
labelText: 'Title'
),
validator: (value) => value.length < 1 ? 'Invalid Title' : null,
onSaved: (value) => value = titleController.text,
),
TextFormField(
controller: textController,
decoration: InputDecoration(
labelText: 'Text'
),
validator: (text) => text.length < 1 ? 'Invalid Text' : null,
onSaved: (text) => text = textController.text,
)
],
),
),
),
),
FlatButton(
textColor: Colors.deepPurple,
child: Text('SUBMIT'),
onPressed: _submit,
),
],
)
);
}
}
changeTextPage.dart
import 'package:flutter/material.dart';
import './main.dart';
class changeText extends StatefulWidget {
#override
ChangeText createState() => ChangeText();
}
class ChangeText extends State<changeText> {
myCard s = myCard();
final titleController = TextEditingController();
final textController = TextEditingController();
final formkey = GlobalKey<FormState>();
void _submit() {
setState(() {
if (formkey.currentState.validate()) {
formkey.currentState.save();
Navigator.pop(context);
count++;
titlecard = titleController.text;
textcard = textController.text;
}
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Change Title'),
),
body: Column(
children: <Widget>[
Card(
child: Padding(
padding: EdgeInsets.all(2.0),
child: Form(
key: formkey,
child: Column(
children: <Widget>[
TextFormField(
controller: titleController,
decoration: InputDecoration(
labelText: 'Title'
),
validator: (value) => value.length < 1 ? 'Invalid Title' : null,
onSaved: (value) => value = titleController.text,
),
TextFormField(
controller: textController,
decoration: InputDecoration(
labelText: 'Text'
),
validator: (text) => text.length < 1 ? 'Invalid Text' : null,
onSaved: (text) => text = textController.text,
)
],
),
),
),
),
FlatButton(
textColor: Colors.deepPurple,
child: Text('SUBMIT'),
onPressed: _submit,
),
],
)
);
}
}
Okay, so you happen to make some common mistakes, one of which is critical.
most importantly don't use global variables! As you do with count, titlecard and textcard.
there is a practice to name stateful widgets with PascalCase and corresponding states just like the widget but prefixed with an underscore (_) to make it private and suffixed by the State word.
The correct approach for this (or one of them) would be to have a widget that would be your screen with a form to edit stuff and it would pop some struct with user values on submit:
class ChangeTextScreen extends StatefulWidget {
_ChangeTextScreenState createState() => _ChangeTextScreenState();
}
class _ChangeTextScreenState extends State<ChangeTextScreen> {
void _submit() {
setState(() {
formkey.currentState.save();
Navigator.pop(ChangeTextResult(title: titleController.text, text: textController.text));
});
}
// Rest of your code...
}
class ChangeTextResult {
final String title;
final String text;
ChangeTextResult({#required this.title, #required this.text});
}
You should also have a place where you store your notes in some kind of a list. Your main screen looks like a good place for it. Once your app will be bigger, think about using scoped_model or Redux or something.
So let's add a Note class and a list with your notes to your main screen:
class Note {
String title;
String text;
Note(this.title, this.text);
}
class HomePageState extends State<HomePage> {
List<Note> _notes = [Note('Test', 'Some test note')];
#override
Widget build(BuildContext context) {
ListView cards = ListView.builder(
itemCount: _notes.length,
itemBuilder: (context, index) => MyCard(
title: _notes[index].title,
text: _notes[index].text,
onEdit: (title, text) => setState(() { // We'll get back to that later
_notes[index].title = title;
_notes[index].text = text;
})
));
// (...)
Your MyCard widget (try to use better names next time) should contain some kind of information about its content, one of the best approaches would be to pass this info to its constructor, just like that:
class MyCard extends StatefulWidget {
final String title;
final String text;
final Function onEdit;
MyCard({Key key, #required this.title, #required this.text, #required this.onEdit}) : super(key: key);
#override
_MyCardState createState() => _MyCardState();
}
Having this Key parameter is a good practice.
And use those parameters in your _MyCardState class (I renamed it from _myCard):
// (...)
children: <Widget>[
ListTile(
leading: Icon(Icons.album),
title: Text(widget.title),
subtitle: Text(widget.text),
),
// (...)
Returning to the moment where you open your ChangeTextScreen, you should assign the result of Navigation.push() to a variable. This is your result, you can deal with it (once we check it for null, the user could have returned from this screen and then the result would be null).
void click() {
setState(() {
final ChangeTextResult result = Navigator.push(context, MaterialPageRoute(
builder: (context) => ChangeTextScreen())
);
if (result != null) {
widget.onEdit(result.title, result.text);
}
});
}
Do you remember that onEdit parameter (I mentioned it in a comment in the code above)? We call that parameter here.
That's it I think. I could have mixed some concepts of your app, but I think you'll manage to get my point anyways.
I quite rewrote all of your code. I think it will be easier for you to start again from scratch and have those tips in mind. Also, try to Google some similar things (like a simple Todo application) or do Getting started from flutter.io with part two! That should give you a nice idea on how to resolve that common problem in Flutter.
And also, read about good practises in Flutter and Dart. Things like correctly formatting your code are really important.
BTW that's my longest answer on Stack Overflow so far. I hope you'll appreciate that.

How to read data from Flutter elements

I've the below code for entering some data, I do not know how to handle it!
i.e. What shall I write at the onPressed for the IconButton so that the data is read from all the elements (name, birthday, ...)? and how to display it in Dialog to check if read correctly?
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
theme: new ThemeData(
primarySwatch: Colors.deepOrange,
),
home: new MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
var _value=false;
double _bodyHeight=0.0;
void onchange(bool value) {
setState(() {
_value = value;
this._bodyHeight = (value == true) ? 400.0 : 0.0;
});
}
#override
Widget build(BuildContext context) {
return new Scaffold(
backgroundColor: Colors.grey[500],
appBar: new AppBar(
title: new Text(widget.title),
),
body: new SingleChildScrollView(
child: new Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new Card(
child: new Container(
height: 50.0,
child: new Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
new Text("Select me pls"),
new Switch(value: _value, onChanged: (bool value) => onchange(value)),
],
),
),
),
new Card(
child: new AnimatedContainer(
child: new ListView(
children: <Widget>[
new ListTile(
leading: const Icon(Icons.person),
title: new TextField(
decoration: new InputDecoration(
hintText: "Name",
),
),
),
new ListTile(
leading: const Icon(Icons.phone),
title: new TextField(
decoration: new InputDecoration(
hintText: "Phone",
),
),
),
new ListTile(
leading: const Icon(Icons.email),
title: new TextField(
decoration: new InputDecoration(
hintText: "Email",
),
),
),
const Divider(
height: 1.0,
),
new ListTile(
leading: const Icon(Icons.label),
title: const Text('Nick'),
subtitle: const Text('None'),
),
new ListTile(
leading: const Icon(Icons.today),
title: const Text('Birthday'),
subtitle: const Text('February 20, 1980'),
),
new IconButton(icon: const Icon(Icons.save),
onPressed: null
/*
*
* What shall I write here to read the data in the elements
*
*
*
* */
),
],
),
curve: Curves.easeInOut,
duration: const Duration(milliseconds: 500),
height: _bodyHeight,
// color: Colors.red,
),
),
],
),
),
);
}
}
One way is that TextFields take a property called TextEditingController which allow you to access the value of the TextField.
And to show a dialog you can just call showDialog() function.
class TextFieldExample extends StatefulWidget {
#override
_TextFieldExampleState createState() => new _TextFieldExampleState();
}
class _TextFieldExampleState extends State<TextFieldExample> {
TextEditingController c1;
TextEditingController c2;
#override
void initState() {
c1 = new TextEditingController();
c2 = new TextEditingController();
super.initState();
}
#override
Widget build(BuildContext context) {
return new Scaffold(
body: new Center(
child: new Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new TextField (
controller: c1,
),
new TextField(
controller: c2,
),
new OutlineButton(onPressed: () {
showDialog(child: new AlertDialog(
content: new Text("You entered ${c1.text} ${c2.text} ")
),
context: context
);
},
child: new Text("Show Dialog"),
)
],
),
),
);
}
}

Flutter - Set focus to another TextFormField in onFieldSubmitted

I'm working with subsequent TextFormFields in an AlertDialog where the submit of an input should set the focus on the next input. I'm currently trying to achieve this using the following code:
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
home: new MainScreen()
);
}
}
class MainScreen extends StatefulWidget {
#override
_MainScreenState createState() => new _MainScreenState();
}
class _MainScreenState extends State<MainScreen> {
final TextEditingController _firstFieldController = new TextEditingController();
final TextEditingController _secondFieldController = new TextEditingController();
FocusNode _focusNode ;
#override
void initState() {
super.initState();
_focusNode = new FocusNode();
}
#override
void dispose() {
_focusNode.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
var firstField = new TextFormField(
controller: _firstFieldController,
keyboardType: TextInputType.phone,
decoration: new InputDecoration(
labelText: 'First field',
contentPadding: new EdgeInsets.fromLTRB(.0, 8.0, .0, 4.0),
counterText: ' '
),
onFieldSubmitted: (String textInput) {
FocusScope.of(context).requestFocus(_focusNode);
},
);
var secondField = new TextFormField(
focusNode: _focusNode,
controller: _secondFieldController,
keyboardType: TextInputType.phone,
decoration: new InputDecoration(
labelText: 'Second field',
contentPadding: new EdgeInsets.fromLTRB(.0, 8.0, .0, 4.0),
counterText: ' '
),
);
return new Scaffold(
appBar: new AppBar(
title: new Text('Main'),
),
body: new Center(
child: new Text('Hello from the main screen!'),
),
floatingActionButton: new FloatingActionButton(
child: new Icon(Icons.add),
onPressed: () {
showDialog(
context: context,
child: new AlertDialog(
title: new Text('Form'),
content: new Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
new Container(
margin: new EdgeInsets.only(bottom: 4.0),
child: firstField
),
secondField
],
),
)
);
},
),
);
}
}
This is not setting the focus in the second field. However, if I close the dialog and open it again, the second field comes focused.
Can anyone help me with this?
That is because your AlertDialog needs to be in its own StatefulWidget. Your current code shows that your state, TextFields and AlertDialog are part of your MainScreen class, which means any updates has to happen first in the MainScreen context, while what you need is to have all your updates happen in the AlertDialog context instead.
TL;DR: Refactor your AlertDialog into its own StatefulWidget with TextFields, and FocusNode.

Flutter - Radio Animation is not showing up on showDialog

I'm trying to create a Radio in a showDialog, however the animation that occurs on Radio does not appear in showDialog.
For example: when tapped in foo2 nothing happens, and when you exit in showDialog and go back to it, foo2 is selected.
Below is the code and a gif showing what is happening:
import "package:flutter/material.dart";
void main() {
runApp(new ControlleApp());
}
class ControlleApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: "My App",
home: new HomePage(),
);
}
}
class HomePage extends StatefulWidget {
#override
HomePageState createState() => new HomePageState();
}
enum _RadioGroup {
foo1,
foo2
}
class HomePageState extends State<HomePage> {
_RadioGroup _itemType = _RadioGroup.foo1;
void changeItemType(_RadioGroup type) {
setState(() {
_itemType = type;
});
}
void showDemoDialog<T>({ BuildContext context, Widget child }) {
showDialog<T>(
context: context,
child: child,
);
}
#override
Widget build(BuildContext context){
return new Scaffold(
appBar: new AppBar(backgroundColor: new Color(0xFF26C6DA)),
body: new Container(
child: new Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new InkWell(
onTap: (){
showDemoDialog<String>(
context: context,
child: new SimpleDialog(
title: const Text("show"),
children: <Widget>[
new Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new Radio<_RadioGroup>(
groupValue: _itemType,
value: _RadioGroup.foo1,
onChanged: changeItemType
),
const Text("foo1"),
new Radio<_RadioGroup>(
groupValue: _itemType,
value: _RadioGroup.foo2,
onChanged: changeItemType
),
const Text("foo2"),
],
)
],
)
);
},
child: new Container(
margin: new EdgeInsets.only(top: 16.0, bottom: 8.0),
child: new Text("Show"),
),
)
],
),
)
);
}
}
Remember that components are immutable.
When you call showDialog, the content of that dialog won't change even if HomePage does.
The solution is easy. You need to refactor a bit your code to something like :
showDialog(
context: context,
builder: (context) => MyForm()
)
and instead of changing the state of HomePage, you instead change the state of MyForm.
example :
class Test extends StatelessWidget {
void onSubmit(String result) {
print(result);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: RaisedButton(
onPressed: () => showDialog(context: context, builder: (context) => MyForm(onSubmit: onSubmit)),
child: Text("dialog"),
),
),
);
}
}
typedef void MyFormCallback(String result);
class MyForm extends StatefulWidget {
final MyFormCallback onSubmit;
MyForm({this.onSubmit});
#override
_MyFormState createState() => _MyFormState();
}
class _MyFormState extends State<MyForm> {
String value = "foo";
#override
Widget build(BuildContext context) {
return SimpleDialog(
title: Text("My form"),
children: <Widget>[
Radio(
groupValue: value,
onChanged: (value) => setState(() => this.value = value),
value: "foo",
),
Radio(
groupValue: value,
onChanged: (value) => setState(() => this.value = value),
value: "bar",
),
FlatButton(
onPressed: () {
Navigator.pop(context);
widget.onSubmit(value);
},
child: new Text("submit"),
)
],
);
}
}

Resources