Handle Android system back button in Flutter app with nested navigators - flutter

I have a Flutter app composed of "sub-apps" with different theme colors.
In a sub-app, the AppBar's back button is working as expected: it navigates back to the previous sub-app page.
However the Android system's back button is NOT working as expected: it navigates directly to the root app.
The expected back navigation in the following example should be: B3 B2 B1 A3 A2 A1. But it's B3 A3 A2 A1.
In other words, I want the Android back button to work the same way as the Flutter back button.
The same problem happens with iOS "back swipe" gesture (iosPageTransition = true).
Please try the following code on an Android device or emulator and test with the system's back button.
Note that I use multiple MaterialApps to apply a color theme to all screens of a sub-app.
Also note that WillPopScope doesn't work since it's not triggered by the system's back button.
import 'package:flutter/material.dart';
void main() {
runApp(_AppA(_PageA(1)));
}
class _ColoredApp extends StatelessWidget {
final Color color;
final Widget home;
final iosPageTransition = false;
_ColoredApp(this.color, this.home);
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
colorScheme: ColorScheme.light(primary: color),
pageTransitionsTheme: iosPageTransition
? PageTransitionsTheme(
builders: Map.fromIterable(
TargetPlatform.values,
value: (_) => const CupertinoPageTransitionsBuilder(),
),
)
: null,
),
home: home,
);
}
}
class _AppA extends _ColoredApp {
_AppA(Widget home) : super(Colors.red, home);
}
class _AppB extends _ColoredApp {
_AppB(Widget home) : super(Colors.green, home);
}
class _PageA extends StatelessWidget {
final int number;
const _PageA(this.number);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('A$number')),
body: Center(
child: ElevatedButton(
child: Text('Next'),
onPressed: () {
Navigator.push(context, MaterialPageRoute(builder: (context) {
if (number > 2) return _AppB(_PageB(1));
return _PageA(number + 1);
}));
},
),
),
);
}
}
class _PageB extends StatelessWidget {
final int number;
const _PageB(this.number);
#override
Widget build(BuildContext context) {
var scaffold = Scaffold(
appBar: AppBar(
leading: number == 1 ? BackButton(onPressed: () => Navigator.of(context, rootNavigator: true).pop()) : null,
title: Text('B$number'),
),
body: Center(
child: ElevatedButton(
child: Text('Next'),
onPressed: () {
Navigator.push(context, MaterialPageRoute(builder: (context) {
return _PageB(number + 1);
}));
},
),
),
);
return scaffold;
}
}

First of all you can't set two MaterialApp in single flutter app its wrong way to use it, in your code it initialize 2 times.Just change your theme from the page you want to update,
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
final iosPageTransition = false;
#override
Widget build(BuildContext context) {
return MaterialApp(
home: _PageA(1),
);
}
}
class _PageA extends StatelessWidget {
final int number;
const _PageA(this.number);
#override
Widget build(BuildContext context) {
return
Theme(
data: ThemeData(
colorScheme: ColorScheme.light(primary: Colors.red),
),
child: Builder(
builder: (context) {
return Scaffold(
appBar: AppBar(title: Text('A$number')),
body: Center(
child: ElevatedButton(
child: Text('Next'),
onPressed: () {
Navigator.push(context, MaterialPageRoute(builder: (context) {
if (number > 2) return _PageB(1);
return _PageA(number + 1);
}));
},
),
),
);
}
),
);
}
}
class _PageB extends StatelessWidget {
final int number;
const _PageB(this.number);
#override
Widget build(BuildContext context) {
return
Theme(
data: ThemeData(
colorScheme: ColorScheme.light(primary: Colors.blue),
),
child: Builder(
builder: (context) {
return Scaffold(
appBar: AppBar(
leading: BackButton(onPressed: () => Navigator.pop(context)) ,
title: Text('B$number'),
),
body: Center(
child: ElevatedButton(
child: Text('Next'),
onPressed: () {
Navigator.push(context, MaterialPageRoute(builder: (context) {
return _PageB(number + 1);
}));
},
),
),
);
}
),
);
}
}

As Dharini said you cannot use 2 material apps, but if you really want to, then here is the workaround for you
Issue: When you try to B(n) it pops the B as it is new material app.
|
|_ A
|_ A1
|_ A2
|_ B
|_ B1
|_B2
Resolution: Pass the BuildContext and Route from B to A and remove route on back tap of icon or android back key.
Navigator.removeRoute(context, route);
Workaround
import 'package:flutter/material.dart';
void main() {
runApp(_AppA(_PageA(1)));
}
class _ColoredApp extends StatelessWidget {
final Color color;
final Widget home;
final iosPageTransition = false;
const _ColoredApp(this.color, this.home);
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
colorScheme: ColorScheme.light(primary: color),
pageTransitionsTheme: iosPageTransition
? PageTransitionsTheme(
builders: Map.fromIterable(
TargetPlatform.values,
value: (_) => const CupertinoPageTransitionsBuilder(),
),
)
: null,
),
home: home,
);
}
}
class _AppA extends _ColoredApp {
const _AppA(Widget home) : super(Colors.red, home);
}
class _AppB extends _ColoredApp {
const _AppB(Widget home) : super(Colors.green, home);
}
class _PageA extends StatelessWidget {
final int number;
_PageA(this.number);
final bRoutes = <BuildContext, Route>{};
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('A$number')),
body: Center(
child: ElevatedButton(
child: const Text('Next'),
onPressed: () {
Navigator.push(context, MaterialPageRoute(builder: (context) {
if (number > 2) {
return WillPopScope(
onWillPop: () async {
if (number > 2 && bRoutes.isNotEmpty) {
Navigator.removeRoute(bRoutes.entries.last.key, bRoutes.entries.last.value);
bRoutes.remove(bRoutes.entries.last.key);
return false;
} else {
return true;
}
},
child: _AppB(_PageB(1, bRoutes)),
);
}
return _PageA(number + 1);
}));
},
),
),
);
}
}
class _PageB extends StatelessWidget {
final int number;
final Map<BuildContext, Route> bRoutes;
const _PageB(this.number, this.bRoutes);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
leading: BackButton(onPressed: () {
if (bRoutes.isNotEmpty) {
final context = bRoutes.entries.last.key;
final route = bRoutes.entries.last.value;
bRoutes.remove(bRoutes.entries.last.key);
Navigator.removeRoute(context, route);
}
})
,
title: Text('B$number'),
),
body: Center(
child: ElevatedButton(
child: const Text('Next'),
onPressed: () {
final route = MaterialPageRoute(builder: (context) {
return _PageB(number + 1, bRoutes);
});
bRoutes[context] = route;
Navigator.push(context, route);
},
),
),
);
}
}

Related

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.

Flutter navigation

Can someone explain why not printing efeioi when it is back from pageE?
Page A
Navigator.pushNamed(context, PageB.ROUTE).then((onValue) {
print("efeioi");
});
Page B
Navigator.of(context)
.pushReplacementNamed(PageC.ROUTE, arguments: onValue);
PageC
Navigator.pushNamed(context, PageD.ROUTE,
arguments: onValue);
PageD
Navigator.pop(context); // back to Page C
Page C
Navigator.pushNamed(context, PageE.ROUTE,
arguments: onValue);
Page E
Navigator.of(context).popUntil(ModalRoute.withName(PageA.ROUTE));
I can't use Navigator.pop in Page E because it will back to Page C!
I have uploaded full code here
https://github.com/tony123S/navigation
As per your requirement I have implemented as below
main.dart
initState : this will be called when you navigate from E to A
refreshPage : it will not called as you already popped before returning to A Page
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: A(),
routes: <String, WidgetBuilder>{
'/A': (BuildContext context) => new A(),
'/B': (BuildContext context) => new B(),
'/C': (BuildContext context) => new C(),
'/D': (BuildContext context) => new D(),
'/E': (BuildContext context) => new E(),
},
);
}
}
class A extends StatefulWidget {
#override
_FirstRouteState createState() => _FirstRouteState();
}
class _FirstRouteState extends State<A> {
final String fromPage;
_FirstRouteState({Key key, #required this.fromPage});
#override
void initState() {
// TODO: implement initState
super.initState();
print("Called askdfjaksdfj");
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Page A'),
),
body: Center(
child: RaisedButton(
child: Text('Open B'),
onPressed: () {
// Navigate to second route when tapped.
// Navigator.push(
// context,
// MaterialPageRoute(builder: (context) => B()),
// );
Navigator.push(
context,
MaterialPageRoute(builder: (context) => B()),
).then((res) => refreshPage());
},
),
),
);
}
refreshPage() {
print("refresh page is called");
}
}
class B extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("B Page"),
),
body: Center(
child: RaisedButton(
onPressed: () {
// Navigate back to first route when tapped.
Navigator.of(context).pushNamed(
"/C",
);
},
child: Text('Go to C'),
),
),
);
}
}
class C extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("C Page"),
),
body: Center(
child: Column(
children: <Widget>[
RaisedButton(
onPressed: () {
// Navigate back to first route when tapped.
Navigator.pushNamed(
context,
"/D",
);
},
child: Text('Go to D'),
),
RaisedButton(
onPressed: () {
// Navigate back to first route when tapped.
Navigator.pushNamed(
context,
"/E",
);
},
child: Text('Go to E'),
),
],
),
),
);
}
}
class D extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("D Page"),
),
body: Center(
child: RaisedButton(
onPressed: () {
// Navigate back to first route when tapped.
Navigator.pop(context);
},
child: Text('Go back to C'),
),
),
);
}
}
class E extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("E Page"),
),
body: Center(
child: RaisedButton(
onPressed: () {
// Navigator.pop(context);
// Navigator.of(context).pushNamed("/A");
// Navigator.of(context).popUntil(ModalRoute.withName('/A'));
Navigator.of(context)
.pushNamedAndRemoveUntil('/A', (Route<dynamic> route) => false,);
},
child: Text('Go to A'),
),
),
);
}
}
Please run code for better understanding and reply if you found any difficulty

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 I can view FloatingActionButton on condition

I have list of orders orderList. If that isEmpty, FloatingActionButton is hide. In case orderList have products - FAB will be shown. My code:
bool statusFAB = false;
_getFABState(){
setState(() {
if(!orderList.isEmpty){
statusFAB = true;
}
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: _getFAB(),
backgroundColor: _kAppBackgroundColor,
body: Builder(
builder: _buildBody,
),
);
Widget _getFAB() {
if(statusFAB){
return FloatingActionButton(
backgroundColor: Colors.deepOrange[800],
child: Icon(Icons.add_shopping_cart),
onPressed: null);
}
}
It's not working, because condition work once, but state of orderList can be change anytime.
You don't need to store the statusFAB variable, you can just evaluate it on the fly. See updated sample below:
#override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: _getFAB(),
backgroundColor: _kAppBackgroundColor,
body: Builder(
builder: _buildBody,
),
);
Widget _getFAB() {
if (orderList.isEmpty) {
return Container();
} else {
return FloatingActionButton(
backgroundColor: Colors.deepOrange[800],
child: Icon(Icons.add_shopping_cart),
onPressed: null);
}
}
Well there is a shortcut which can be used with the ternary operator and can be used within Scaffold of a Stateful Widget as
floatingActionButton: orderList.isEmpty ? Container() : FloatingActionButton(...)
Unless you need a long and complicated function, this works fine. Even if you need a complicated function, then that function can be called only when the drawing was needed
floatingActionButton: orderList.isEmpty ? Container() : ComplicatedFn(...)
Widget ComplicatedFn() {
//.... Complicated Algo
return FloatingActionButton(...)
}
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> {
List<Product> orderList = List();
int counter = 0;
void getCount(){
setState(() {
counter = orderList.length;
});
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
body: Center(
child: Container(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
IconButton(
onPressed: (){
if(orderList.isNotEmpty)
orderList.removeLast();
getCount();
},
icon: Icon(Icons.remove),
color: Colors.red,
),
Text('$counter'),
IconButton(
onPressed: (){
orderList.add(Product('product'));
getCount();
print('product added');
},
icon: Icon(Icons.add),
color: Colors.blue,
)
],
),
),
),
floatingActionButton: _getFAB()
);
}
Widget _getFAB() {
if (orderList.isEmpty) {
return Container();
} else {
return FloatingActionButton(
backgroundColor: Colors.deepOrange[800],
child: Icon(Icons.shopping_cart),
onPressed: null);
} }
}
class Product {
String title;
Product(this.title);
}

Navigator operation requested with a context that does not include a Navigator

I'm trying to start a new screen within an onTap but I get the following error:
Navigator operation requested with a context that does not include a
Navigator.
The code I am using to navigate is:
onTap: () { Navigator.of(context).pushNamed('/settings'); },
I have set up a route in my app as follows:
routes: <String, WidgetBuilder>{
'/settings': (BuildContext context) => new SettingsPage(),
},
I've tried to copy the code using the stocks sample application. I've looked at the Navigator and Route documentation and can't figure out how the context can be made to include a Navigator. The context being used in the onTap is referenced from the parameter passed into the build method:
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
SettingsPage is a class as follows:
class SettingsPage extends Navigator {
Widget buildAppBar(BuildContext context) {
return new AppBar(
title: const Text('Settings')
);
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: buildAppBar(context),
);
}
}
TLDR: Wrap the widget which needs to access to Navigator into a Builder or extract that sub-tree into a class. And use the new BuildContext to access Navigator.
This error is unrelated to the destination. It happens because you used a context that doesn't contain a Navigator instance as parent.
How do I create a Navigator instance then ?
This is usually done by inserting in your widget tree a MaterialApp or WidgetsApp. Although you can do it manually by using Navigator directly but less recommended. Then, all children of such widget can access NavigatorState using Navigator.of(context).
Wait, I already have a MaterialApp/WidgetsApp !
That's most likely the case. But this error can still happens when you use a context that is a parent of MaterialApp/WidgetsApp.
This happens because when you do Navigator.of(context), it will start from the widget associated to the context used. And then go upward in the widget tree until it either find a Navigator or there's no more widget.
In the first case, everything is fine. In the second, it throws a
Navigator operation requested with a context that does not include a Navigator.
So, how do I fix it ?
First, let's reproduce this error :
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Center(
child: RaisedButton(
child: Text("Foo"),
onPressed: () => Navigator.pushNamed(context, "/"),
),
),
);
}
}
This example creates a button that attempts to go to '/' on click but will instead throw an exception.
Notice here that in the
onPressed: () => Navigator.pushNamed(context, "/"),
we used context passed by to build of MyApp.
The problem is, MyApp is actually a parent of MaterialApp. As it's the widget who instantiate MaterialApp! Therefore MyApp's BuildContext doesn't have a MaterialApp as parent!
To solve this problem, we need to use a different context.
In this situation, the easiest solution is to introduce a new widget as child of MaterialApp. And then use that widget's context to do the Navigator call.
There are a few ways to achieve this. You can extract home into a custom class :
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHome()
);
}
}
class MyHome extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Center(
child: RaisedButton(
child: Text("Foo"),
onPressed: () => Navigator.pushNamed(context, "/"),
),
);
}
}
Or you can use Builder :
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Builder(
builder: (context) => Center(
child: RaisedButton(
child: Text("Foo"),
onPressed: () => Navigator.pushNamed(context, "/"),
),
),
),
);
}
}
Hy guys, i have the same problem. This is occur for me. The solution what i found is very simple. Only what i did is in a simple code:
void main() {
runApp(MaterialApp(
home: YOURAPP() ,
),
);
}
I hope was useful.
Make sure your current parent widget not with same level with MaterialApp
Wrong Way
class HomeScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
centerTitle: true,
title: Text('Title'),
),
body: Center(
child: Padding(
padding: EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0),
child: RaisedButton(
onPressed: () {
//wrong way: use context in same level tree with MaterialApp
Navigator.push(context,
MaterialPageRoute(builder: (context) => ScanScreen()));
},
child: const Text('SCAN')),
)),
),
);
}
}
Right way
void main() => runApp(MaterialApp(
title: "App",
home: HomeScreen(),
));
class HomeScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
centerTitle: true,
title: Text('Title'),
),
body: Center(
child: Padding(
padding: EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0),
child: RaisedButton(
onPressed: () {
//right way: use context in below level tree with MaterialApp
Navigator.push(context,
MaterialPageRoute(builder: (context) => ScanScreen()));
},
child: const Text('SCAN')),
)),
);
}
}
Just like with a Scaffold you can use a GlobalKey. It doesn't need context.
final _navKey = GlobalKey<NavigatorState>();
void _navigateToLogin() {
_navKey.currentState.popUntil((r) => r.isFirst);
_navKey.currentState.pushReplacementNamed(LoginRoute.name);
}
#override
Widget build(BuildContext context) {
return MaterialApp(
navigatorKey: _navKey,
...
);
}
I set up this simple example for routing in a flutter app:
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',
home: new MyHomePage(),
routes: <String, WidgetBuilder>{
'/settings': (BuildContext context) => new SettingsPage(),
},
);
}
}
class MyHomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('TestProject'),
),
body: new Center(
child: new FlatButton(
child: const Text('Go to Settings'),
onPressed: () => Navigator.of(context).pushNamed('/settings')
)
)
);
}
}
class SettingsPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('SettingsPage'),
),
body: new Center(
child: new Text('Settings')
)
);
}
}
Note, that the SettingsPage extends StatelessWidget and not Navigator. I'm not able to reproduce your error.
Does this example help you in building your app? Let me know if I can help you with anything else.
You should rewrite your code in main.dart
FROM:
void main() => runApp(MyApp());
TO
void main() {
runApp(MaterialApp(
title: 'Your title',
home: MyApp(),));}
The point is to have the home property to be your first page
this worked for me, I hope it will help someone in the future
A complete and tested solution:
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:my-app/view/main-view.dart';
class SplashView extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
home: Builder(
builder: (context) => new _SplashContent(),
),
routes: <String, WidgetBuilder>{
'/main': (BuildContext context) => new MainView()}
);
}
}
class _SplashContent extends StatefulWidget{
#override
_SplashContentState createState() => new _SplashContentState();
}
class _SplashContentState extends State<_SplashContent>
with SingleTickerProviderStateMixin {
var _iconAnimationController;
var _iconAnimation;
startTimeout() async {
var duration = const Duration(seconds: 3);
return new Timer(duration, handleTimeout);
}
void handleTimeout() {
Navigator.pushReplacementNamed(context, "/main");
}
#override
void initState() {
super.initState();
_iconAnimationController = new AnimationController(
vsync: this, duration: new Duration(milliseconds: 2000));
_iconAnimation = new CurvedAnimation(
parent: _iconAnimationController, curve: Curves.easeIn);
_iconAnimation.addListener(() => this.setState(() {}));
_iconAnimationController.forward();
startTimeout();
}
#override
Widget build(BuildContext context) {
return new Center(
child: new Image(
image: new AssetImage("images/logo.png"),
width: _iconAnimation.value * 100,
height: _iconAnimation.value * 100,
)
);
}
}
As per this comment If your navigator is inside Material context navigator push will give this error. if you create a new widget and assign it to the material app home navigator will work.
This won't work
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
home: new Scaffold(
appBar: new AppBar(
title: new Text("Title"),
),
body: new Center(child: new Text("Click Me")),
floatingActionButton: new FloatingActionButton(
child: new Icon(Icons.add),
backgroundColor: Colors.orange,
onPressed: () {
print("Clicked");
Navigator.push(
context,
new MaterialPageRoute(builder: (context) => new AddTaskScreen()),
);
},
),
),
);
}
}
This will work
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
home: new HomeScreen());
}
}
class HomeScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text("Title"),
),
body: new Center(child: new Text("Click Me")),
floatingActionButton: new FloatingActionButton(
child: new Icon(Icons.add),
backgroundColor: Colors.orange,
onPressed: () {
print("Clicked");
Navigator.push(
context,
new MaterialPageRoute(builder: (context) => new AddTaskScreen()),
);
},
),
);
}
}
I was facing the same problem and solved by removing home from MaterialApp and use initialRoute instead.
return MaterialApp(
debugShowCheckedModeBanner: false,
initialRoute: '/',
routes: {
'/': (context) => MyApp(),
'/settings': (context) => SettingsPage(),
},
);
And
onTap: () => {
Navigator.pushNamed(context, "/settings")
},
It is Simple
instead using this normal code
`runApp(BasicBankingSystem());`
wrap it with MaterialApp
runApp(MaterialApp(home: BasicBankingSystem()));
It happens because the context on the widget that tries to navigate is still using the material widget.
The short answer for the solution is to :
extract your widget
that has navigation to new class so it has a different context when calling the navigation
When your screen is not navigated from other screen,you don't initially have access to the navigator,Because it is not instantiated yet.So in that case wrap your widget with builder and extract context from there.This worked for me.
builder: (context) => Center(
child: RaisedButton(
child: Text("Foo"),
onPressed: () => Navigator.pushNamed(context, "/"),
),
You ca use this plugin
https://pub.dev/packages/get/versions/2.0.2
in The MaterialApp assign property navigatorKey: Get.key,
MaterialApp(
navigatorKey: Get.key,
initialRoute: "/",
);
you can access Get.toNamed("Your route name");
Change your main function example:
void main() {
runApp(
MaterialApp(
title: 'Your title',
home: MyApp(),
)
);
}
use this
void main() {
runApp(MaterialApp(debugShowCheckedModeBanner: false, home: MyApp()),);
}
instead of this
void main() {runApp(MyApp());}
Wrap with materialapp
reproduce code
import 'dart:convert';
import 'package:flutter/material.dart';
void main() {
// reproduce code
runApp(MyApp());
// working switch //
// runApp(
//
// MaterialApp(debugShowCheckedModeBanner: false, home: MyApp()),);
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body:
Column(mainAxisAlignment: MainAxisAlignment.center, children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
height: 100,
width: 100,
child: ElevatedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => IntroPage(Isscar4: true)),
);
},
child: RichText(
text: TextSpan(
text: 'CAR',
style: TextStyle(
letterSpacing: 3,
color: Colors.white,
fontWeight: FontWeight.w400),
children: [
TextSpan(
text: '4',
style: TextStyle(
fontSize: 25,
color: Colors.red,
fontWeight: FontWeight.bold))
],
)),
),
),
],
),
SizedBox(
height: 10,
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
height: 100,
width: 100,
child: ElevatedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => IntroPage(Isscar4: false)),
);
},
child: RichText(
text: TextSpan(
text: 'BIKE',
style: TextStyle(
letterSpacing: 3,
color: Colors.white,
fontWeight: FontWeight.w400),
children: [
TextSpan(
text: '2',
style: TextStyle(
fontSize: 25,
color: Colors.red,
fontWeight: FontWeight.bold))
],
)),
),
),
],
)
])));
}
MaterialApp Swithwidget(istrue) {
return MaterialApp(
home: Scaffold(
body: IntroPage(
Isscar4: istrue,
),
),
);
}
}
class Hi extends StatelessWidget {
const Hi({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
child: Text("df"),
);
}
}
class IntroPage extends StatelessWidget {
final Isscar4;
IntroPage({
Key? key,
required this.Isscar4,
}) : super(key: key);
List<Widget> listPagesViewModel = [];
List<IntroModel> models = [];
#override
Widget build(BuildContext context) {
List<dynamic> intro = fetchIntroApi(Isscar4);
intro.forEach((element) {
var element2 = element as Map<String, dynamic>;
var cd = IntroModel.fromJson(element2);
models.add(cd);
});
models.forEach((element) {
listPagesViewModel.add(Text(""));
});
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Container(),
));
}
List fetchIntroApi(bool bool) {
var four = bool;
if (four) {
var data =
'[ {"name_Title": "title name1","description": "description1"}, {"name_Title": "title name2","description": "description2"}, {"name_Title": "title name3","description": "description3"}, {"name_Title": "title name4","description": "description4"} ]';
return json.decode(data);
} else {
var data =
'[ {"name_Title": "title name","description": "description1"}, {"name_Title": "title name2","description": "description2"}, {"name_Title": "title name3","description": "description3"} ]';
return json.decode(data);
}
}
}
class IntroModel {
String? nameTitle;
String? description;
IntroModel({this.nameTitle, this.description});
IntroModel.fromJson(Map<String, dynamic> json) {
nameTitle = json['name_Title'];
description = json['description'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['name_Title'] = this.nameTitle;
data['description'] = this.description;
return data;
}
}
class Splash extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Splash Screen',
theme: ThemeData(
primarySwatch: Colors.green,
),
home: MyState(),
debugShowCheckedModeBanner: false,
);
}
void main() {
runApp(Splash());
}
class MyState extends StatefulWidget{
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyState> {
#override
void initState() {
super.initState();
Timer(Duration(seconds: 3),
()=>Navigator.pushReplacement(context,
MaterialPageRoute(builder:
(context) =>
Login()
)
)
);
}
#override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center ,
children: [
Container(
child:
Image.asset("assets/images/herosplash.png"),
),
],
),
);
}
}
Builder(
builder: (context) {
return TextButton(
child: const Text('Bearbeiten'),
onPressed:(){
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const gotothesiteyouwant()),
);
});
}
),
Here, all you need is to make MaterialApp the parent of your Build. This is because the context that you've used to navigate to a different screen is finding a MaterialApp or a WidgetApp as a parent of the build.
And Since in your case, the situation is the opposite, therefore you need to modify it by either calling a new Stateless widget the parent of is the MaterialApp or by simply using a Builder as home: Builder in MaterialApp.
Hope this would help!