flutter 2.0: nested MaterialApps -> problem with textInput inside showModalBottomSheet - flutter

In my App, I use 2 Material Apps to handle the Navigation with the BottomNavigation Bar.
As my App is pretty complex, this was the best way to do this.
On one Screen, when the user is not logged in, a bottomSheet opens, where the user can put in his login credentials.
The bottomSheet shall appear above the Navigation Bar.
In the following Code Snippet, this is how i solved it.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(),
);
}
}
final _navigationKey = GlobalKey<NavigatorState>();
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int currentIndex = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
resizeToAvoidBottomInset: true,
body: MaterialApp(
navigatorKey: _navigationKey,
routes: {
'0': (context) => Home(),
'1': (context) => Scaffold(backgroundColor: Colors.yellow),
},
initialRoute: '0',
),
bottomNavigationBar: BottomNavigationBar(
currentIndex: currentIndex,
items: [
BottomNavigationBarItem(
icon: Icon(Icons.account_box_rounded),
label: 'account',
),
BottomNavigationBarItem(
icon: Icon(Icons.baby_changing_station),
label: 'stuff',
)
],
onTap: (int index) {
setState(() {
currentIndex = index;
});
Navigator.of(context).popUntil((route) => !(route is PopupRoute));
_navigationKey.currentState.pushReplacementNamed(index.toString());
},
),
);
}
}
class Home extends StatefulWidget {
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
#override
void initState() {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
showModalBottomSheet(
context: context,
useRootNavigator: false,
isScrollControlled: true,
builder: (_) => Container(
child: Padding(
padding: const EdgeInsets.all(20.0),
child: TextField(),
),
),
);
});
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: true,
backgroundColor: Colors.red,
);
}
}
Now to the problem:
In flutter 1.22:
When the user taps on the TextInput, the Keyboard opens and the bottomSheet moves its position above the keyboard.
In flutter 2.0:
When the user taps on the TextInput, the Keyboard opens and the bottomSheet moves its position out of screen
Does anyone have a good workaround?
what I tried so far
I gave the bottomSheet a bottom Padding:
bottom: MediaQuery.of(context).viewInsets.bottom),
But it didn't solve the problem

I think the app structure is a bit strange with the two nested MaterialApps.
The behavior you described is not exactly the same I get when I use your code on my device.
However in my case I solved the problem by setting resizeToAvoidBottomInset: false to both the Scaffold elements.

I had the same issue, I fixed using a SingleChildScrollView inside my bottom sheet.
showModalBottomSheet(
isScrollControlled: true,
context: context,
builder: (context) {
return SingleChildScrollView(
child: Container(
padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
child: ...,
),
);
});
Source: https://github.com/flutter/flutter/issues/18564#issuecomment-586205403

Related

How to fix BottomNavigationBar disappearance issue in Flutter App

I am facing an issue with my bottom navigation bar. The problem is that the bottom navigation bar disappears whenever I navigate to another page.
I am using the following code to navigate to another page:
Navigator.of(context).pushReplacement(MaterialPageRoute(
builder: (BuildContext context) => WorkoutsPage()
));
Here is a summarized part of the BottomBar
class BottomBar extends StatefulWidget {
const BottomBar({Key? key}) : super(key: key);
#override
State<BottomBar> createState() => _BottomBarState();
}
class _BottomBarState extends State<BottomBar> {
int _selectedIndex = 0;
static final List<Widget> _widgetOptions = <Widget>[
HomeScreen(),
WorkoutsPage(),
................
];
void _onItemTapped(int index) {
setState(() {
_selectedIndex = index;
//print('Tapped index is: ${_selectedIndex}');
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: _widgetOptions[_selectedIndex],
),
bottomNavigationBar: Column(
children: [
BottomNavigationBar(
currentIndex: _selectedIndex,
onTap: _onItemTapped,
..................................
type: BottomNavigationBarType.fixed,
items: const [
BottomNavigationBarItem(
icon: Icon(FluentSystemIcons.ic_fluent_home_regular),
activeIcon: Icon(FluentSystemIcons.ic_fluent_home_filled),
label: "Home"),
BottomNavigationBarItem(
icon:
Icon(FluentSystemIcons.ic_fluent_clipboard_text_regular),
activeIcon:
Icon(FluentSystemIcons.ic_fluent_clipboard_text_filled),
label: "Plan"),
Here is the main.dart
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
routes: {
'/': (context) => _defaultHome,
'/home': (context) => const BottomBar(),
'/login': (context) => const LoginPage(),
'/register': (context) => const RegisterPage(),
},
);
}
My question what should I do to keep the bottombar when I use
Navigator.of(context).pushReplacement(MaterialPageRoute(
builder: (BuildContext context) => WorkoutsPage()
));
after clicking a submit button or something
BottomBar is a page. When you use pushReplacement, You move to another Scaffold page that is no BottomBar.
An alternative is to turn Column widget of bottomNavigationBar into a separate StatefulWidget and start passing it in all Scaffold pages.
Keep selectedIndex as a global variable so that its value remains even when you change to another route.

How to Passing Data from Navigator Pop to Previous Page Where The Data is Used in The Widget Inside the ListView.builder

As stated in the title. How to return data to the previous page where the data is used to list widgets.
I have read this article Flutter Back button with return data or other similar articles. The code works perfectly. But there is a problem if I want to use the data returned to the widget that is in the list.\
Note that I only want to update one ListWidget, I don't want to refresh the state of the entire HomePage like the solution in this article Flutter: Refresh on Navigator pop or go back.
Here is a simple code sample to represent the problem I'm facing.
(check on ListWidget Class and SecondPage Class below)
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: HomePage(),
);
}
}
HomePage class
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Home'),
),
body: Center(
child: ListView.builder(
itemCount: 4,
itemBuilder: (_, index){
return ListWidget(number: index+1);
},
)
),
);
}
}
ListWidget Class
class ListWidget extends StatelessWidget{
ListWidget({#required this.number});
final int? number;
String? statusOpen;
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () async {
statusOpen = await Navigator.of(context, rootNavigator: true)
.push(
MaterialPageRoute(
builder: (BuildContext context) => SecondPage(),
),
);
},
child: Container(
margin: EdgeInsets.all(10),
padding: EdgeInsets.all(20),
color: Colors.amber,
child: Text(statusOpen != null ? '$number $statusOpen' : '$number Unopened'),
//
// I want to change the text here to 'has Opened' when the user returns from SecondPage
//
),
);
}
}
SecondPage Class
class SecondPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Second Page'),
),
body: Center(
child: RaisedButton(
onPressed: () {
Navigator.pop(context, 'has Opened');
// return 'has Opened' to await statusOpen variable
},
child: Text('Go Back'),
),
),
);
}
}
is there any solution to handle this?
If you make your listWidget a stateful widget, then you can get the solution where you just need to call setState when you return to your previous screen. And in this way you will be only changing your single list element and not the full screen.
sample code:
changing this line- class ListWidget extends StatefulWidget
and adding these lines -
onTap: () async {
statusOpen = await Navigator.of(context, rootNavigator: true)
.push(
MaterialPageRoute(
builder: (BuildContext context) => SecondPage(),
),
);
setState(() {
});
},
If you used the data in your listview just call setstate after Navigator.pop like below code
onTap: () async {
statusOpen = await Navigator.of(context, rootNavigator: true)
.push(
MaterialPageRoute(
builder: (BuildContext context) => SecondPage(),
),
).then((value) async {
setState(() {});
});
},

How to implement telegram style pop up in flutter? [duplicate]

I want a widget that will sit on top of the entire application. When I have tried to do this with Overlay.of(context).insert the overlay would later disappear after replacing that route. Is there a way I can have a widget on top of my app even if the screen is later popped?
Maybe a more optimal way exists, but as an option this is an example with two pages, local navigator and Overlay.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
final _navigatorKey = GlobalKey<NavigatorState>();
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: WillPopScope(
onWillPop: () async => !await _navigatorKey.currentState.maybePop(),
child: LayoutBuilder(
builder: (context, constraints) {
WidgetsBinding.instance.addPostFrameCallback((_) => _insertOverlay(context));
return Navigator(
key: _navigatorKey,
onGenerateRoute: (RouteSettings settings) {
switch (settings.name) {
case '/page2':
return MaterialPageRoute(builder: (_) => Page2());
default:
return MaterialPageRoute(builder: (_) => Page1(_navigatorKey));
}
},
);
},
),
),
);
}
void _insertOverlay(BuildContext context) {
return Overlay.of(context).insert(
OverlayEntry(builder: (context) {
final size = MediaQuery.of(context).size;
print(size.width);
return Positioned(
width: 56,
height: 56,
top: size.height - 72,
left: size.width - 72,
child: Material(
color: Colors.transparent,
child: GestureDetector(
onTap: () => print('ON TAP OVERLAY!'),
child: Container(
decoration: BoxDecoration(shape: BoxShape.circle, color: Colors.redAccent),
),
),
),
);
}),
);
}
}
class Page1 extends StatelessWidget {
final GlobalKey<NavigatorState> navigatorKey;
Page1(this.navigatorKey);
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.green[200],
appBar: AppBar(title: Text('Page1')),
body: Container(
alignment: Alignment.center,
child: RaisedButton(
child: Text('go to Page2'),
onPressed: () => navigatorKey.currentState.pushNamed('/page2'),
),
),
);
}
}
class Page2 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.yellow[200],
appBar: AppBar(title: Text('back to Page1')),
body: Container(
alignment: Alignment.center,
child: Text('Page 2'),
),
);
}
}
Screenshot (Null safe):
Full code:
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
Offset _offset = Offset.zero;
#override
Widget build(BuildContext context) {
return MaterialApp(
home: LoginPage(),
builder: (context, child) {
return Stack(
children: [
child!,
Positioned(
left: _offset.dx,
top: _offset.dy,
child: GestureDetector(
onPanUpdate: (d) => setState(() => _offset += Offset(d.delta.dx, d.delta.dy)),
child: FloatingActionButton(
onPressed: () {},
backgroundColor: Colors.black,
child: Icon(Icons.add),
),
),
),
],
);
},
);
}
}
LoginPage:
class LoginPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('LoginPage')),
body: Center(
child: ElevatedButton(
onPressed: () => Navigator.push(context, MaterialPageRoute(builder: (_) => HomePage())),
child: Text('Page2'),
),
),
);
}
}
HomePage:
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('HomePage')),
body: FlutterLogo(size: 300),
);
}
}
After reading the comments, find github-repo-link
created an overlay that will sit on top of everything
that can be called from anywhere.
just 4 easy steps to follow
flutterflutter-layout
STEP-1: in main.dart:
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Stack( <-- using stack
children: [
MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
),
OverlayView(),<-- my overlay widget
],
),
);
}
}
STEP-2: OverLayView.dart
class OverlayView extends StatelessWidget {
const OverlayView({
Key key,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return ValueListenableBuilder<bool>( <--- IMP , using ValueListenableBuilder for showing/removing overlay
valueListenable: Loader.appLoader.loaderShowingNotifier,
builder: (context, value, child) {
if (value) {
return yourOverLayWidget(); <-- your awesome overlay
} else {
return Container();
}
},
);
}
STEP-3: loder_controller.dart (to show/hide)
class Loader {
static final Loader appLoader = Loader(); <-- singleton
ValueNotifier<bool> loaderShowingNotifier = ValueNotifier(false);
ValueNotifier<String> loaderTextNotifier = ValueNotifier('error message');
void showLoader() { <-- using to show from anywhere
loaderShowingNotifier.value = true;
}
void hideLoader() { <-- using to hide from anywhere
loaderShowingNotifier.value = false;
}
void setText({String errorMessage}) { <-- using to change error message from anywhere
loaderTextNotifier.value = errorMessage;
}
void setImage() { <-- DIY
// same as that of setText //
}
}
FINAL STEP-4: show/hide loder
I'm showing it, on boilerplate code of increment method to show the loader
void _incrementCounter() async {
Loader.appLoader.showLoader(); <-- show loder
Loader.appLoader.setText(errorMessage: 'this is custom error message');<-- set custom message
await Future.delayed(Duration(seconds: 5)); <-- im hiding it after 5 sec
Loader.appLoader.hideLoader(); <-- do whatever you want
}
As a supplement to other answers: If you want to show some overlays, the flutter_portal may indeed be a better choice that is simpler to use.
Basically, it looks like:
PortalTarget(
// Declarative
portalFollower: MyAwesomeOverlayWidget(),
// Align anywhere you like. Now `portalFollower` floats at right of `child`
anchor: const Aligned(follower: Alignment.topLeft, target: Alignment.topRight),
child: MyChildWidget(),
)
Notice that it is declarative (not imperative as opposed to Overlay). Moreover, you get the bonus that the alignment is very easy, and the context is intuitive.
Disclaimer: I am the current owner of this library.
Have you tried to add a Navigator as a child/descendant of your Scaffold? As far as I remember, the default navigator is in the MaterialApp, which is above everything. When you add your own Navigator, your routing will happen under the Scaffold rather than above it in the tree.

How to overlay a widget on top of a flutter App?

I want a widget that will sit on top of the entire application. When I have tried to do this with Overlay.of(context).insert the overlay would later disappear after replacing that route. Is there a way I can have a widget on top of my app even if the screen is later popped?
Maybe a more optimal way exists, but as an option this is an example with two pages, local navigator and Overlay.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
final _navigatorKey = GlobalKey<NavigatorState>();
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: WillPopScope(
onWillPop: () async => !await _navigatorKey.currentState.maybePop(),
child: LayoutBuilder(
builder: (context, constraints) {
WidgetsBinding.instance.addPostFrameCallback((_) => _insertOverlay(context));
return Navigator(
key: _navigatorKey,
onGenerateRoute: (RouteSettings settings) {
switch (settings.name) {
case '/page2':
return MaterialPageRoute(builder: (_) => Page2());
default:
return MaterialPageRoute(builder: (_) => Page1(_navigatorKey));
}
},
);
},
),
),
);
}
void _insertOverlay(BuildContext context) {
return Overlay.of(context).insert(
OverlayEntry(builder: (context) {
final size = MediaQuery.of(context).size;
print(size.width);
return Positioned(
width: 56,
height: 56,
top: size.height - 72,
left: size.width - 72,
child: Material(
color: Colors.transparent,
child: GestureDetector(
onTap: () => print('ON TAP OVERLAY!'),
child: Container(
decoration: BoxDecoration(shape: BoxShape.circle, color: Colors.redAccent),
),
),
),
);
}),
);
}
}
class Page1 extends StatelessWidget {
final GlobalKey<NavigatorState> navigatorKey;
Page1(this.navigatorKey);
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.green[200],
appBar: AppBar(title: Text('Page1')),
body: Container(
alignment: Alignment.center,
child: RaisedButton(
child: Text('go to Page2'),
onPressed: () => navigatorKey.currentState.pushNamed('/page2'),
),
),
);
}
}
class Page2 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.yellow[200],
appBar: AppBar(title: Text('back to Page1')),
body: Container(
alignment: Alignment.center,
child: Text('Page 2'),
),
);
}
}
Screenshot (Null safe):
Full code:
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
Offset _offset = Offset.zero;
#override
Widget build(BuildContext context) {
return MaterialApp(
home: LoginPage(),
builder: (context, child) {
return Stack(
children: [
child!,
Positioned(
left: _offset.dx,
top: _offset.dy,
child: GestureDetector(
onPanUpdate: (d) => setState(() => _offset += Offset(d.delta.dx, d.delta.dy)),
child: FloatingActionButton(
onPressed: () {},
backgroundColor: Colors.black,
child: Icon(Icons.add),
),
),
),
],
);
},
);
}
}
LoginPage:
class LoginPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('LoginPage')),
body: Center(
child: ElevatedButton(
onPressed: () => Navigator.push(context, MaterialPageRoute(builder: (_) => HomePage())),
child: Text('Page2'),
),
),
);
}
}
HomePage:
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('HomePage')),
body: FlutterLogo(size: 300),
);
}
}
After reading the comments, find github-repo-link
created an overlay that will sit on top of everything
that can be called from anywhere.
just 4 easy steps to follow
flutterflutter-layout
STEP-1: in main.dart:
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Stack( <-- using stack
children: [
MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
),
OverlayView(),<-- my overlay widget
],
),
);
}
}
STEP-2: OverLayView.dart
class OverlayView extends StatelessWidget {
const OverlayView({
Key key,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return ValueListenableBuilder<bool>( <--- IMP , using ValueListenableBuilder for showing/removing overlay
valueListenable: Loader.appLoader.loaderShowingNotifier,
builder: (context, value, child) {
if (value) {
return yourOverLayWidget(); <-- your awesome overlay
} else {
return Container();
}
},
);
}
STEP-3: loder_controller.dart (to show/hide)
class Loader {
static final Loader appLoader = Loader(); <-- singleton
ValueNotifier<bool> loaderShowingNotifier = ValueNotifier(false);
ValueNotifier<String> loaderTextNotifier = ValueNotifier('error message');
void showLoader() { <-- using to show from anywhere
loaderShowingNotifier.value = true;
}
void hideLoader() { <-- using to hide from anywhere
loaderShowingNotifier.value = false;
}
void setText({String errorMessage}) { <-- using to change error message from anywhere
loaderTextNotifier.value = errorMessage;
}
void setImage() { <-- DIY
// same as that of setText //
}
}
FINAL STEP-4: show/hide loder
I'm showing it, on boilerplate code of increment method to show the loader
void _incrementCounter() async {
Loader.appLoader.showLoader(); <-- show loder
Loader.appLoader.setText(errorMessage: 'this is custom error message');<-- set custom message
await Future.delayed(Duration(seconds: 5)); <-- im hiding it after 5 sec
Loader.appLoader.hideLoader(); <-- do whatever you want
}
As a supplement to other answers: If you want to show some overlays, the flutter_portal may indeed be a better choice that is simpler to use.
Basically, it looks like:
PortalTarget(
// Declarative
portalFollower: MyAwesomeOverlayWidget(),
// Align anywhere you like. Now `portalFollower` floats at right of `child`
anchor: const Aligned(follower: Alignment.topLeft, target: Alignment.topRight),
child: MyChildWidget(),
)
Notice that it is declarative (not imperative as opposed to Overlay). Moreover, you get the bonus that the alignment is very easy, and the context is intuitive.
Disclaimer: I am the current owner of this library.
Have you tried to add a Navigator as a child/descendant of your Scaffold? As far as I remember, the default navigator is in the MaterialApp, which is above everything. When you add your own Navigator, your routing will happen under the Scaffold rather than above it in the tree.

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();
}
},
),