flutter, adding a PageRouteBuilder - flutter

I am trying to modify my Navigation Transition code to use PageRouteBuilder. See method _gotoThirdPage().
The problem is that I cannot pass parameters to new Page. For example, I crash when passing title. Also looking for examples of sending multiple parameters, such as a map.
REQUEST: Could someone Modify the _gotoThirdPage() method to send parameters to Class SecondPage.
//====================. Code below ===================
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 MyHomePage(title: 'Flutter Demo Home Page'),
// Added ===
routes: <String, WidgetBuilder>{
SecondPage.routeName: (BuildContext context) => new SecondPage(title: "SecondPage"),
},
);
}
}
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;
void _incrementCounter() {
setState(() {
_counter++;
});
}
#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 RaisedButton(
onPressed: _gotoSecondPage,
child: new Text("Goto SecondPage- Normal"),
),
new RaisedButton(onPressed: _gotoThirdPage,
child: new Text("goto SecondPage = with PRB"),
),
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.
);
}
void _gotoSecondPage() {
//=========================================================
// Transition - Normal Platform Specific
print('==== going to second page ===');
print( SecondPage.routeName);
Navigator.pushNamed(context, SecondPage.routeName);
}
void _gotoThirdPage() {
//=========================================================
// I believe this is where I would be adding a PageRouteBuilder
print('==== going to second page ===');
print( SecondPage.routeName);
//Navigator.pushNamed(context, SecondPage.routeName);
//=========================================================
//=== please put PageRouteBuilderCode here.
final pageRoute = new PageRouteBuilder(
pageBuilder: (BuildContext context, Animation animation,
Animation secondaryAnimation) {
// YOUR WIDGET CODE HERE
// I need to PASS title here...
// not sure how to do this.
// Also, is there a way to clean this code up?
return new SecondPage();
},
transitionsBuilder: (BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation, Widget child) {
return SlideTransition(
position: new Tween<Offset>(
begin: const Offset(1.0, 0.0),
end: Offset.zero,
).animate(animation),
child: new SlideTransition(
position: new Tween<Offset>(
begin: Offset.zero,
end: const Offset(1.0, 0.0),
).animate(secondaryAnimation),
child: child,
),
);
},
);
Navigator.of(context).push(pageRoute);
}
}
class SecondPage extends StatefulWidget {
SecondPage({Key key, this.title}) : super(key: key);
static const String routeName = "/SecondPage";
final String title;
#override
_SecondPageState createState() => new _SecondPageState();
}
/// // 1. After the page has been created, register it with the app routes
/// routes: <String, WidgetBuilder>{
/// SecondPage.routeName: (BuildContext context) => new SecondPage(title: "SecondPage"),
/// },
///
/// // 2. Then this could be used to navigate to the page.
/// Navigator.pushNamed(context, SecondPage.routeName);
///
class _SecondPageState extends State<SecondPage> {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
//======== HOW TO PASS widget.title ===========
title: new Text(widget.title),
//title: new Text('==== second page ==='),
),
body: new Container(),
floatingActionButton: new FloatingActionButton(
onPressed: _onFloatingActionButtonPressed,
tooltip: 'Add',
child: new Icon(Icons.add),
),
);
}
void _onFloatingActionButtonPressed() {
}
}

I am not quite sure what you are trying to do. The easiest way to pass parameters to new Page for me is using the constructor of the new page if I need to do some processing- show/ use/ pass them later on. Although I dont think it is appropriate if you have large number of parameters.
So in some cases I would use SharedPrefences. I think this will be better for sending multiple parameters, such as a map.
Btw about your navigation I advise you to use Navigate with named routes. Something like:
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:
Navigator.pushNamed(context, '/second');
Documentation.

Related

alert dialog pops up even when different route is called

I have a simple flutter app with two screens. On the first screen, i have an alert dialog that pops up every time a user visits the screen.
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
void main() => runApp(const MyApp());
/// The route configuration.
final GoRouter _router = GoRouter(
routes: <RouteBase>[
GoRoute(
path: '/',
builder: (BuildContext context, GoRouterState state) {
return const HomeScreen();
},
routes: <RouteBase>[
GoRoute(
path: 'details',
builder: (BuildContext context, GoRouterState state) {
return const DetailsScreen();
},
),
],
),
],
);
/// The main app.
class MyApp extends StatelessWidget {
/// Constructs a [MyApp]
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp.router(
routeInformationProvider: _router.routeInformationProvider,
routeInformationParser: _router.routeInformationParser,
routerDelegate: _router.routerDelegate);
}
}
/// The home screen
class HomeScreen extends StatefulWidget {
/// Constructs a [HomeScreen]
const HomeScreen({Key? key}) : super(key: key);
#override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
#override
void initState() {
Timer(const Duration(seconds : 1), (() {
showDialog(
context: context,
builder: (context) {
return someDialogy();
});
print('i have been called forth');
}));
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Home Screen')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
ElevatedButton(
onPressed: () => context.go('/details'),
child: const Text('Go to the Details screen'),
),
],
),
),
);
}
}
/// The details screen
class DetailsScreen extends StatelessWidget {
/// Constructs a [DetailsScreen]
const DetailsScreen({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Details Screen')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <ElevatedButton>[
ElevatedButton(
onPressed: () => context.go('/'),
child: const Text('Go back to the Home screen'),
),
],
),
),
);
}
}
Widget someDialogy () {
return AlertDialog(
content: Center(
child: Text('data'),
),
);
}
When i try to navigate to my second screen using a hyperlink on web say http://localhost/secondscreen, the popup from my first screen shows up.
My guess is that in constructing the route stack, flutter calls the initstate in my first page which does show my popup. What is the best way to go around this while maintaining the popup that shows when my first page is called?
it would be helpful to show some mode code on this, like the somedialogy() method
anyways I suspect the issue might be with your routing solution,
try
onPressed: () {
// Navigate to the second route when the button is pressed
Navigator.push(
context,
MaterialPageRoute(builder: (context) => SecondRoute()),
);
},
I think its all about Timer
try this:
Future.delayed(Duration(seconds: 1), () {
showDialog(
context: context,
builder: (context) {
return someDialogy();
});});
read this for more about Timer Understanding Flutter’s Timer class and Timer.periodic
and this 2 Types of Flutter Delay Widgets

Flutter Navigator 2.0: Named routes with Drawer Navigation

I'm working on a simple navigation on an app that follows Flutter Navigator 2.0 to have better support on routes. An advantage of Navigator 2.0 is that you have granular controls on screen navigations and have better support for web.
Here's a diagram of what I'm trying to achieve.
I'm able to navigate from the LoginScreen to the "main page" without issues. The main page has a Drawer that displays HomeScreen by default. My issue here is that I'm unsure on how to properly display HomeScreen and ProfileScreen from the Drawer using Navigator 2.0. Following this guide demonstrates that a screen is pushed to the Navigator stack and routes are tracked with a RouterDelegate. Doing so pushes a new screen to the stack.
The app seems to work fine, but I just removed the transition animation. You can notice that a new screen is drawn even before the Drawer can finish its closing animation. The entire Drawer widget is drawn again since the main page is pushed to the stack.
This guide on displaying screens on a Drawer only replaces the widget on the same screen. What I'm currently doing is I'm rebuilding the main page every time it navigates to HomeScreen and ProfileScreen
Here's how the main page looks like. The currentPage is updated to display the Widget for the HomeScreen and ProfileScreen.
late Widget currentPage;
#override
Widget build(BuildContext context) {
currentPage = HomeScreen();
return Scaffold(
appBar: AppBar(title: Text(title)),
body: currentPage,
drawer: Drawer(
child: ListView(
children: <Widget>[
// ...
ListTile(
title: Text('Home'),
onTap: () {
widget.navHome();
Navigator.pop(context);
},
),
ListTile(
title: Text('Profile'),
// ...
),
],
),
),
);
}
The Navigator on my RouterDelegate has this setup.
Navigator(
key: navigatorKey,
transitionDelegate: NoAnimationTransitionDelegate(),
pages: [
if (show404)
MaterialPage(
key: ValueKey('UnknownPage'),
child: UnknownScreen(),
)
else if (page == Pages.home)
MaterialPage(
key: ValueKey('HomePage'),
child: HomePage(
title: 'Home',
handleLogout: _logOut,
navHome: _navHome,
navProfile: _navProfile,
currentScreen: HomeScreen(username: username),
),
)
else if (page == Pages.profile)
MaterialPage(
key: ValueKey('ProfilePage'),
child: HomePage(
title: 'Profile',
handleLogout: _logOut,
navHome: _navHome,
navProfile: _navProfile,
currentScreen: ProfileScreen(),
),
)
else // username is null, no user logged in
MaterialPage(
key: ValueKey('LoginPage'),
child: LoginPage(
title: 'Login',
onTapped: _handleLogin,
),
),
]
)
The functions are passed as arguments to update the routes in the RouterDelegate.
enum Pages { login, home, profile }
class PageRouterDelegate extends RouterDelegate<PageRoutePath>
with ChangeNotifier, PopNavigatorRouterDelegateMixin<PageRoutePath> {
// ... Other lines omitted for simplicity
void _handleLogin(String username, String password) {
// TODO Auth
this.page = Pages.home;
notifyListeners();
}
void _navHome() {
this.page = Pages.home;
notifyListeners();
}
void _navProfile() {
this.page = Pages.profile;
notifyListeners();
}
void _logOut() {
this.page = Pages.login;
notifyListeners();
}
}
Any suggestions on what approach can be done to display different screens from a Drawer and have their routes tracked using Navigator 2.0?
Update:
I found a solution for this issue, but it'll need to use two Navigators. Assigning keys to the Navigators helps us manage both of them.
The second Navigator is on the Home page that has a Drawer. This enables us to navigate through different pages without rebuilding the entire screen. The caveat in this approach is that only the routes on mainNavigator are displayed on the address bar on web.
Let me know if you have other suggestions on how this can be approached.
I gave up on using Navigator 2.0 for now due to the amount of boiler plate code that I need to write. I took a step back and used Navigator 1.0 with named routes. The same principle can still be applied to Navigator 2.0
What I did now is instead of building a different Screen on the main Navigator, I still rebuild the same screen(NavigatorPage) to save resources and pass arguments for the screen that I'd like to navigate to.
final _mainNavigatorKey = GlobalKey<NavigatorState>();
...
MaterialApp(
title: 'Navigator Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
navigatorKey: _mainNavigatorKey,
routes: {
/// [title] updates the title on the main AppBar
/// [route] NavigatorPage Router depends on route defined on this parameter
/// [showDrawer] show/hide main AppBar drawer
Nemo.home: (context) => NavigatorPage(
title: 'Home',
route: Nemo.home,
navigatorKey: _mainNavigatorKey,
showDrawer: true,
),
Nemo.post: (context) => NavigatorPage(
title: 'Post',
route: Nemo.post,
navigatorKey: _mainNavigatorKey,
showDrawer: true),
Nemo.profile: (context) => NavigatorPage(
title: 'Profile',
route: Nemo.profile,
navigatorKey: _mainNavigatorKey,
showDrawer: true),
Nemo.settings: (context) => NavigatorPage(
title: 'Settings',
route: Nemo.settings,
navigatorKey: _mainNavigatorKey,
showDrawer: true),
},
);
The NavigatorPage contains the main AppBar where we can update the title and display/hide a LinearProgressBar. From there, we can navigate to the desired Screen by checking the route passed in the arguments with the _mainNavigatorKey as our Navigator.
Navigator(
// key: _navigatorKey,
onGenerateRoute: (RouteSettings settings) {
WidgetBuilder builder;
// Manage your route names here
// switch (settings.name) {
switch (widget.route) {
/// Default page displayed on Home Screen
case Nemo.home:
builder = (BuildContext context) => _homePage();
break;
case Nemo.post:
builder = (BuildContext context) => _postPage();
break;
case Nemo.profile:
builder = (BuildContext context) => _profilePage();
break;
case Nemo.settings:
builder = (BuildContext context) => _settingsPage();
break;
default:
builder = (BuildContext context) => const UnknownPage();
}
return MaterialPageRoute(
builder: builder,
settings: settings,
);
},
),
While we're still rebuilding a page on the main Navigator. A workaround for the janky animation when the Navigation Drawer is closed is to add a delay of at least 300ms to wait for the animation to finish before executing the navigation. You can adjust the delay as you see fit.
ListTile(
title: const Text('Home'),
onTap: () {
// Close the drawer
Navigator.pop(context);
/// [drawerDelay] gives time to animate the closing of the Drawer
Timer(Duration(milliseconds: drawerDelay), () async {
widget.navigatorKey.currentState!.pushNamed(Nemo.home);
});
},
),
Demo
Sample Code
import 'dart:async';
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
MyApp({Key? key}) : super(key: key);
final _mainNavigatorKey = GlobalKey<NavigatorState>();
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Navigator Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
// home: const NavigatorPage(title: 'Flutter Demo Home Page'),
navigatorKey: _mainNavigatorKey,
routes: {
/// [title] updates the title on the main AppBar
/// [route] NavigatorPage Router depends on route defined on this parameter
/// [showDrawer] show/hide main AppBar drawer
Nemo.home: (context) => NavigatorPage(
title: 'Home',
route: Nemo.home,
navigatorKey: _mainNavigatorKey,
showDrawer: true,
),
Nemo.post: (context) => NavigatorPage(
title: 'Post',
route: Nemo.post,
navigatorKey: _mainNavigatorKey,
showDrawer: true),
Nemo.profile: (context) => NavigatorPage(
title: 'Profile',
route: Nemo.profile,
navigatorKey: _mainNavigatorKey,
showDrawer: true),
Nemo.settings: (context) => NavigatorPage(
title: 'Settings',
route: Nemo.settings,
navigatorKey: _mainNavigatorKey,
showDrawer: true),
},
);
}
}
class NavigatorPage extends StatefulWidget {
const NavigatorPage(
{Key? key,
required this.title,
required this.route,
required this.navigatorKey,
required this.showDrawer})
: super(key: key);
final String title;
final String route;
final bool showDrawer;
final GlobalKey<NavigatorState> navigatorKey;
#override
State<NavigatorPage> createState() => _NavigatorPageState();
}
class _NavigatorPageState extends State<NavigatorPage> {
// final _navigatorKey = GlobalKey<NavigatorState>();
/// Drawer delay let's us have the Navigation Drawer close first
/// before the navigating to the next Screen
int drawerDelay = 300;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
drawer: widget.showDrawer
? Drawer(
/// TODO return null to hide Drawer if in Login/Registration page
// 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: ListView(
// Important: Remove any padding from the ListView.
padding: EdgeInsets.zero,
children: <Widget>[
const DrawerHeader(
decoration: BoxDecoration(
color: Colors.blue,
),
child: Text('Drawer Header'),
),
ListTile(
title: const Text('Home'),
onTap: () {
// Close the drawer
Navigator.pop(context);
/// [drawerDelay] gives time to animate the closing of the Drawer
Timer(Duration(milliseconds: drawerDelay), () async {
widget.navigatorKey.currentState!.pushNamed(Nemo.home);
});
},
),
ListTile(
title: const Text('Profile'),
onTap: () {
// Close the drawer
Navigator.pop(context);
Timer(Duration(milliseconds: drawerDelay), () async {
widget.navigatorKey.currentState!
.pushNamed(Nemo.profile);
});
},
),
ListTile(
title: const Text('Settings'),
onTap: () {
// Close the drawer
Navigator.pop(context);
Timer(Duration(milliseconds: drawerDelay), () async {
widget.navigatorKey.currentState!
.pushNamed(Nemo.settings);
});
},
),
],
),
)
: null,
body: Navigator(
// key: _navigatorKey,
/// initialRoute needs to be set to '/'
onGenerateRoute: (RouteSettings settings) {
WidgetBuilder builder;
// Manage your route names here
// switch (settings.name) {
switch (widget.route) {
/// Default page displayed on Home Screen
case Nemo.home:
builder = (BuildContext context) => _homePage();
break;
case Nemo.post:
builder = (BuildContext context) => _postPage();
break;
case Nemo.profile:
builder = (BuildContext context) => _profilePage();
break;
case Nemo.settings:
builder = (BuildContext context) => _settingsPage();
break;
default:
builder = (BuildContext context) => const UnknownPage();
}
return MaterialPageRoute(
builder: builder,
settings: settings,
);
},
),
);
}
Widget _homePage() =>
HomePage(title: 'Home', navigatorKey: widget.navigatorKey);
Widget _postPage() =>
PostPage(title: 'Post', navigatorKey: widget.navigatorKey);
Widget _profilePage() =>
ProfilePage(title: 'Profile', navigatorKey: widget.navigatorKey);
Widget _settingsPage() =>
SettingsPage(title: 'Settings', navigatorKey: widget.navigatorKey);
}
class Nemo {
static const home = '/';
static const login = '/login';
static const register = '/register';
static const post = '/post';
static const profile = '/profile';
static const settings = '/settings';
}
/// Constant values for UI elements
class Constants {
static const String webVersion = 'web-0.1.9-dev';
static const double paddingSmall = 8.0;
static const double paddingNormal = 16.0;
static const double heightNormal = 64.0;
static const double heightThreadCard = 72.0;
static const double heightButtonNormal = 42.0;
static const double widthButtonNormal = 160.0;
}
class UnknownPage extends StatefulWidget {
const UnknownPage({Key? key}) : super(key: key);
#override
State<UnknownPage> createState() => _UnknownPageState();
}
class HomePage extends StatefulWidget {
const HomePage({Key? key, required this.title, required this.navigatorKey})
: super(key: key);
final String title;
final GlobalKey<NavigatorState> navigatorKey;
#override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
// appBar: AppBar(
// title: Text(widget.title),
// ),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'Home',
),
ElevatedButton(
child: const Text('View Post Page'),
onPressed: () {
widget.navigatorKey.currentState!.pushNamed(Nemo.post);
},
),
],
),
),
);
}
}
class PostPage extends StatefulWidget {
const PostPage(
{Key? key, required this.title, this.id, required this.navigatorKey})
: super(key: key);
final String title;
final String? id;
final GlobalKey<NavigatorState> navigatorKey;
#override
State<PostPage> createState() => _PostPageState();
}
class _PostPageState extends State<PostPage> {
#override
Widget build(BuildContext context) {
return Scaffold(
// appBar: AppBar(
// title: Text(widget.title),
// ),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'Id from Route: ${widget.id}',
),
],
),
),
);
}
}
class SettingsPage extends StatefulWidget {
const SettingsPage(
{Key? key, required this.title, required this.navigatorKey})
: super(key: key);
final String title;
final GlobalKey<NavigatorState> navigatorKey;
#override
State<SettingsPage> createState() => _SettingsPageState();
}
class _SettingsPageState extends State<SettingsPage> {
#override
Widget build(BuildContext context) {
return Scaffold(
// appBar: AppBar(
// title: Text(widget.title),
// ),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'Settings',
),
ElevatedButton(
child: const Text('View Details'),
onPressed: () {
widget.navigatorKey.currentState!.pushNamed(Nemo.post);
},
),
],
),
),
);
}
}
class ProfilePage extends StatefulWidget {
const ProfilePage({Key? key, required this.title, required this.navigatorKey})
: super(key: key);
final String title;
final GlobalKey<NavigatorState> navigatorKey;
#override
State<ProfilePage> createState() => _ProfilePageState();
}
class _ProfilePageState extends State<ProfilePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
// appBar: AppBar(
// title: Text(widget.title),
// ),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'Profile',
),
ElevatedButton(
child: const Text('View Details'),
onPressed: () {
widget.navigatorKey.currentState!.pushNamed(Nemo.post);
},
),
],
),
),
);
}
}
class _UnknownPageState extends State<UnknownPage> {
#override
Widget build(BuildContext context) {
return Scaffold(
// appBar: AppBar(
// title: Text(widget.title),
// ),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: const <Widget>[
Text(
'404',
),
],
),
),
);
}
}
If you'd like to have a sample with basic Login and Register Screen included. I've created this template that you can check: https://github.com/omatt/flutter-navdrawer-template
It's not a direct answer, just maybe a reference of how to solve this problem.
Flutter recently updated routing example and in that example, they use InheritedWidget with ChangeNotifier to push new route to route delegate like RouteStateScope.of(context)!.go('/book/${book.id}'); while keeping state of routing alive.

Flutter Navigator, Horizontal Transiation with PageRoutebuilder

I'm trying to force a horizontal Transiation in the Flutter Navigator Widget. Navigator uses the platform Default Transition from one screen to the next. In iOS, the transition is Right to left. Pops Left to Right. In Android it is Bottom to Top. I believe solution is to use a PageRouteBuilder, but no luck getting it to work. I marked the method with the Navigator Widget that I'd like to modify with a PageRouteBuilder to get desired Horizontal transition.
Code Snippet 2, is the Transition code I have been trying to make work with no luck.
This code works as default transition.
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 MyHomePage(title: 'Flutter Demo Home Page'),
initialRoute: "/",
routes: {
'/Second': (context) => SecondScreen(),
},
);
}
}
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> {
#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>[
ButtonMaterial02(),
new Text('NAV DEMO...',),
new Text('How do I get a Horizontal Transition in Android?',),
],
),
),
// This trailing comma makes auto-formatting nicer for build methods.
);
}
//================================================================
//================================================================
Padding ButtonMaterial02() {
return Padding(
padding: const EdgeInsets.all(18.0),
child: new MaterialButton(
onPressed: MatButton02_onPress,
child: new Text("Material Button 02"),
padding: EdgeInsets.all(50.0),
minWidth: double.infinity,
color: Theme.of(context).primaryColor,
),
);
}
// add Horizontal code here
void MatButton02_onPress() {
print(" MaterialButton02 onPressed...");
Navigator.pushNamed(context, '/Second');
//*************************************************
//*************************************************
// HOW do I replace the Navigator above to use
// PageRouteBuilder so I can force ANDROID to
// Transition Right to Left instead of BottomToTop?
//*************************************************
//*************************************************
}
}
//================================================================
//================================================================
class SecondScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
// TODO: implement build
return Scaffold(
appBar: AppBar(
title: Text("Second Screen"),
),
body: Center(
child: RaisedButton(
child: new Text("RETURN"),
onPressed: (){
Navigator.pop(context);
},
),
),
);
}
}
//================================================================
//================================================================
Code Snippet 2... transition code I have been trying to use.
transitionsBuilder: (
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child) {
return SlideTransition(
position: new Tween<Offset>(
begin: const Offset(1.0, 0.0),
end: Offset.zero,
).animate(animation),
child: new SlideTransition(
position: new Tween<Offset>(
begin: Offset.zero,
end: const Offset(1.0, 0.0),
).animate(secondaryAnimation),
child: child,
),
);
},
);
Navigator.of(context).push('Second');
You're interested in using the CupertinoPageRoute.
It animates from right to left, designed to imitate iOS's transition animation.
Following the example here, replace MaterialPageRoute references with CupertinoPageRoute and you'll be set!

Flutter, passing parameters to PageRoutebuilder

In Method gotoThirdScreen(), I am tryiing to pass parameters to a class.
I believe the issue starts at:
//=========================================================
//=== please put PageRouteBuilderCode here.
The Class SecondPage has... widget.title. But not sure how to pass titel from _gotoThirdPage().
This code works
//===============================
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 MyHomePage(title: 'Flutter Demo Home Page'),
// Added ===
routes: <String, WidgetBuilder>{
SecondPage.routeName: (BuildContext context) => new SecondPage(title: "SecondPage"),
},
);
}
}
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;
void _incrementCounter() {
setState(() {
_counter++;
});
}
#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>[
Padding(
padding: const EdgeInsets.all(28.0),
child: new RaisedButton(
onPressed: _gotoSecondPage,
child: new Text("Goto SecondPage- Normal"),
),
),
Padding(
padding: const EdgeInsets.all(38.0),
child: new RaisedButton(
onPressed: _gotoThirdPage,
child: new Text("goto SecondPage = with PRB"),
),
),
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.
);
}
void _gotoSecondPage() {
//=========================================================
// Transition - Normal Platform Specific
print('==== going to second page ===');
print( SecondPage.routeName);
Navigator.pushNamed(context, SecondPage.routeName);
}
void _gotoThirdPage() {
//=========================================================
// I believe this is where I would be adding a PageRouteBuilder
print('==== going to second page ===');
print( SecondPage.routeName);
//Navigator.pushNamed(context, SecondPage.routeName);
//=========================================================
//=== please put PageRouteBuilderCode here.
final pageRoute = new PageRouteBuilder(
pageBuilder: (BuildContext context, Animation animation,
Animation secondaryAnimation) {
// YOUR WIDGET CODE HERE
// I need to PASS title here...
// not sure how to do this.
// Also, is there a way to clean this code up?
return new SecondPage();
},
transitionsBuilder: (BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation, Widget child) {
return SlideTransition(
position: new Tween<Offset>(
begin: const Offset(1.0, 0.0),
end: Offset.zero,
).animate(animation),
child: new SlideTransition(
position: new Tween<Offset>(
begin: Offset.zero,
end: const Offset(1.0, 0.0),
).animate(secondaryAnimation),
child: child,
),
);
},
);
Navigator.of(context).push(pageRoute);
}
}
class SecondPage extends StatefulWidget {
SecondPage({Key key, this.title}) : super(key: key);
static const String routeName = "/SecondPage";
final String title;
#override
_SecondPageState createState() => new _SecondPageState();
}
/// // 1. After the page has been created, register it with the app routes
/// routes: <String, WidgetBuilder>{
/// SecondPage.routeName: (BuildContext context) => new SecondPage(title: "SecondPage"),
/// },
///
/// // 2. Then this could be used to navigate to the page.
/// Navigator.pushNamed(context, SecondPage.routeName);
///
class _SecondPageState extends State<SecondPage> {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
//======== HOW TO PASS widget.title ===========
title: new Text(widget.title),
//title: new Text('==== second page ==='),
),
body: new Container(),
floatingActionButton: new FloatingActionButton(
onPressed: _onFloatingActionButtonPressed,
tooltip: 'Add',
child: new Icon(Icons.add),
),
);
}
void _onFloatingActionButtonPressed() {
}
}
you can use onGenerateRoute here the example
main.dart
void main() {
runApp(new App());
}
app.dart
class App extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
home: new HomeScreen(),
routes: <String, WidgetBuilder>{
'/home': (BuildContext context) => new HomeScreen(),
//other routes
},
//onGenerateRoute Custom
onGenerateRoute: getGenerateRoute);
}
}
Route<Null> getGenerateRoute(RouteSettings settings) {
final List<String> path = settings.name.split('/');
if (path[0] != '') return null;
if (path[1].startsWith('team')) {
if (path.length != 3) return null;
String _title = path[2];
return new MaterialPageRoute<Null>(
settings: settings,
builder: (BuildContext context) => new TeamScreen(
title: _title,
),
);
}
// The other paths we support are in the routes table.
return null;
}
home_screen.dart
import 'package:flutter/material.dart';
class HomeScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('Home Screen'),
),
body: new Center(
child: new Column(
children: <Widget>[
new RaisedButton(
onPressed: () {
String _title = "Team 1";
Navigator.pushNamed(context, '/team/$_title');
},
child: new Text('Go Team 1'),
),
new RaisedButton(
onPressed: () {
String _title = "Team 2";
Navigator.pushNamed(context, '/team/$_title');
},
child: new Text('Go Team 2'),
)
],
),
),
);
}
}
team.dart
import 'package:flutter/material.dart';
class TeamScreen extends StatelessWidget {
final String title;
const TeamScreen({Key key, this.title}) : super(key: key);
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('Team screen'),
),
body: new Center(
child: new Text(title),
),
);
}
}
Pass title value to SecondPage constructor:
void _gotoThirdPage() {
String page_title = "Yet Another Page";
final pageRoute = new PageRouteBuilder(
pageBuilder: (BuildContext context, Animation animation,
Animation secondaryAnimation) {
return new SecondPage(title: page_title);
},

How to do Navigator.popUntil properly when using different animations for each push

I am trying to rebuild iOS app in Flutter, but facing a problem with navigation.
Here what I am trying to do:
List of Added Exchange Pairs with Add button (A screen)
Add button opens Picker with Exchanges (B screen) with transition from bottom to top.
By tapping on exchange it pushes new Picker with Pairs (C
screen) with transition from right to left.
when user taps on pair it closes all pickers at once and deliver result of picking to A screen.
I have tried double pop and popUntil but result always same, I see 2 back transitions (left to right and top to bottom) at same time.
How it looks in iOS native app:
How it looks in Flutter app:
Solved with nested Navigator
Wrapped Screen B with Navigator and used this navigator to push screen C, on screen C used root navigator to pop. Result is below:
Here the example of how I solved it:
import 'package:flutter/material.dart';
void main() {
MaterialPageRoute.debugEnableFadingRoutes = true;
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 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> {
String _result = "--";
void _openSubscreen() {
Navigator.of(context).push<String>(
new MaterialPageRoute(
settings: RouteSettings(name: '/subscreen'),
builder: (context) => SubScreen(),
),
).then((result) => setState((){
_result = result;
}));
}
#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(
'Result from navigator:',
),
new Text(
_result,
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.headline,
),
SizedBox(height: 32.0,),
OutlineButton(
onPressed: _openSubscreen,
child: Text('Start flow'),
),
],
),
),
);
}
}
class SubScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: new Navigator(
onGenerateRoute: (routeSettings) {
final path = routeSettings.name;
if (path == '/') {
return new MaterialPageRoute(
settings: routeSettings.copyWith(isInitialRoute: true),
builder: (_) => SubScreenPage1(),
);
} else if (path == '/nexpage') {
return new MaterialPageRoute(
settings: routeSettings,
builder: (_) => SubScreenPage2(),
);
}
},
),
);
}
}
class SubScreenPage1 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new Center(
child: OutlineButton(
child: Text('Next sub page!'),
onPressed: () {
Navigator.of(context).pushNamed('/nexpage');
},
),
);
}
}
class SubScreenPage2 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new Center(
child: OutlineButton(
child: Text('Deliver result!'),
onPressed: () {
final date = DateTime.now().toString();
Navigator
.of(context, rootNavigator: true)
.pop('Delivered at $date');
},
),
);
}
}
When you build your MaterialApp by setting home: and routes: you can achieve "pop to root" without hardcoding what route to pop until by;
Navigator.popUntil(
context,
ModalRoute.withName(Navigator.defaultRouteName),
);
Because Navigator.defaultRouteName will be set to whatever you set home: to.
Going a bit off-topic but, this is especially nice if you have "variable" home screen, as in using a FutureBuilder to decide what will be the home screen. For example, if you are showing a splash screen until you are loading the initial state from disk.
home: isUserLoggedIn
? HomePage()
: FutureBuilder(
future: () async {
print('Initializing');
print('Waiting For NoReason');
await Future.delayed(const Duration(seconds: 1));
print('Initialization Complete');
}(),
builder: (_, snap) {
if (snap.connectionState == ConnectionState.waiting) {
return SplashPage();
} else {
return LogInPage();
}
},
),