Flutter TextFormField reloads current screen when focused - dart

I have a TextFormField that reloads the current screen when I tap on it to enter text. When I tap on the formfield the software keyboard is displayed briefly before the entire screen reloads and renders all the widgets again. I am running the app on an Android device.
Container(
child: Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
TextFormField(
validator: (value) {
if (value.isEmpty) {
return 'Your input cannot be empty';
}
},
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: RaisedButton(
onPressed: () {
if (_formKey.currentState.validate()) {
print('validated');
}
},
child: Text('Save'),
),
),
],
),
),
margin: EdgeInsets.only(top:8.0),
),

The problem is that the controller of the TextFormField is rebuild when you click on the field, and that's the reason of your issue.
So to solve that, did you try to create a Statefull widget and then creating a TextEditingController in the State of this widget and passing it as an argument to the TextFormField ?

I had the same Problem. this was my code
class MainPage extends StatefulWidget {
#override
_MainPageState createState() => _MainPageState();
}
class _MainPageState extends State<MainPage> {
Model model = Model();
#override
Widget build(BuildContext context) {
GlobalKey<FormState> _formKey = GlobalKey<FormState>();
var mediaWidth = MediaQuery.of(context).size.width / 2.0;
return Scaffold(
...
and I solved this problem by declaring the _formKey outside of build method. and this worked for me.
class MainPage extends StatefulWidget {
#override
_MainPageState createState() => _MainPageState();
}
class _MainPageState extends State<MainPage> {
Model model = Model();
GlobalKey<FormState> _formKey = GlobalKey<FormState>();
#override
Widget build(BuildContext context) {
var mediaWidth = MediaQuery.of(context).size.width / 2.0;
return Scaffold(
...
hope it will help you

Yes, that happens because when the keyboard appears, the flutter scaffold gets resize to the current available screen size. So, we can easily handle this by preventing the scaffold size change. I suggest to set scaffold resizeToAvoidBottomInset property false. If it's true the body and the scaffolds floating widgets should size themselves to avoid the onscreen keyboard whose height is defined by the ambient MediaQuery's, MediaQueryData,viewInsets bottom property.
Solution:
resizeToAvoidBottomInset: false,
Complete example:
#override
Widget build(BuildContext context) {
setDisplayData();
return Scaffold(
resizeToAvoidBottomInset: false,
appBar: getAppBar(),
body: OrientationBuilder(
builder: (context, orientation) {
return orientation == Orientation.portrait
? _buildVerticalLayout()
: _buildHorizontalLayout();
},
),
);

Check if you are using MediaQueries wrongly in your project, I had similar issue and it stopped when I changed the MediaQuery
in my case:
Size _size = MediaQuery.of(context).size;
removing this piece of code fixed my app.

When TextFormField focused the size of screen will changed because of the appearance of keyboard, that cause rebuild of state, you cant prevent re-build of state.
Instead of trying prevent re-build state, you need to solve problems which happen when state do re-build, one of common problem is declaration and initialization variables inside build(BuildContext context){ ... }' function.
The main problem, when you need to get some data related of context (like size of screen), in this case I prefer to pass this value from parent Widget...
For example this code will cause problem when re-build state:
#override
Widget build(BuildContext context) {
double? _screenHeight = MediaQuery.of(context).size.height;
return Container();
}
To solve problem get _screenHeight from parent, to know how to do that look at https://stackoverflow.com/a/50289032/2877427

Related

How to prevent Flutter app from scrolling to top after calling setState?

I have an ExpansionPanelList inside a SingleChildScrollView. Whenever I (un)fold a panel and therefore call setState, the SingleChildScrollView scrolls back to the top. How can I prevent this?
#override
Widget build(BuildContext context) {
final _scaffoldKey = new GlobalKey<ScaffoldState>();
return new Scaffold(
key: _scaffoldKey,
appBar: new AppBar(
title: new Text(widget.title),
),
body: new SingleChildScrollView(
child: new ExpansionPanelList(
children: <ExpansionPanel>[
// panels
],
expansionCallback: (int index, bool isExpanded) {
setState(() {
// toggle expanded
});
},
), // ExpansionPanelList
), // SingleChildScrollView
); // Scaffold
}
This answer suggests using a custom ScrollController with keepScrollOffset set to true, however this is the default value and setting it explicitly to true therefore does not change anything.
That's because you are using a new Key every time you rebuild the widget (setState).
To fix your issue just move the code below outside the build method
final _scaffoldKey = new GlobalKey<ScaffoldState>();
Like this :
final _scaffoldKey = new GlobalKey<ScaffoldState>();
#override
Widget build(BuildContext context) {
return new Scaffold(
key: _scaffoldKey,
appBar: new AppBar(
title: new Text(widget.title),
),
I was having the same problem, and somehow the answer from #diegoveloper did not do the trick for me.
What i ended up doing was separating the SingleChildScrollView in an independent StatefulWidget: That also fixed the scroll animation.
My code then ended up being something like
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: MyExpansionList(),
...
class MyExpansionListextends StatefulWidget {
#override
_MyExpansionListState createState() => _MyExpansionListState();
}
class _MyExpansionListState extends State<MyExpansionList> {
#override
Widget build(BuildContext context) {
return Scrollbar(
isAlwaysShown: true,
showTrackOnHover: true,
child: SingleChildScrollView(
child: Column(
children: [
ExpansionPanelList(animationDuration: Duration(seconds: 1),
In this way the setState() did update only the ExpansionPanelList/ScrollView and not the whole Scaffold.
I hope this also helps others facing same problem...
Nothing helped me until I realised that in the build() function I was calling jumpTo() of the controller that was attached to my list. Like this:
#override
Widget build(BuildContext context) {
if (widget.controller.hasClients) {
widget.controller.jumpTo(0);
}
...
}
I removed these lines and the problem was gone. Happy coding :)
You have to pass a key to the SingleChildScrollView. Otherwise, its state is renewed every setState call.
final _scrollKey = GlobalKey();
SingleChildScrollView(
key: _scrollKey,
child:

Flutter - Not being able to change tab using a FAB instead of TabBar Icons

I am trying to make a small notes app and I had the idea that instead of using a different page for the creation section of the app to use a different tab that is only accessible by an "add" fab in the main screen. Also I want it such that after you press the button it turns it into a "back" button which takes you back to the original page with the notes list.
class HomePage extends StatefulWidget {
const HomePage({Key key}) : super(key: key);
static final _myTabbedPageKey = new GlobalKey<HomePageState>();
#override
HomePageState createState() => HomePageState();
}
class HomePageState extends State<HomePage> with SingleTickerProviderStateMixin {
TabController tController;
#override
void initState(){
super.initState();
tController = new TabController(vsync: this, length: 2,);
}
#override
void dispose(){
tController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("NoteMe"),
),
body: Container(
child: TabBarView(
physics: NeverScrollableScrollPhysics(),
controller: tController,
children: <Widget>[
new ListPage(),
new CreationPage(),
],
)
),
floatingActionButton: actionButton(tController),
floatingActionButtonLocation: FloatingActionButtonLocation.endDocked,
bottomNavigationBar: BottomAppBar(
shape: CircularNotchedRectangle(),
child: Row(
children: <Widget>[
new Container(
child: IconButton(
icon: Icon(Icons.search),
onPressed: () {},
)),
],
),
),
);
}
}
//Implementing the ADD/RETURN Button as func
FloatingActionButton actionButton(TabController tC){
bool isListPage = true;
goToCreation(){
if(isListPage == true){
tC.animateTo(tC.index+1);
isListPage = false;
}
else{
tC.animateTo(tC.index - 1);
isListPage = true;
}
}
FloatingActionButton theButton = FloatingActionButton(
backgroundColor: kColorPink,
elevation: 2.0,
child: isListPage == true ? Icon(Icons.add) : Icon(Icons.arrow_back),
onPressed: goToCreation(),
);
return theButton;
}
As you can see the fab that is displayed is returned by the function above that also takes the tabcontroller as a parameter. I get no error message while running this. It simply does not work. I have tried not passing the tabController but instead accessing it through something like
HomePage._myTabbedPageKey.tController.animateTo(...)
that I have found in another post but that's when I get an error message stating something like calling tController on null.
Sorry if I didn't format this well enough. This is my first post here
It may be that the reference to tc or event goToCreation is null.
Can you put a breakpoint in the method and check if the method is called and if the tc object is not null.

Flutter showDialog, AlertDialog background gradient.

For color, I can use dialogBackgroundColor property to give AlertDialog background my own color.
I was looking to use Gradient as my background. How can I use that? DecoratedBox is something that will be needed, but I don't know what to wrap in what. Can anyone give me idea or link for the same?
You can add a Container inside which will be decorated with gradient. For example:
class GradientDialog extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return new _GradientDialogState();
}
}
class _GradientDialogState extends State<GradientDialog> {
#override
Widget build(BuildContext context) {
return AlertDialog(
content: Container(
padding: const EdgeInsets.all(8.0),
decoration: new BoxDecoration(
gradient: new LinearGradient(
colors: AppColors.BG_GRADIENT,
begin: Alignment.topCenter,
end: Alignment.bottomCenter)),
child: YourContentInside(),
),
contentPadding: EdgeInsets.all(0.0),
);
}
}
Open it with
showDialog(
context: context,
barrierDismissible: true,
builder: (BuildContext context) {
return GradientDialog();
});
In build method of AlertDialog there is return Dialog(child: dialogChild, shape: shape);. In Dialog.build() - it returns Material(color: _getColor(context), .... There is no way to set gradient background for AlertDialog without customization.
I can add example if it'll be needed.
P.S. Or you can call showDialog and send another widget instead of AlertDialog.

Using setState with StatelessWidget

Is there a way to use setState with StatelessWidget?
I know that I could be used with StatefulWidget and using a State, but I don't know if there's a way to use it with StatelessWidget.
I think that's a direct question and it doesn't need code to be shown.
If you could help me, I will appreciate it.
here is an example of code that makes it possible for a StatelessWidget to update itself, its from an article of Didier Boelens.
https://www.didierboelens.com/2019/09/flutter-internals/
The following useless code makes possible for a StatelessWidget to
update itself (as if it was a StatefulWidget but without using any
setState()), by using the BuildContext …
void main(){
runApp(MaterialApp(home: TestPage(),));
}
class TestPage extends StatelessWidget {
// final because a Widget is immutable (remember?)
final bag = {"first": true};
#override
Widget build(BuildContext context){
return Scaffold(
appBar: AppBar(title: Text('Stateless ??')),
body: Container(
child: Center(
child: GestureDetector(
child: Container(
width: 50.0,`enter code here`
height: 50.0,
color: bag["first"] ? Colors.red : Colors.blue,
),
onTap: (){
bag["first"] = !bag["first"];
//
// This is the trick
//
(context as Element).markNeedsBuild();
}
),
),
),
);
}
}
Between us, when you are invoking the setState() method, the latter
ends up doing the very same thing: _element.markNeedsBuild().
No. That's the whole point of StatelessWidget: It doesn't have a state.
Only StatefulWidget has a state, and therefore only it has a setState.

Overflowing parent widgets

I'm trying to create a widget that has a button and whenever that button is pressed, a list opens up underneath it filling in all of the space under the button. I implemented it with a simple Column, something like this:
class _MyCoolWidgetState extends State<MyCoolWidget> {
...
#override
Widget build(BuildContext context) {
return new Column(
children: <Widget>[
new MyButton(...),
isPressed ? new Expanded(
child: new SizedBox(
width: MediaQuery.of(context).size.width,
child: new MyList()
)
) : new Container()
]
)
}
}
This works totally fine in a lot of cases, but not all.
The problem I'm having with creating this widget is that if a MyCoolWidget is placed inside a Row for example with other widgets, lets say other MyCoolWidgets, the list is constrained by the width that the Row implies on it.
I tried fixing this with an OverflowBox, but with no luck unfortunately.
This widget is different from tabs in the sense that they can be placed anywhere in the widget tree and when the button is pressed, the list will fill up all the space under the button even if this means neglecting constraints.
The following image is a representation of what I'm trying to achieve in which "BUTTON1" and "BUTTON2" or both MyCoolWidgets in a Row:
Edit: Snippet of the actual code
class _MyCoolWidgetState extends State<MyCoolWidget> {
bool isTapped = false;
#override
Widget build(BuildContext context) {
return new Column(
children: <Widget>[
new SizedBox(
height: 20.0,
width: 55.0,
child: new Material(
color: Colors.red,
child: new InkWell(
onTap: () => setState(() => isTapped = !isTapped),
child: new Text("Surprise"),
),
),
),
bottomList()
],
);
}
Widget comboList() {
if (isTapped) {
return new Expanded(
child: new OverflowBox(
child: new Container(
color: Colors.orange,
width: MediaQuery.of(context).size.width,
child: new ListView( // Random list
children: <Widget>[
new Text("ok"),
new Text("ok"),
new Text("ok"),
new Text("ok"),
new Text("ok"),
new Text("ok"),
new Text("ok"),
new Text("ok"),
new Text("ok"),
new Text("ok"),
new Text("ok"),
new Text("ok"),
new Text("ok"),
],
)
)
),
);
} else {
return new Container();
}
}
}
I'm using it as follows:
class Home extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new Row(
children: <Widget>[
new Expanded(child: new MyCoolWidget()),
new Expanded(child: new MyCoolWidget()),
]
)
}
}
Here is a screenshot of what the code is actually doing:
From the comments, it was clarified that what the OP wants is this:
Making a popup that covers everything and goes from wherever the button is on the screen to the bottom of the screen, while also filling it horizontally, regardless of where the button is on the screen. It would also toggle open/closed when the button is pressed.
There are a few options for how this could be done; the most basic would be to use a Dialog & showDialog, except that it has some issues around SafeArea that make that difficult. Also, the OP is asking for the button to toggle rather than pressing anywhere not the dialog (which is what dialog does - either that or blocks touches behind the dialog).
This is a working example of how to do something like this. Full disclaimer - I'm not stating that this is a good thing to do, or even a good way to do it... but it is a way to do it.
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
// We're extending PopupRoute as it (and ModalRoute) do a lot of things
// that we don't want to have to re-create. Unfortunately ModalRoute also
// adds a modal barrier which we don't want, so we have to do a slightly messy
// workaround for that. And this has a few properties we don't really care about.
class NoBarrierPopupRoute<T> extends PopupRoute<T> {
NoBarrierPopupRoute({#required this.builder});
final WidgetBuilder builder;
#override
Color barrierColor;
#override
bool barrierDismissible = true;
#override
String barrierLabel;
#override
Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
return new Builder(builder: builder);
}
#override
Duration get transitionDuration => const Duration(milliseconds: 100);
#override
Iterable<OverlayEntry> createOverlayEntries() sync* {
// modalRoute creates two overlays - the modal barrier, then the
// actual one we want that displays our page. We simply don't
// return the modal barrier.
// Note that if you want a tap anywhere that isn't the dialog (list)
// to close it, then you could delete this override.
yield super.createOverlayEntries().last;
}
#override
Widget buildTransitions(
BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) {
// if you don't want a transition, remove this and set transitionDuration to 0.
return new FadeTransition(opacity: new CurvedAnimation(parent: animation, curve: Curves.easeOut), child: child);
}
}
class PopupButton extends StatefulWidget {
final String text;
final WidgetBuilder popupBuilder;
PopupButton({#required this.text, #required this.popupBuilder});
#override
State<StatefulWidget> createState() => PopupButtonState();
}
class PopupButtonState extends State<PopupButton> {
bool _active = false;
#override
Widget build(BuildContext context) {
return new FlatButton(
onPressed: () {
if (_active) {
Navigator.of(context).pop();
} else {
RenderBox renderbox = context.findRenderObject();
Offset globalCoord = renderbox.localToGlobal(new Offset(0.0, context.size.height));
setState(() => _active = true);
Navigator
.of(context, rootNavigator: true)
.push(
new NoBarrierPopupRoute(
builder: (context) => new Padding(
padding: new EdgeInsets.only(top: globalCoord.dy),
child: new Builder(builder: widget.popupBuilder),
),
),
)
.then((val) => setState(() => _active = false));
}
},
child: new Text(widget.text),
);
}
}
class MyApp extends StatefulWidget {
#override
State<StatefulWidget> createState() => MyAppState();
}
class MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return new MaterialApp(
home: new SafeArea(
child: new Container(
color: Colors.white,
child: new Column(children: [
new PopupButton(
text: "one",
popupBuilder: (context) => new Container(
color: Colors.blue,
),
),
new PopupButton(
text: "two",
popupBuilder: (context) => new Container(color: Colors.red),
)
]),
),
),
);
}
}
For even more outlandish suggestions, you can take the finding the location part of this and look at this answer which describes how to create a child that isn't constrained by it's parent's position.
However you end up doing this, it's probably best that the list not to be a direct child of the button as a lot of things in flutter depend on a child's sizing and making it be able to expand to the full screen size could quite easily cause problems.

Resources