I have a screen which I pass data back to like so:
final myUpdatedObject = await Navigator.of(context).push(...);
setState({
object = myUpdatedObject;
});
Having checked with a simple print at all places in my widget body that my object is used, the new data is present after it is passed back by the Navigator and setState is called.
However, when the widget is rebuilt, even though the new data is apparently there, it is not reflected in the UI changes, it shows old data.
Is this some sort of caching in debug mode? Whats causing this issue?
The example below starts with a Map named textMessageMap with a message key that populates a Text Widget with 'Home'. Tap the FloatingActionButton and you'll navigate to SecondScreen. If you tap the 'Go back!' button in SecondScreen, the message key in textMessageMap will be updated to read 'Second Screen'. If you tap the back button on the Scaffold of SecondScreen, textMessageMap will be nulled out. Calling setState updates the UI appropriately. See if your implementation is different.
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
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> {
Map<String, String> textMessageMap = {'message': 'Home'};
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
body: new Center(
child: new Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new Text(
'${textMessageMap != null ? textMessageMap['message'] : 'map is null'}',
style: Theme.of(context).textTheme.display1,
),
],
),
),
floatingActionButton: new FloatingActionButton(
onPressed: () {
_launchSecondScreen();
},
child: new Icon(Icons.add),
),
);
}
_launchSecondScreen() async {
final value = await Navigator.push(
context,
MaterialPageRoute<Map<String, String>>(
builder: (BuildContext _) => SecondScreen()));
setState(() {
textMessageMap = value;
});
}
}
class SecondScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Second Screen"),
),
body: Center(
child: RaisedButton(
onPressed: () {
// Navigate back to the first screen by popping the current route
// off the stack. The text 'Second Screen' will replace 'Home'.
// If you hit the scaffold's back button, the return value will be
// null instead.
final map = {'message': 'Second Screen'};
Navigator.pop(context, map);
},
child: Text('Go back!'),
),
),
);
}
}
Related
I want user to be able to jump between controls with 'Tab' in my flutter web app.
I followed this post to catch the key "Tab" and to navigate to next controls.
When user presses 'Tab', cursor jumps to the next text box, but then, when user types, no letters appears in the text box.
What can be wrong?
Here is the code:
class _LoginScreenState extends State<LoginScreen> {
FocusNode _passwordFocus;
FocusNode _emailFocus;
#override
void initState() {
super.initState();
_emailFocus = FocusNode();
_passwordFocus = FocusNode();
}
#override
void dispose() {
_emailFocus.dispose();
_passwordFocus.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
final TextEditingController emailController =
new TextEditingController(text: this._email);
final TextEditingController passwordController =
new TextEditingController();
return Scaffold(
appBar: AppBar(
title: Text('Sign In'),
),
body: Column(
children: <Widget>[
RawKeyboardListener(
child: TextField(
autofocus: true,
controller: emailController,
decoration: InputDecoration(
labelText: "EMail",
),
),
onKey: (dynamic key) {
if (key.data.keyCode == 9) {
FocusScope.of(context).requestFocus(_passwordFocus);
}
},
focusNode: _emailFocus,
),
TextField(
controller: passwordController,
obscureText: true,
focusNode: _passwordFocus,
decoration: InputDecoration(
labelText: "Password",
),
),
],
),
);
}
}
It turned out the browser was taking the focus to other place.
I added prevention of default browser behavior to the method 'build':
import 'dart:html';
...
#override
Widget build(BuildContext context) {
document.addEventListener('keydown', (dynamic event) {
if (event.code == 'Tab') {
event.preventDefault();
}
});
...
The solution that worked for me is a little different.
I am on Flutter 2.0.1 with Dart 2.12.0
import 'dart:html';
import 'package:flutter/foundation.dart';
...
#override
Widget build(BuildContext context) {
if (kIsWeb) {
document.addEventListener('keydown',
(event) => {if (event.type == 'tab') event.preventDefault()});
}
...
}
...
I have a main dart class in which the app bar is located and the app bar contains a refresh button. I'm using a navigation drawer to populate two other views f1 and f2.
From my main.dart how can I pass the refresh button clicks to the sub fragment kind of f1.dart so that I can refresh my contents on f1.dart
// State of Main
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
drawer: Drawer(
child: new Column(
children: <Widget>[
////////////////////////////////////////////////////////////
new FirstFragment(),
new SecondFragment()
/////////////////////////////////////////////////////////////
],
),
),
appBar: AppBar(
title: Text(widget.title),
actions: <Widget>[
IconButton(
icon: Icon(Icons.refresh),
onPressed: () {
print("refresh pressed");
/////////////////////////
How to send this refresh pressed event to my FirstFragment class??
/////////////////////////
},
color: Colors.white,
)
],
),
body: _getDrawerItemWidget(_selectedDrawerIndex),
);
}
}
In Android, I've been using event listeners and for iOS, I can use delegates for the purpose. How can I achieve this on flutter/dart. ?
You can pass a callback, use the VoidCallback and receive the event on your Main widget.
class MainPage extends StatelessWidget {
_onTapButton() {
print("your event here");
}
#override
Widget build(BuildContext context) {
return Container(
child: ChildPage(
onTap: _onTapButton,
),
);
}
}
class ChildPage extends StatelessWidget {
final VoidCallback onTap;
const ChildPage({Key key, this.onTap}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
child: RaisedButton(
child: Text("Click Me"),
onPressed: () {
//call to your callback here
onTap();
},
),
);
}
}
In case you want the opposite, you can just refresh the state of your parent widget and change the parameter that you pass to your fragments or also you can use GlobalKey, like the example below:
class MainPage extends StatelessWidget {
final GlobalKey<ChildPageState> _key = GlobalKey();
_onTapButton() {
_key.currentState.myMethod();
}
#override
Widget build(BuildContext context) {
return Container(
child: Column(
children: [
ChildPage(
key: _key,
),
RaisedButton(
child: Text("Click me"),
onPressed: _onTapButton,
)
],
)
);
}
}
class ChildPage extends StatefulWidget {
const ChildPage({Key key}) : super(key: key);
#override
ChildPageState createState() {
return new ChildPageState();
}
}
class ChildPageState extends State<ChildPage> {
myMethod(){
print("called from parent");
}
#override
Widget build(BuildContext context) {
return Container(
child: Text("Click Me"),
);
}
}
Is there any way to change the default animation when navigating to/from a page in Flutter?
You can use PageRouteBuilder.
The following example shows FadeTransition when you navigate to second screen.
Navigator.push(
context,
PageRouteBuilder(
pageBuilder: (_, __, ___) => Page2(),
transitionDuration: Duration(seconds: 2),
transitionsBuilder: (_, a, __, c) => FadeTransition(opacity: a, child: c),
),
);
If you're using go_router:
GoRoute(
path: '/page2',
pageBuilder: (_, state) {
return CustomTransitionPage(
key: state.pageKey,
child: Page2(),
transitionDuration: Duration(seconds: 2),
transitionsBuilder: (_, a, __, c) => FadeTransition(opacity: a, child: c),
);
},
)
and then:
context.go('/page2');
You can subclass MaterialPageRouteand override buildTransitions.
Eg:
class MyCustomRoute<T> extends MaterialPageRoute<T> {
MyCustomRoute({ WidgetBuilder builder, RouteSettings settings })
: super(builder: builder, settings: settings);
#override
Widget buildTransitions(BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child) {
if (settings.isInitialRoute)
return child;
// Fades between routes. (If you don't want any animation,
// just return child.)
return new FadeTransition(opacity: animation, child: child);
}
}
to use :
new RaisedButton(
child: new Text('Goto'),
onPressed: (){
Navigator.push(
context,
new MyCustomRoute(builder: (context) => new SecondPage()),
);
}),
Replace fade transition with your animation
You can achieve this by using CupertinoPageRoute.
Please check the below code.
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Transition Animation Demo',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new FirstPage(),
);
}
}
class FirstPage extends StatefulWidget {
#override
_FirstPageState createState() => new _FirstPageState();
}
class _FirstPageState extends State<FirstPage> {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('First Page'),
),
body: new Center(
child: new RaisedButton(
child: new Text('Goto Second Page'),
onPressed: () {
Navigator.of(context).push(new SecondPageRoute());
},
),
),
);
}
}
class SecondPageRoute extends CupertinoPageRoute {
SecondPageRoute()
: super(builder: (BuildContext context) => new SecondPage());
// OPTIONAL IF YOU WISH TO HAVE SOME EXTRA ANIMATION WHILE ROUTING
#override
Widget buildPage(BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation) {
return new FadeTransition(opacity: animation, child: new SecondPage());
}
}
class SecondPage extends StatefulWidget {
#override
_SecondPageState createState() => new _SecondPageState();
}
class _SecondPageState extends State<SecondPage> {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('Second Page'),
),
body: new Center(
child: new Text('This is the second page'),
),
);
}
}
Some play-around with animation
// OPTIONAL IF YOU WISH TO HAVE SOME EXTRA ANIMATION WHILE ROUTING
#override
Widget buildPage(BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation) {
return new RotationTransition(
turns: animation,
child: new ScaleTransition(
scale: animation,
child: new FadeTransition(
opacity: animation,
child: new SecondPage(),
),
));
}
I have done this by providing my own builders with custom map in pageTransitionsTheme for the app level theme.
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Startup Name Generator Tile',
home: RandomWords(),
theme: new ThemeData(
primaryColor: Colors.white,
// Add the line below to get horizontal sliding transitions for routes.
pageTransitionsTheme: PageTransitionsTheme(builders: {TargetPlatform.android: CupertinoPageTransitionsBuilder(),}),
),
);
}
}
Of course, I didn't add a map entry for ios as I use only android for TargetPlatform.
You can also check out page_transition package from https://pub.dev/packages/page_transition. This package contains the following different transitions.
fade,
rightToLeft,
leftToRight,
upToDown,
downToUp,
scale (with alignment),
rotate (with alignment),
size (with alignment),
rightToLeftWithFade,
leftToRightWithFade
the simplest way I figured, is to use MaterialPageRoute normally just add: fullscreenDialog: true, inside MaterialPageRoute()
I would like to have my list items perform this animation (mp4) when tapped. I tried using AnimatedCrossFade but it requires its two children to be at the same level, e.g. the detail view cross-fades with the ListView not the tapped item. In fact it seems a Hero animation is the only one that can animate across widgets.
I'm having trouble using Hero. Should it wrap the list item? Does it matter if the Widget subtree is significantly different in the Hero source/destination? Also, can Hero animations be used with LocalHistoryRoutes or staggered animations?
Edit
It's now looking like what I need to do is use an Overlay, the hard part there is that I need to add the selected item to the overlay at the same position on screen where it was tapped, then the animation part would be easy. Possibly of use here is a target/follower pattern e.g. CompositedTransformTarget
You can just use Hero widget to make that kind of animation. Here's my example:
and the source code:
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.blue,
),
home: new FirstPage(title: 'Color Palette'),
);
}
}
class FirstPage extends StatefulWidget {
FirstPage({Key key, this.title}) : super(key: key);
final String title;
#override
_FirstPageState createState() => new _FirstPageState();
}
class _FirstPageState extends State<FirstPage> {
final palette = [
{'#E53935': 0xFFE53935},
{'#D81B60': 0xFFD81B60},
{'#8E24AA': 0xFF8E24AA},
{'#5E35B1': 0xFF5E35B1},
{'#3949AB': 0xFF3949AB},
{'#1E88E5': 0xFF1E88E5},
{'#039BE5': 0xFF039BE5},
{'#00ACC1': 0xFF00ACC1},
{'#00897B': 0xFF00897B},
{'#43A047': 0xFF43A047},
{'#7CB342': 0xFF7CB342},
{'#C0CA33': 0xFFC0CA33},
];
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
body: new Container(
child: new ListView.builder(
itemCount: palette.length,
itemBuilder: (context, index) => new Hero(
tag: palette[index].keys.first,
child: new GestureDetector(
onTap: () {
Navigator
.of(context)
.push(new ColorPageRoute(palette[index]));
},
child: new Container(
height: 64.0,
width: double.infinity,
color: new Color(palette[index].values.first),
child: new Center(
child: new Hero(
tag: 'text-${palette[index].keys.first}',
child: new Text(
palette[index].keys.first,
style: Theme.of(context).textTheme.title.copyWith(
color: Colors.white,
),
),
),
),
),
),
)),
),
);
}
}
class SecondPage extends StatelessWidget {
final Map<String, int> color;
SecondPage({this.color});
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('Color'),
),
body: new Hero(
tag: color.keys.first,
child: new Container(
color: new Color(color.values.first),
child: new Center(
child: new Hero(
tag: 'text-${color.keys.first}',
child: new Text(
color.keys.first,
style:
Theme.of(context).textTheme.title.copyWith(color: Colors.white),
),
),
),
),
),
);
}
}
class ColorPageRoute extends MaterialPageRoute {
ColorPageRoute(Map<String, int> color)
: super(
builder: (context) => new SecondPage(
color: color,
));
#override
Widget buildTransitions(BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation, Widget child) {
return FadeTransition(opacity: animation, child: child);
}
}
Someone wrote an amazing dart-package for just this purpose: https://pub.dev/packages/morpheus#-readme-tab-
All you then need to do is use the MorpheusPageRoute and the package handles the rest.
...
Navigator.push(
context,
MorpheusPageRoute(
builder: (context) => MyWidget(title: title),
),
);
...
I'd just cheat and wrap the whole thing in a Stack - bottom layer would be a page with the AppBar, and the top layer would be transparent until painted on.
onTap, duplicate ListTile onto the top surface, and then a Hero animation would fill the full screen. It's not very elegant, but the framework doesn't (yet) provide for covering the AppBar easily, so having a canvas ready to be painted on for other tricky animations might be resourceful.
I'm unable to comment or edit Lucas' post (new account) but you also need to provide the parentKey of the widget where the animation is to begin:
final widgetKey = GlobalKey();
...
ListTile(
key: widgetKey,
title: Text('My ListItem'),
onTap: () => Navigator.push(
context,
MorpheusPageRoute(
builder: (context) => MyNewPage(),
parentKey: widgetKey,
),
),
),
https://pub.dev/packages/morpheus
After adding firebase_admob plugin and getting it up and running I noticed it overlays the fab and navigation drawer. I've fixed the fab using persistentFooterButtons but I can't seem to find a workaround for the navigation-drawer. Any help is much appreciated.
Find below a sample implementation, to recreate the issue in flutter:
import 'package:flutter/material.dart';
import 'package:firebase_admob/firebase_admob.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Firebase AdMob',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new MyHomePage(title: 'AdMob Test App'),
);
}
}
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> {
int _counter = 0;
BannerAd myBanner;
void _incrementCounter() {
setState(() {
_counter++;
});
}
#override
void initState() {
super.initState();
myBanner = new BannerAd(
// Replace the testAdUnitId with an ad unit id from the AdMob dash.
// https://developers.google.com/admob/android/test-ads
// https://developers.google.com/admob/ios/test-ads
adUnitId: BannerAd.testAdUnitId,
size: AdSize.smartBanner,
targetingInfo: new MobileAdTargetingInfo(
// gender: MobileAdGender.unknown
),
listener: (MobileAdEvent event) {
print("BannerAd event is $event");
},
);
myBanner..load()..show(
// Banner Position
anchorType: AnchorType.bottom,
);
}
#override
void dispose() {
myBanner?.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
drawer: new Drawer(),
body: new Center(
child: new Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new Text('You have pushed the button this many times:'),
new Text('$_counter', style: Theme.of(context).textTheme.display1),
],
),
),
floatingActionButton: new FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: new Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
I'm a little late to this but had same problem.
My nav drawer lives in a scrollable container with a fixed height so that it stops above the add and is scrollable. May not be perfect answer but works for me.
I had the same problem and my solution was the same Added by #moehagene. I added an empty item to the bottom of the drawer with the height of the Ad, so the drawer becomes scrollable when there is not enough space and the Ad is showing. I think this is reasonable. Code below:
return Drawer(
// Add a ListView to the drawer. This ensures the user can scroll
// through the options in the drawer if there isn't enough vertical
// space to fit everything.
child: Column(
children: <Widget>[
Expanded(
child: Container(
child: ListView(
// Important: Remove any padding from the ListView.
padding: EdgeInsets.zero,
children: <Widget>[
_Item1,
_Item2,
_Item3,
_Item4,
model.isShowingAds ? _emptySpaceItem : null,
],
),
),
),
],
),
);