Flutter GestureDetector onTap not working - flutter

Trying to make a TextFormField unfocus with a GestureDetector when user taps outside but I can't get it to work. onTap never fires.
class EditScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: new GestureDetector(
onTap: () {
print('this does not fire, why???????????');
// this is my attempt to unfocus textformfield when click away
FocusScope.of(context).requestFocus(new FocusNode());
},
child: SingleChildScrollView(
child: Column(
children: <Widget>[
TextFormField(
maxLines: null,
),
],
),
),
),
);
}
}

Try wrap your Scaffold with Gesture Detector and then onTap function:
onTap: () {
FocusScopeNode currentFocus = FocusScope.of(context);
if (!currentFocus.hasPrimaryFocus) {
currentFocus.unfocus();
}
So it will fire everytime you tap the scaffold

Related

Flutter bug with PopupMenuButton and TextFiend inside AlertDialog

I have an AlertDialog that contains a TextField and a PopupMenuButton. When the TextField has focus and the keyboard is open, the AlertDialog is in a "raised" position, but if I press the PopupMenuButton, the TextField get unfocus, and the animations of the AlertDialog "going down" (because the keyboard disappeard) and the PopupMenuButton opening start togheter, and result in a wrong position of PopupMenuItems. How can I solve this?
I tried to edit the PopupMenuButton class but I don't know how to wait until the AlertDialog is repositioned to show the popup menu.
This is a sample of code that replicate the problem
return AlertDialog(
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextField(),
PopupMenuButton(itemBuilder: (BuildContext context)=>[PopupMenuItem(child: Text('123')),PopupMenuItem(child: Text('456'))])
],
),
);
This is a gif showing the bug:
https://i.stack.imgur.com/dNjq1.gif
after getting some hours for researching that was PopupMenuButton normal behaviour, when I tried to recreate the PopupMenuButton manually, it work as that because when clicked it get the first position of the below menubutton so when i recreate it, it look like this
class TestWidget extends StatefulWidget {
#override
_TestWidgetState createState() => _TestWidgetState();
}
class _TestWidgetState extends State<TestWidget> {
void _showPopupMenu(Offset offset) async {
double left = offset.dx;
double top = offset.dy;
await showMenu(
context: context,
position: RelativeRect.fromLTRB(left, top, 0, 0),
items: [
const PopupMenuItem<String>(value: 'Doge', child: Text('Doge')),
const PopupMenuItem<String>(value: 'Lion', child: Text('Lion')),
],
elevation: 8.0,
);
}
#override
Widget build(BuildContext context) {
return AlertDialog(
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
const TextField(),
GestureDetector(
behavior: HitTestBehavior.opaque,
onTapDown: (TapDownDetails details) {
FocusScopeNode currentFocus = FocusScope.of(context);
if (!currentFocus.hasPrimaryFocus) {
currentFocus.unfocus();
}
Future.delayed(const Duration(milliseconds: 500), () {
setState(() {
_showPopupMenu(details.globalPosition);
});
});
},
child: (const Icon(Icons.blender_outlined)),
),
PopupMenuButton(
itemBuilder: (BuildContext context) => [
const PopupMenuItem(child: Text('123')),
const PopupMenuItem(child: Text('456'))
])
],
),
);
}
}
so i think there's no better option for it. prefer not using popup and just design the design in a singlechildscrollview

Flutter: Calling function when clicking outside the TextField

I want to call a function when users tap outside the TextField, after clicking the textfield to unfocus.
return new GestureDetector(
onTap: () {
FocusScope.of(context).requestFocus(new FocusNode());
print('hey');
},
child: Container(
color: Colors.white,
child: TextField(
onChanged: (text) {},
),
),
);
I am trying to use this GestureDetector but it is not working as I would expect. How can I unfocus the textfield by clicking outside of the textfield?
Wrap the whole Scaffold in GestureDetector will do
return GestureDetector(
onTap: () {
FocusScope.of(context).requestFocus(new FocusNode());
},
child: Scaffold(...));

Navigator.of(context).pop(); makes the whole screen disappear, not the popup

In my flutter app I want to show the popup with two buttons when user presses a button, I'm doing it with the following code:
class ProfileScreen extends StatefulWidget {
#override
_ProfileScreenState createState() {
return _ProfileScreenState();
}
}
class _ProfileScreenState extends State<ProfileScreen> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: SingleChildScrollView(
padding: EdgeInsets.all(16),
child: ConstrainedBox(
constraints: BoxConstraints(maxWidth: 400),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
...[
Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: ElevatedButton(
onPressed: () {
showAlertDialog(context);
},
child: Text('Remove account'),
),
),
and the code for showAlertDialog is as follows:
showAlertDialog(BuildContext context) {
// set up the buttons
Widget cancelButton = FlatButton(
child: Text("Cancel"),
onPressed: () {
Navigator.of(context).pop();
},
);
Widget continueButton = FlatButton(
child: Text("Continue"),
onPressed: () {},
);
// set up the AlertDialog
AlertDialog alert = AlertDialog(
title: Text("AlertDialog"),
content: Text("Would you like to continue learning how to use Flutter alerts?"),
actions: [
cancelButton,
continueButton,
],
);
// show the dialog
showDialog(
context: context,
builder: (BuildContext context) {
return alert;
},
);
}
It works, the popup shows correctly, but when I click cancel, popup stays up front, but the screen beneath it goes away (and it stays black). Why so? And how could I fix it? Thanks!
Navigator.of(context, rootNavigator: true).pop();

How to hide an Overlay with tap somewhere on the Screen with flutter

I wrote a Widget that contains a Button. On tap on this Button, a small "box" is shown with some text information inside.
I solved this with an Overlay. Inside of this Overlay is a GestureDetector. When you tap on the Overlay the Overlay will be hidden.
Because the Overlay is not filling the whole screen it will not be closed if you tap behind this Overlay.
But I want that the Overlay will be hidden when you tap somewhere on the screen, maybe outside of the Overlay.
Here is the part of the Overlay:
class _OverlayHelpWidgetState extends State<OverlayHelpWidget> {
OverlayEntry _overlayEntry;
OverlayState _overlayState;
bool _isVisible = false;
#override
Widget build(BuildContext context) {
return Row(
children: [
Center(
child: GestureDetector(
child: Icon(
Icons.help,
color: Colors.green[800],
),
onTap: () {
showHelp();
},
),
)
],
mainAxisAlignment: MainAxisAlignment.end,
);
}
OverlayEntry _createOverlayEntry() {
return OverlayEntry(
builder: (context) => Align(
alignment: Alignment.center,
child: GestureDetector(
child: _createContent(),
onTap: () {
hideHelp();
},
),
),
);
}
showHelp() async {
if (!_isVisible) {
_overlayState = Overlay.of(context);
_overlayEntry = _createOverlayEntry();
_overlayState.insert(_overlayEntry);
_isVisible = true;
}
}
void hideHelp() {
_isVisible = false;
_overlayEntry.remove();
}
Can someone help me to solve it?
thanks, Bhuelse
Thanks pskink,
thats the solution. Changed to this and it works:
OverlayEntry _createOverlayEntry() {
return OverlayEntry(
builder: (context) => GestureDetector(
behavior: HitTestBehavior.translucent,
child: Align(
alignment: Alignment.center,
child: _createContent(),
),
onTap: () {
hideHelp();
},
),
);
}

flutter setstate rebuild only one child

in flutter I need that when I call setstate, it only rebuilds a widget
I put 2 children in a stack, I need that when a button is pressed, only the second one is rebuilt.
bool popup = false;
Scaffold(
appBar: AppBar(
title: const Text('TEST'),
actions: <Widget>[
IconButton( // + BUTTON
icon: Icon(Icons.add),
onPressed: () {
setState(() {
popup = true;
});
},
),
IconButton( // - BUTTON
icon: Icon(Icons.remove),
onPressed: () {
setState(() {
popup = false;
});
),
],
),
body: SafeArea(
child: Stack(
children: <Widget>[
Container( // FIRST WIDGET
key: ValueKey(1),
child: Text("Random - "+new Random().nextInt(20).toString())
),
popup ? Center(child: Text("abc")) : Text("") , // SECOND WIDGET
],
),
),
);
I expect that when I press the "+" button only the second widget will be re-built, but now it will rebuild all the contents of the stack.
thank you all.
From the official docs we can read:
"When setState() is called on a State, all descendent widgets rebuild. Therefore, localize the setState() call to the part of the subtree whose UI actually needs to change. Avoid calling setState() high up in the tree if the change is contained to a small part of the tree."
My suggestion, and I use it most of the times, is separate the widget that you want to rebuild in a new StatefulWidget. This way the setState only will be rebuild that widget.
class MyAppBar extends StatefulWidget
...
class _MyAppBarState extends State<MyAppBar> {
bool popup = false;
#override
Widget build(BuildContext context) {
return AppBar(
title: const Text('TEST'),
actions: <Widget>[
IconButton( // + BUTTON
icon: Icon(Icons.add),
onPressed: () {
setState(() {
popup = true;
});
},
),
IconButton( // - BUTTON
icon: Icon(Icons.remove),
onPressed: () {
setState(() {
popup = false;
});
),
],
),
}
Then call it in your Scaffold:
Scaffold(
appBar: MyAppBar(),
Other method I can suggest is using ValueNotifier or notifyListeners(). Please read this page Avoid rebuilding all the widgets repetitively. It is well explained.
Another option is to use ValueListenableBuilder:
class _MyHomePageState extends State<MyHomePage> {
final ValueNotifier<bool> popup = ValueNotifier<bool>(false);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('TEST'),
actions: <Widget>[
IconButton(
// + BUTTON
icon: Icon(Icons.add),
onPressed: () {
popup.value = true;
}),
IconButton(
// - BUTTON
icon: Icon(Icons.remove),
onPressed: () {
popup.value = false;
})
],
),
body: Center(
child: ValueListenableBuilder<bool>(
valueListenable: popup,
builder: (context, value, _) {
return Stack(
children: [
Text("Random - " + new Random().nextInt(20).toString()),
popup.value ? Center(child: Text("abc")) : Text(""),
],
);
}),
),
);
}
}
You can use StreamBuilder:
StreamController<bool> popup = StreamController<bool>();
#override
void dispose() {
popup.close();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('TEST'),
actions: <Widget>[
IconButton( // + BUTTON
icon: Icon(Icons.add),
onPressed: () => popup.add(true),
),
IconButton( // - BUTTON
icon: Icon(Icons.remove),
onPressed: () => popup.add(false),
),
],
),
body: SafeArea(
child: Stack(
children: <Widget>[
Container( // FIRST WIDGET
key: ValueKey(1),
child: Text("Random - "+new Random().nextInt(20).toString())
),
StreamBuilder<bool>(
stream: popup.stream,
initialData: false,
builder: (cxt, snapshot) {
return snapshot.data ? Center(child: Text("abc")) : Text("");
},
)
],
),
),
);
}
Remove the setState from the widget you don't want to be changed. And only use setState for the ones you need to rebuild
Or you can consider using inheritedModel widget
Here is the example from where you can learn how to build an Inherited model widget to update only specific widgets rather than the whole widgets.
https://medium.com/flutter-community/flutter-state-management-setstate-fn-is-the-easiest-and-the-most-powerful-44703c97f035