I am having an issue where the whole screen widget reloads when there is a textfield is used.
This doesn't happen when the the app is loaded with this screen as landing page.
But when the routing happens from another page to this page and when textfield is clicked then the rebuild happens.
I even tried a simple app and this is getting reproduced. Tried many ways but could not get to a solution.
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
class Screen1 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text("Screen 1"), // screen title
),
body: new Center(
child: new Column(
children: <Widget>[
new RaisedButton(
onPressed: () {
button1(context);
},
child: new Text("Go to Screen 2"),
)
],
),
),
);
}
}
class Screen2 extends StatelessWidget {
#override
Widget build(BuildContext context) {
print("Widget rebuilds");
return new Scaffold(
appBar: new AppBar(
title: new Text("Screen 2"),
),
body: new Center(
child: new Column(
children: <Widget>[
new Container(
height: 350.0,
child: TextFormField(
keyboardType: TextInputType.text,
style: TextStyle(fontSize: 16.0, color: Colors.black),
)),
],
),
),
);
}
}
void main() {
runApp(new MaterialApp(
home: new Screen1(),
routes: <String, WidgetBuilder>{
'/screen2': (BuildContext context) => new Screen2()
},
));
}
void button1(BuildContext context) {
print("Button 1");
Navigator.of(context).pushNamed('/screen2');
}
Here the app is loaded with screen 1 and clicking on button Go to screen2 will load the screen 2 with the text field. Clicking on this field will bring the keyboard and clicking the done on keyboard and again focusing on the text field will rebuild the screen. This keeps happening when keyboard appears and when disappears.
But then if Screen2 is set as landing page, then clicking on the text field and doing the same process mentioned above will not reload the widget. The widget build happens only once. Seems the issue is when the Screen2 is navigated from Screen 1
runApp(new MaterialApp(
home: new Screen2(),
routes: <String, WidgetBuilder>{
'/screen2': (BuildContext context) => new Screen2()
},
));
This is the normal behavior, there is no problem with it. It is in fact within the specs of build method :
It can be called an arbitrary number of time and you should expect it to be so.
If this causes a problem to do so, it is very likely that your build function is not pure. Meaning that it contains side effects such as http calls or similar.
These should not be done within the build method. More details here : How to deal with unwanted widget build?
About the "What triggers the build", there are a few common situations:
Route pop/push, for in/out animations
Screen resize, usually due to keyboard appearance or orientation change
Parent widget recreated its child
An InheritedWidget the widget depends on (Class.of(context) pattern) change
if you are using MediaQuery, the popup of the keyboard triggers MediaQuery. all widgets that depends on the MediaQuery will be rebuild. there are packages that can replace MediaQuery and deal with dimensions without causing unnecessary rebuilds such as the sizer package.
Related
This is my SplashScreen code
I'm facing 2 issues
In SplashScreen is opening late after login
Some dDevice white/ black screen is shown
How can i fix this issue?
enimport 'package:cms/Configs/app_constants.dart';
import 'package:flutter/material.dart';
class Splashscreen extends StatefulWidget
{
#override
_Splashscreen createState() => _Splashscreen();
}
class _Splashscreen extends State<Splashscreen> {
#override
Widget build(BuildContext context) {
return IsSplashOnlySolidColor == true
? Scaffold(
backgroundColor: Colors.blue,
body: Center(
child: CircularProgressIndicator(
valueColor:
AlwaysStoppedAnimation<Color>(Colors.blue)),
))
: Scaffold(
backgroundColor: Colors.blue,
body: Center(
child: Image.asset(
'assets/images/logo.png',
fit: BoxFit.cover,
)),
);
}
}
You are making a screen display once your app opens, but a splash screen is something you show to the user when the app is being loaded by the OS.
You are seeing a white/black screen because there is no splash screen to show while loading the app. The code you provided runs once the black/white screen is over i.e. the app has loaded.
Use this https://pub.dev/packages/flutter_native_splash to create a native splash screen which replaces the white/black screen you are seeing.
I'm using a StreamProvider to capture changes that occur in Firestore and display them on the user homepage. When the user initially signs in or registers, they're directed to this homepage and expect to see their details, for which I use the StreamProvider.
Keeping in mind that the first value fetched will be null, I've added a Loading Text Widget. However, the widget does not seem to change unless I hot-refresh the page and the Stateful Widget gets rebuilt again.
What seems to be happening is that the Stateful Widget I'm using as my HomePage gets "built" only once due to which even when the stream gets populated with non-null values, the loading widget does not disappear because its parent widget doesn't get rebuilt.
This is the basic HomeScreen widget around which I'm wrapping my StreamProvider.
#Override
Widget build(BuildContext context) {
return StreamProvider<UserDataModel>.value(
initialData: null,
value: DatabaseServices(userDetails: _userDetails).userDataDocument,
updateShouldNotify: (previous, current) => true,
child: Scaffold(
backgroundColor: Colors.white,
body: HomeBody(),
)
);
}
This is the relevant snippet from the HomeBody widget
class HomeBody extends StatefulWidget {
#override
_HomeBodyState createState() => _HomeBodyState();
}
class _HomeBodyState extends State<HomeBody> {
#override
Widget build(BuildContext context) {
final AuthenticationServices _authServices = AuthenticationServices();
final UserDataModel _data = Provider.of<UserDataModel>(context);
print(_data);
print((_data != null) ? _data.name : 'NULL');
/* Some code */
Row(
children: [
Padding(
padding: const EdgeInsets.fromLTRB(25, 0, 0, 5),
child: Text(_data != null ? _data.name : 'LOADING...', style: dashboardName),
),
],
),
Row(
children: [
Padding(
padding: const EdgeInsets.fromLTRB(25, 2, 0, 5),
child: Text(_data != null ? _data.scholarId : 'LOADING...', style: dashboardName),
),
],
),
I also noticed that the print statements I've used for debugging get executed only once printing those initial null values, until, I hot-refresh the page and the non-null values are shown.
Is there a way to go around this? I've been stuck for a couple of days now without making any significant advances and any sort of guidance would be appreciated.
I'm new in Flutter.
I have a question
How to call layouts in flutter ?
I've been create some layouts that contains a lot of widget.
It's not right if I make every code inside 1 file.
so I decide to put the code for the widgets in every 1 layouts file.
and I dont know how to call them in the home-page.dart that I create.
I mean, if I push THIS (i.e page1.dart), then the page1.dart is appear.
thought that file (page1.dart) is in other directory (not inside lib dir).
I dont know. am I should use ROUTES ?
but I dont know how.
would you like to teach me ?
..............
here are. I have TabBar like this in my home_page.dart:
import 'package:flutter/material.dart';
import 'package:coba/second.dart';
class HomePage extends StatelessWidget {
static String tag = 'home-page';
#override
Widget build(BuildContext ctxt) {
return new MaterialApp(
title: "MySampleApplication",
home: new DefaultTabController(
length: 3,
child: new Scaffold(
appBar: new AppBar(
title: new Text("Hello Flutter App"),
bottom: new TabBar(
tabs: <Widget>[
new Tab(text: "First Tab"),
new Tab(text: "Second Tab"),
new Tab(text: "Third Tab"),
],
),
),
body: new TabBarView(
children: <Widget>[
new Text("You've Selected First"),
new SecondWidget(),
new ThirdWidget(),
]
)
),
)
);
}
}
class SecondWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) =>
second(data: 'Hello there from the first page!'),
),
}
}
class ThirdWidget extends StatelessWidget {
#override
Widget build(BuildContext ctxt) {
return new Column(
children: <Widget>[
Text('halooo'),
Container(
color: Colors.black,
width: 200,
height: 200,
)
],
);
}
}
thank you so much
You can use any name that you want (generally, we have seen xxxScreen.dart or xxxPage.dart, but it is totally up to you).
Import your "destiny" page using in "origin" page using import:
import 'package:myproject/myPageScreen.dart';
Flutter offers 3 options:
Using Navigator:
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) =>
SecondPage(data: 'Hello there from the first page!'),
),
Using Named routes:
Declare you routes in MaterialApp:
MaterialApp(
// Start the app with the "/" named route. In our case, the app will start
// on the FirstScreen Widget
initialRoute: '/',
routes: {
// When we navigate to the "/" route, build the FirstScreen Widget
'/': (context) => FirstScreen(),
// When we navigate to the "/second" route, build the SecondScreen Widget
'/second': (context) => SecondScreen(),
},
);
And then use named route with Navigator:
onPressed: () {
Navigator.pushNamed(context, '/second');
}
Using onGenerateRoute:
Declare this property on your MaterialApp:
return MaterialApp(
// Initially display FirstPage
initialRoute: '/',
onGenerateRoute: _getRoute,
);
And create your route generator :
final args = settings.arguments;
switch (settings.name) {
case '/':
return MaterialPageRoute(builder: (_) =>
FirstPage());
case '/second':
// Validation of correct data type
if (args is String) {
return MaterialPageRoute(
builder: (_) => SecondPage(
data: args,
),
);
}
You can create your router as another file to help to organize your project.
Given 2 routes, e.g. parent and a child and a Hero(..) widget with the same tag.
When the user is on the "parent" screen and opens a "child" - the Hero widget is animated. When it goes back (via Navigator.pop) it's also animated.
I'm looking for a way to disable that animation when going back (from child to parent via Navigator.pop).
Is there a kind of handler which will be called on a widget before it's going to be "animated away" ? Then I probably could change Hero tag and problem solved.
Or, when creating a "builder" for a route in parent widget, I could probably remember a reference to a target widget and before calling Navigator.pop notify it about "you are gonna be animated out". That would also require making that widget stateful (I haven't found a way to force rebuild a stateless widget).
Is there an easier way of implementing this?
While there currently isn’t a built-in way to disable Hero animations in any particular direction, though CLucera’s use of FadeTransition with HeroFlightDirection is one creative way, the most direct approach is to break the tag association between the two Hero’s:
When you go from the 2nd Hero back to the 1st Hero, just temporarily change the 1st Hero’s tag to something else, then the Hero won’t animate back. A simplified example:
class _MyHomePageState extends State<MyHomePage> {
String tag1, tag2;
String sharedTag = 'test';
String breakTag = 'notTest';
#override
void initState() {
super.initState();
tag1 = sharedTag;
tag2 = sharedTag;
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Hero(
tag: tag1,
child: RaisedButton(
child: Text("hi"),
onPressed: () {
// restore the tag
if (tag1 != sharedTag) {
setState(() {
tag1 = sharedTag;
});
}
// second route
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Container(
alignment: Alignment.topLeft,
child: Hero(
tag: tag2,
child: RaisedButton(
child: Text('hello'),
onPressed: () {
// change the tag to disable the reverse anim
setState(() {
tag1 = breakTag;
});
Navigator.of(context).pop();
},
),
)
),
);
}
)
);
},
)
),
),
);
}
}
But if you want to directly modify the animation, then playing around inside the flightShuttleBuilder is the way to do it like CLucera did. You can also check out medium/mastering-hero-animations-in-flutter to further explore that area.
The only approach that I can come up at the moment is to "Animate" the popping Hero in a way that seems not animated, let's check this code:
class SecondRoute extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Hero(
flightShuttleBuilder: (context, anim, direction, fromContext, toContext) {
final Hero toHero = toContext.widget;
if (direction == HeroFlightDirection.pop) {
return FadeTransition(
opacity: AlwaysStoppedAnimation(0),
child: toHero.child,
);
} else {
return toHero.child;
}
},
child: FlatButton(
child: Text("prev 1"),
onPressed: () {
Navigator.of(context).pop();
},
),
tag: "test",
));
}
}
in your SecondRoute (the one that should pop) you have to supply a flightShuttleBuilder parameter to your Hero then you can check the direction and if it is popping, just hide the Widget with an AlwaysStoppedAnimation fade transition
the result is something like this:
I hope that this is something like the expected result, of course, you can completely change the transition inside the flightShuttleBuilder to change the effect! it's up to you :)
I have a ListTile (in the drawer), which when clicked, goes to a new screen and in that screen the user can choose from a number of (using a for loop) IconButtons (which are in a ListView), but the function which is to be executed when one taps the button is actually executed when the user is sent to the choosing screen (when he/she clicked the ListTile) and as a result the function (which by the way opens another screen) gets executed the same amount of times as there are IconButtons in my ListView and before they are clicked.
For more details see the sourcecode:
When the ListTile is clicked:
setState(() {
goDownloads(files);
});
Function for opening to the new screen:
goDownloads(List<File> files) async {
for (int n = 0; n < files.length; n++) {
String url = files[n].path;
Widget container = new Container(
child: new Row(
children: <Widget>[
new IconButton(
onPressed: openBook(url),//This is the code that gets executed before it's supposed to
icon: new Icon(Icons.open_in_browser),
),
...
]
)
);
dlbooks.add(container);
}
setState(() {
Navigator.of(context).push(new MaterialPageRoute<Null>(
builder: (BuildContext context) {
return new Scaffold(
appBar: new AppBar(title: new Text('My Page')),
body: new Center(
child: new ListView(
children: dlbooks
),
),
);
},
));
});
}
I also have the full code in a main.dart file, though I don't recommend it since it's not well written, I'm still a noob. https://pastebin.com/vZapvCKx
Thanks in advance!
This code passes a function to onPressed that is executed when the button is pressed
onPressed: () => openBook(url)
Your code
onPressed: openBook(url)
executes openBook(url) and passed the result to onPressed to be executed on button press, which doesn't seem to be what you want.