Flutter Dismissible causing unnecesary redraw when pushed navigated on top - flutter

I'm making an app with Flutter, and I found a weird behaviour that I got to reproduce in a small demo: https://pastebin.com/ZJd2fnHK
The objective is to have a TextField that when in focus should select the whole text. I achieved this with a FocusNode.
Everything was working fine, until I set a Dismissible widget to remove items from a list. For some reason (probably drawing ones) the whole page has a rebuild and the focusnode state is lost.
It's very interesting since the issue only happens when the widget is within a page that has being pushed by the navigator. The same Widget works fine in the root Page.
The Widget is the following:
Widget build(BuildContext context) {
textControllerA.text = "Hello";
textControllerB.text = "World";
return Column(children: [
Dismissible(
key: Key("potatoes"),
onDismissed: (direction) {},
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Padding(
child: TextField(
focusNode: focusNodeA,
controller: textControllerA,
),
padding: EdgeInsets.all(20),
),
],
),
),
Padding(
child: TextField(
focusNode: focusNodeB,
controller: textControllerB,
),
padding: EdgeInsets.all(20),
),
]);
}
Maybe I should try to store the focus' state in state, but that'd have some overhead. Or maybe (possibly) I'm doing something very wrong, in which case I would love to have some guidance.
Cheers

You should use StatefulWidget and call setState
onDismissed: (direccion) {
setState(() {
...
});

Related

Animated Switcher in combination with case conditioned Streambuilder

Following question:
I managed to get a lot further with my dosage calculator app and the state management procedure and now I'm trying to scale things up visually speaking.
So I wanted to change the built widget based on a dropdown menu which actually worked out fine but I'm trying to implement an AnimatedSwitcher so every time the user changes the dropdown menu, the old widget fades out and the new one in instead of just switching. Searched for solutions, found one but I don't know if I implemented it the right way, since I'm not getting any animation, but no error message neither.
I'm supposing I either used the wrong child or something like a unique key is missing (which I don't know how to implement)
Here are the necessary parts of my code:
DropdownMenu:
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 12.0),
child:DropdownButtonHideUnderline(
child: DropdownButton<String>(
value: selectedItem,
onChanged: (String string) => setState(() {
streamController.sink.add(string);
return selectedItem = string;
}),
selectedItemBuilder: (BuildContext context) {
return items.map<Widget>((String item) {
return Text(item,
//style: TextStyle(fontWeight: FontWeight.bold),
textAlign: TextAlign.right,
);
}).toList();
},
items: items.map((String item) {
return DropdownMenuItem<String>(
child: Text('$item',
//style: TextStyle(fontWeight: FontWeight.bold),
textAlign: TextAlign.right,
),
value: item,
);
}).toList(),
),
),
);
}
}
StreamBuilder and AnimatedSwitcher:
StreamBuilder(
stream: streamController.stream,
builder: (context, snapshot) {
return AnimatedSwitcher(
duration: Duration(seconds: 1),
child: updateBestandteile(snapshot.data),
);
},
),
Example of condition:
Padding updateBestandteile(String i) {
switch (i) {
case "MMF":
{
return Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
height: 200,
decoration: BoxDecoration(
color: b,
borderRadius: BorderRadius.circular(10.0)
),
child: Align(
alignment: Alignment.center,
child: Row(
children: [
Column(
children: [
Text('Zu verwendende Präparate:',
style: TextStyle(fontWeight: FontWeight.bold)),
Text('Medetomidin 1mg/ml'),
Text('Midazolam 5mg/ml'),
Text('Fentanyl 0.5mg/ml'),
Text('NaCl 0,9%'),
],
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
),
Column(
children: [
Text('Anzumischende Menge:',
style: TextStyle(fontWeight: FontWeight.bold),
),
Text((MedetomidindosierungmgprokgKGW*selectedamount*selectedweight/(1000*Medetomidinmgproml)).toString()+"ml"),
Text((MidazolamdosierungmgprokgKGW*selectedamount*selectedweight/(1000*Midazolammgproml)).toString()+"ml"),
Text((FentanyldosierungmgprokgKGW*selectedamount*selectedweight/(1000*Fentanylmgproml)).toString()+"ml"),
Text((((MedetomidindosierungmgprokgKGW*selectedamount*selectedweight/(1000*Medetomidinmgproml))+(MidazolamdosierungmgprokgKGW*selectedamount*selectedweight/(1000*Midazolammgproml))+(FentanyldosierungmgprokgKGW*selectedamount*selectedweight/(1000*Fentanylmgproml)))*4).toString()+"ml"),
],
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
),
],
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
),
),
),
);
}
break;
Hope you might be able to help as you did last time :) Thanks in advance!
Cheers,
P
The issue might be that you are not setting a key. If the new child widget is of the same type as the old widget type then AnimatedSwitcher will NOT animate between them since as as far as the framework is concerned, they are the same widget. Set a unique ValueKey on each child child widget that you wish to animate.
Please refer to Flutter Docs for AnimatedSwitcher and check out the AnimatedSwitcher Widget of the Week video by Flutter Team.
If the "new" child is the same widget type and key as the "old" child,
but with different parameters, then AnimatedSwitcher will not do a
transition between them, since as far as the framework is concerned,
they are the same widget and the existing widget can be updated with
the new parameters. To force the transition to occur, set a Key on
each child widget that you wish to be considered unique (typically a
ValueKey on the widget data that distinguishes this child from the
others).

getSelectedText on inactive InputConnection flutter

I have this weird problem,When i try to enter value to textfield the keyboard comes up when i try to type the the keyboard goes away , i am also doing fill up Username,Place,mobile that i fetching from sharedprefrence and it's working,but when i try to enter age,height,weight keyboard comes up & goes with in seconds,this is the error/warning i am getting,there is no problem in flutter doctor,
W/IInputConnectionWrapper(23904): getSelectedText on inactive InputConnection
W/IInputConnectionWrapper(23904): getTextAfterCursor on inactive InputConnection
W/IInputConnectionWrapper( 8756): beginBatchEdit on inactive InputConnection
W/IInputConnectionWrapper( 8756): endBatchEdit on inactive InputConnection
Code & View
class RegistrationScreenState extends State<RegistrationScreen> {
TextEditingController mobile = TextEditingController();
TextEditingController Username = TextEditingController();
TextEditingController Place = TextEditingController();
TextEditingController age = TextEditingController();
TextEditingController height = TextEditingController();
TextEditingController weight = TextEditingController();
void initState() {
getDetails();
}
#override
Widget build(BuildContext context) {
Size size = MediaQuery
.of(context)
.size;
return Scaffold(
appBar: AppBar(
title: Text("Registration", style: TextStyle(color: Colors.black)),
backgroundColor: Colors.orange,
),
body: SingleChildScrollView(
child: Stack(
children: [
Column(
children: [
Container(
child: Image.asset('assets/images/gym.png',
height: 150,
width: double.infinity,
fit: BoxFit.fitWidth),
),
SizedBox(
height: 50,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Expanded(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: TextField(
controller: Username,
decoration: InputDecoration(
prefixIcon: Icon(Icons.perm_identity),
),
),
),
),
Expanded(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: TextField(
controller: mobile,
decoration: InputDecoration(
prefixIcon: Icon(Icons.mobile_screen_share),
),
),
),
),
],
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Align(
alignment: Alignment.centerLeft,
child: Text(
"User Information",
style: TextStyle(
fontSize: 15, fontWeight: FontWeight.bold),
)),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Expanded(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: TextField(
controller: Place,
decoration: InputDecoration(),
),
),
),
Expanded(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: TextField(
controller: age,
decoration: InputDecoration(
hintText: 'Age',
),
),
),
),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Expanded(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: TextField(
controller: height,
decoration: InputDecoration(
hintText: 'Height(in cm)',
),
),
),
),
Expanded(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: TextField(
controller: weight,
decoration: InputDecoration(
hintText: 'Weight(in kg)',
),
),
),
),
],
),
],
),
],
),
),
);
}
// get & fill Shareprefrece data to textfield
getDetails() async {
Future user = SharedPrefrence().getUserMobile();
Future name = SharedPrefrence().getUserName();
Future place = SharedPrefrence().getUserPlace();
user.then((data) async {
var mobile_no = data;
setState(() {
if (mobile_no.isNotEmpty) {
mobile.text = mobile_no;
}
else
{
mobile.text = "";
}
});
});
name.then((data) async {
var user_name = data;
setState(() {
if (user_name.isNotEmpty) {
Username.text = user_name;
}
});
});
place.then((data) async {
var user_place = data;
setState(() {
if (user_place.isNotEmpty) {
Place.text = user_place;
}
});
});
}
}
I am not sure that if this will help you but I was also facing the same issue.
I was getting this error when I was using the TextFormField with in Form and the issue was that I created the global form key with in the build method.
class _AddTaskState extends State<AddTask> {
final _formKey = GlobalKey<FormState>(); // <-
String name;
String description;
#override
Widget build(BuildContext context) {
// first I was using that here.
return Scaffold(
In my case I had TextFeild in AnimatedSwitcher. The problem was i had key: UniqueKey() inside my widget that containes the TextFeild. So When ever i touched the text feid UniqueKey() gets called and then the widget tree gets regernated.
If you are using form validator, move your form key out of your build context. This should solve your issue.
Instead of -
class SingInScreen extends StatelessWidget {
final _sigInFormKey = GlobalKey<FormState>();
#override
Widget build(BuildContext context) {
Size size = getRelativeSize(context);
Use this-
`final _sigInFormKey = GlobalKey<FormState>();
class SingInScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
Size size = getRelativeSize(context);`
Known issue, tracked here,it's still there. There is no explanation on what's causing it.
I did a little bit of digging. It looks like Android is generating these warnings because we are holding the InputConnection incorrectly in the Engine's TextInputPlugin. I haven't really figured out what we're doing wrong, though.
source
I was also getting the same issue when tapping on the TextField.
Use auto focus = true, and issue will be resolved.
TextField( autofocus: true, );
I "forced" the formatting, in my case for example my form controller was an integer so I did it :
TextFormField(
controller: _formTextControllers.mycontroller,
keyboardType: TextInputType.number,
decoration: InputDecoration(
labelText: 'label',
),
inputFormatters: [
**FilteringTextInputFormatter.digitsOnly,**
MyInputFormatter(),
],
),
Try using the textfield methods like onChanged, onSubmitted,...
Also try using focus nodes.
Use
FocusScope.of(context).requestFocus(FocusNode());
I've been scratching my head over the same issue.
The thing that resolved it for me was removing the media query from within the build method. I guess calling the keyboard messes with this if its running inside the build method and funny things happen.
Size size = MediaQuery
.of(context)
.size;
Try doing without it, or passing it into the widget as a static property above the build method
So okay I have found a solution for this in my use case.
My keyboard used to disappear as soon as it appeared on my screen. This not the bug in the framework, let us understand what happens in the background.
When the keyboard pops up, the screen needs to rebuild its components accordingly, so it forces a rebuild. Therefore the entire widgets are re-rendered including the TextFormField or TextField, which closes the Keyboard.
To solve this problem, we have to find a way to uniquely identify our widget which is rebuilding, and stop flutter from recreating that widget.
This can be done by using keys, first find the parent widget under which our TextFormField/TextField falls, and provide a unique key to it. Remember NOT to initialize the key on the key parameter of the widget. Instead declare it outside the build method and then use it. This makes sure that the unique does not change every time the build method is called.
I hope that this helps.
Thank You!
In my case, I had this problem while my array of TextFormFields were children of DataCells of a DataTable which this DataTable was itself child of a ExpansionPanel widget.
The problem got fixed when I replaced the parent ExpansionPanelList with a Column and removed the ExpansionPanel widget.
I thought that explaining my own case might help somebody with similar issues.
In my case, I was using a global navigation key for navigation. when I was on an inner page the moment I touch Textfeild or TextFormFeild it pop to the navigation initial root, the problem that cause this problem was I declared the Navigation module inside a Stateless widget. When I changed it to stateful the issue is solved.

Card and ListView on single screen

I have a dropdown box and a listview below it on a screen.
On selection of a item in dropdownbox, the list view gets populated with multiple cards. As such the logic is working, but somehow having issues with the scroll configuration.
I have the main parent widget like this:
Widget build(BuildContext context) {
return Column(
children: <Widget>[locationDropdown(), showPackages(plansLoading)],
);
}
The locationDropDown() function returns a card widget with dropdown in it.
showPackages(plansLoading) returns a ListView widget whose code is like
return ListView.builder(
scrollDirection: Axis.vertical,
shrinkWrap: true,
return Card(
color: Colors.transparent,
child: Column(
children: <Widget>[
///...
],
),
);
)
Requirement is to keep the card in the loationDropdown() widget fixed at its place and the cards generated in showPackages's listview be scrolled.
This is the output I am getting, I am unable to scroll.
This functionality is not working. Where am I going wrong?
you can used SafeArea Widget or use Expanded widget before Listview.
ex.
Column (
children: <Widget>[
new TextField(
decoration: new InputDecoration(
labelText: "Search something"
),
Expanded(
ListView(),
),
]
)

Flutter snackbar alternative or easier method than wrapping everything in Scaffold?

I'm working on my first Flutter app (debugging on my Android phone). I have a list with row items. When you long-press the row, it copies the content into the user's clipboard. This is working great!
But I need to let the user know that the content was copied.
I've attempted to follow many tutorials on trying to get the row surrounded by a build method or inside a Scaffold, but I can't get any to work. Is there an alternative method to notifying the user (simply) that something like "Copied!" took place?
Notice the commented out Scaffold.of(... below. It just seems like there must be an easier method to notifying the user other than wrapping everything in a Scaffold. (and when I try, it breaks my layout).
import 'package:flutter/material.dart';
import 'package:my_app/Theme.dart' as MyTheme;
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/services.dart';
class RowRule extends StatelessWidget {
final DocumentSnapshot ruleGroup;
RowRule(this.ruleGroup);
_buildChildren() {
var builder = <Widget>[];
if (!ruleGroup['label'].isEmpty) {
builder.add(new Text(ruleGroup['label'],
style: MyTheme.TextStyles.articleContentLabelTextStyle));
}
if (!ruleGroup['details'].isEmpty) {
builder.add(new Text(ruleGroup['details'],
style: MyTheme.TextStyles.articleContentTextStyle));
}
return builder;
}
#override
Widget build(BuildContext context) {
return new GestureDetector(
onLongPress: () {
Clipboard.setData(new ClipboardData(text: ruleGroup['label'] + " " + ruleGroup['details']));
// Scaffold.of(context).showSnackBar(SnackBar
// (content: Text('text copied')));
},
child: Container(
margin: const EdgeInsets.symmetric(vertical: 3.0),
child: new FlatButton(
color: Colors.white,
padding: EdgeInsets.symmetric(horizontal: 0.0),
child: new Stack(
children: <Widget>[
new Container(
margin: const EdgeInsets.symmetric(
vertical: MyTheme.Dimens.ruleGroupListRowMarginVertical),
child: new Container(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 32.0, vertical: 8.0),
child: new Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: _buildChildren(),
),
)),
)
],
),
),
));
}
}
The goal is to have a page like this (see image), which I have, and it works and scrolls...etc, but I cannot get it to work with a Scaffold, and therefore, haven't been able to use the snackbar. Each "Row" (which this file is for) should show a snackbar on longPress.
You can use GlobalKey to make it work the way you want it.
Since I don't have access to your database stuff, this is how I gave you an idea to do it. Copy and paste this code in your class and make changes accordingly. I also believe there is something wrong in your RowRule class, can you just copy the full code I have given you and run?
void main() => runApp(MaterialApp(home: HomePage()));
class HomePage extends StatelessWidget {
final GlobalKey<ScaffoldState> _key = GlobalKey();
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Color(0xFFFFFFFF).withOpacity(0.9),
key: _key,
body: Column(
children: <Widget>[
Container(
color: Color.fromRGBO(52, 56, 245, 1),
height: 150,
alignment: Alignment.center,
child: Container(width: 56, padding: EdgeInsets.only(top: 12), decoration: BoxDecoration(shape: BoxShape.circle, color: Colors.yellow)),
),
Expanded(
child: ListView.builder(
padding: EdgeInsets.zero,
itemCount: 120,
itemBuilder: (context, index) {
return Container(
color: Colors.white,
margin: const EdgeInsets.all(4),
child: ListTile(
title: Text("Row #$index"),
onLongPress: () => _key.currentState
..removeCurrentSnackBar()
..showSnackBar(SnackBar(content: Text("Copied \"Row #$index\""))),
),
);
},
),
),
],
),
);
}
}
These is a simple plugin replacement for the Snackbar named "Flushbar".
You can get the plugin here - https://pub.dartlang.org/packages/flushbar
You don't have to take care of any wrapping of widgets into scaffold also you get a lot of modifications for you like background gradient, adding forms and so on into Snackbar's and all.
Inside your onLongPressed in GestureDetectore you can do this.
onLongPressed:(){
Clipboard.setData(new ClipboardData(text: ruleGroup['label'] + " " + ruleGroup['details']));
Flushbar(
message: "Copied !!",
duration: Duration(seconds: 3),
)..show(context);
}
This will display the snackbar in you app where you would want to see it also you can get a lot of modification available to you so the you can make it look as per your app.
There are couple of things you need to do, like use onPressed property of the FlatButton it is mandatory to allow clicks, wrap your GestureDetector in a Scaffold. I have further modified the code so that it uses GlobalKey to make things easy for you.
Here is the final code (Your way)
class RowRule extends StatelessWidget {
final GlobalKey<ScaffoldState> globalKey = GlobalKey();
final DocumentSnapshot ruleGroup;
RowRule(this.ruleGroup);
_buildChildren() {
var builder = <Widget>[];
if (!ruleGroup['label'].isEmpty) {
builder.add(new Text(ruleGroup['label'], style: MyTheme.TextStyles.articleContentLabelTextStyle));
}
if (!ruleGroup['details'].isEmpty) {
builder.add(new Text(ruleGroup['details'], style: MyTheme.TextStyles.articleContentTextStyle));
}
return builder;
}
#override
Widget build(BuildContext context) {
return Scaffold(
key: globalKey,
body: GestureDetector(
onLongPress: () {
Clipboard.setData(new ClipboardData(text: ruleGroup['label'] + " " + ruleGroup['details']));
globalKey.currentState
..removeCurrentSnackBar()
..showSnackBar(SnackBar(content: Text('text copied')));
},
child: Container(
margin: const EdgeInsets.symmetric(vertical: 3.0),
child: new FlatButton(
onPressed: () => print("Handle button press here"),
color: Colors.white,
padding: EdgeInsets.symmetric(horizontal: 0.0),
child: new Stack(
children: <Widget>[
new Container(
margin: const EdgeInsets.symmetric(vertical: MyTheme.Dimens.ruleGroupListRowMarginVertical),
child: new Container(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 32.0, vertical: 8.0),
child: new Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: _buildChildren(),
),
),
),
)
],
),
),
),
),
);
}
}
I made a dropdown banner package on pub that allows you to easily notify users of errors or confirmation of success. It's a work in progress as I continue to add visually rich features.
I am not sure if your build() method is completed or you are yet to change it, because it consist of many widgets which are just redundant. Like there is no need to have Container in Container and further Padding along with a FlatButton which would make complete screen clickable. Also having Column won't be a good idea because your screen may overflow if you have more data. Use ListView instead.
So, if you were to take my advice, use this simple code that should provide you what you are really looking for. (See the build() method is of just 5 lines.
class RowRule extends StatelessWidget {
final GlobalKey<ScaffoldState> globalKey = GlobalKey();
final DocumentSnapshot ruleGroup;
RowRule(this.ruleGroup);
_buildChildren() {
var builder = <Widget>[];
if (!ruleGroup['label'].isEmpty) {
builder.add(
ListTile(
title: Text(ruleGroup['label'], style: MyTheme.TextStyles.articleContentLabelTextStyle),
onLongPress: () {
globalKey.currentState
..removeCurrentSnackBar()
..showSnackBar(SnackBar(content: Text("Clicked")));
},
),
);
}
if (!ruleGroup['details'].isEmpty) {
builder.add(
ListTile(
title: Text(ruleGroup['details'], style: MyTheme.TextStyles.articleContentTextStyle),
onLongPress: () {
globalKey.currentState
..removeCurrentSnackBar()
..showSnackBar(SnackBar(content: Text("Clicked")));
},
),
);
}
return builder;
}
#override
Widget build(BuildContext context) {
return Scaffold(
key: globalKey,
body: ListView(children: _buildChildren()),
);
}
}
I read your comments on all answers and here is my conslusion:
You need ScaffoldState object that is just above the widget in tree to show Snackbar. You can either get it through GlobalKey as many have suggested. Fairly simple if the Scaffold is created inside build of the widget, but if it is outside the widget (in your case) then it becomes complicated. You need to pass that key, wherever you need it through Constructor arguments of child widgets.
Scaffold.of(context) is a very neat way to just do that. Just like an InheritedWidget, Scaffold.of(BuildContext context) gives you access of the closest ScaffoldState object above the tree. Else it could be a nightmare to get that instance (by passing it through as constructor arguments) if your tree was very deep.
Sorry, to disappoint but I don't think there is any better or cleaner method than this, if you want to get the ScaffoldState that is not built inside build of that widget. You can call it in any widget that has Scaffold as a parent.

Flutter Overlay and TextFields

I am using Flutter. In the scaffold body I use
Overlay.of(context).insert(...)
to insert a login dialog.
However, when I try to select the username/password fields, no keyboard shows up.
When I use the login widget in the 'normal' tree, it works. Moving it in the overlay makes it so the keyboard does not show.
Am I missing something here? Shouldn't this just work?
You need wrap your widgets in a FocusScope like the following:
overlayEntry = OverlayEntry(builder: (context) {
FocusScope.of(context).setFirstFocus(focusScopeNode);
return Material(
child: FocusScope(
node: focusScopeNode,
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
children: <Widget>[
TextField(),
],
),
),
),
);
});
Overlay.of(context).insert(overlayEntry);
In fact, for a login page, I would just use the Navigator to push it in.