Related
I am using awesome_notification package for the notifications.
Actually, I have a custom bottom navigation bar and I am using screen[selectedIndex] to change the content of the body inside the scaffold. I guess that is what causing all the problem. Please suggest me a way out.
Whenever I am trying to navigate from my notification I am getting this error.
The code:
// ignore_for_file: use_key_in_widget_constructors
import 'package:awesome_notifications/awesome_notifications.dart';
import 'package:flutter/material.dart';
import 'package:notiminder/pages/done.dart';
import 'package:notiminder/pages/history.dart';
import 'package:notiminder/pages/reminder.dart';
class HomePage extends StatefulWidget {
#override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
#override
void initState() {
super.initState();
AwesomeNotifications().isNotificationAllowed().then((isAllowed) {
if (!isAllowed) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text(
'Allow Notifications',
),
content: const Text(
'Notiminder app would like to send you notifications',
),
actions: [
TextButton(
onPressed: () {
Navigator.pop(context);
},
child: const Text("Don't Allow"),
),
TextButton(
onPressed: () => AwesomeNotifications()
.requestPermissionToSendNotifications()
.then(
(_) => Navigator.pop(context),
),
child: const Text(
'Allow',
style: TextStyle(
fontStyle: FontStyle.italic,
color: Colors.teal,
),
),
),
],
),
);
}
});
AwesomeNotifications().createdStream.listen((notification) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Reminder created for you'),
),
);
});
AwesomeNotifications().actionStream.listen((event) {
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(
builder: (_) => HomePage(),
),
(route) => route.settings.name == screen[1].toString(),
// (route) => route.isFirst,
);
});
}
int selectedIndex = 0;
final screen = [
ReminderPage(),
DonePage(),
HistoryPage(),
];
#override
Widget build(BuildContext context) {
final Size size = MediaQuery.of(context).size;
return GestureDetector(
onTap: () {
final FocusScopeNode currentFocus = FocusScope.of(context);
if (!currentFocus.hasPrimaryFocus) {
currentFocus.unfocus();
}
},
child: SafeArea(
child: Scaffold(
// resizeToAvoidBottomInset: false,
appBar: AppBar(
title: const Text('Notiminder'),
centerTitle: true,
),
body: screen[selectedIndex],
bottomNavigationBar: Row(
children: [
SizedBox(
width: size.width,
height: 80,
child: Stack(
children: [
CustomPaint(
size: Size(size.width, 80),
painter: BNBCustomPainter(),
),
Center(
heightFactor: 0.6,
child: FloatingActionButton(
onPressed: () {
setState(() {
selectedIndex = 0;
});
},
child: const Icon(Icons.notifications, size: 30),
),
),
SizedBox(
width: size.width,
height: 80,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
buttomBarItems(
Icons.done_outline_rounded,
1,
),
Container(
width: size.width * 0.20,
),
buttomBarItems(
Icons.history_rounded,
2,
),
],
),
),
],
),
),
],
),
),
),
);
}
Specifically the notification route:
AwesomeNotifications().actionStream.listen((event) {
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(
builder: (_) => HomePage(),
),
(route) => route.settings.name == screen[1].toString(),
// (route) => route.isFirst,
);
});
}
I'm new to flutter and am building my first app. I have a small app with maybe 4 pages, there is a bottom bar which has icons on it. Some icons call functions such as copy text (which does not need its own page but is a function to be called) while others are complex and call a new page using Navigator.push().
I tried to achieve this using a custom Row() widget on each page, but it became very odd and I was rewriting a lot of functions. I felt there had to be a bottom menu bar, but couldn't find one.
I found BottomNavigationBar and was excited that it would achieve what I wanted. But I don't think its meant to be used as a menu, but rather as the name implies, a navigation bar.
I also want the navigation bar to change menu icons as it moves to a new page. The tutorial I am following uses it as follows:
class _HomeState extends State<Home> {
int _currentIndex = 0;
final List<Widget> _children = [
PlaceholderWidget(Colors.white),
PlaceholderWidget(Colors.deepOrange),
PlaceholderWidget(Colors.green)
];
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Flutter Bottem Navi '),
),
body: _children[_currentIndex],
bottomNavigationBar: BottomNavigationBar(
onTap: onTabTapped,
currentIndex: _currentIndex,
items: [
BottomNavigationBarItem(
icon: Icon(Icons.home),
title: Text('Home'),
),
BottomNavigationBarItem(
icon: Icon(Icons.search),
title: Text('Search'),
),
BottomNavigationBarItem(
icon: Icon(Icons.person),
title: Text('Profile')
)
],
),
);
}
The body: is changed based on the index selected. Can this be repurposed to act as a menu? to make use of Navigator.push or is my previous method of having a Row() widget on each page the best option?
You can copy paste run full code below
You can use BottomAppBar
code snippet
bottomNavigationBar: BottomAppBar(
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
IconButton(
icon: Icon(Icons.menu),
onPressed: () {
Navigator.push(context,
MaterialPageRoute(builder: (context) => SecondRoute()));
},
),
Builder(
builder: (context) => IconButton(
icon: Icon(Icons.title),
onPressed: () {
Scaffold.of(context).showSnackBar(SnackBar(
content: Text('Show Snackbar'),
duration: Duration(seconds: 3),
));
},
)),
working demo
full code
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(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Tasks - Bottom App Bar')),
body: ListView(
padding: const EdgeInsets.all(8),
children: <Widget>[
Container(
height: 50,
color: Colors.amber[600],
child: const Center(child: Text('Entry A')),
),
Container(
height: 50,
color: Colors.amber[500],
child: const Center(child: Text('Entry B')),
),
Container(
height: 50,
color: Colors.amber[100],
child: const Center(child: Text('Entry C')),
),
],
),
bottomNavigationBar: BottomAppBar(
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
IconButton(
icon: Icon(Icons.menu),
onPressed: () {
Navigator.push(context,
MaterialPageRoute(builder: (context) => SecondRoute()));
},
),
Builder(
builder: (context) => IconButton(
icon: Icon(Icons.title),
onPressed: () {
Scaffold.of(context).showSnackBar(SnackBar(
content: Text('Show Snackbar'),
duration: Duration(seconds: 3),
));
},
)),
IconButton(
icon: Icon(Icons.settings),
onPressed: () {},
),
IconButton(
icon: Icon(Icons.sync),
onPressed: () {},
),
],
),
),
);
}
}
class SecondRoute extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Second Route"),
),
body: Center(
child: RaisedButton(
onPressed: () {
Navigator.pop(context);
},
child: Text('Go back!'),
),
),
);
}
}
I am new to flutter.
In my real problem, my client is in places where it is very frequent that the internet is very slow, so sometimes an attempt is made to make a web request and this may take time, so the user leaves the screen before the web request is completed. Sometimes my app after completing a web request generates a dialog. So here is where my problem lies, the user is trying to make a web request and while it is done, they leave the screen and then the dialog is generated.
I am trying to simulate this problem with a delay that later generates the dialog.
I am not thinking of any strategy to end the web request, what I want is to find a way that once I leave the screen, causes the dialog not to be generated something like a dispose
I made an example where I have 2 screens. On the second screen a dialog is generated with a delay of 5 seconds when the button is clicked. If I navigate to another screen before the dialog is opened I get an error. I assume this occurs because the view was destroyed and therefore the dialog cannot be opened.
What can I do to avoid the error when the dialog is generated after being in another view? if I am in another view I DO NOT WANT the dialog to be generated.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
print("main");
return MaterialApp(title: 'Provider Example', initialRoute: '/', routes: {
'/': (context) => Home(),
'home': (context) => Home(),
'dialogpage': (context) => Dialogpage(),
});
}
}
class Home extends StatelessWidget {
Home() {
print("home");
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
title: const Text('home'),
actions: <Widget>[
IconButton(
icon: const Icon(Icons.add_alert),
tooltip: 'Show Snackbar',
onPressed: () {
Navigator.pushNamed(context, "dialogpage");
},
),
],
),
body: const Center(
child: Text(
'home',
style: TextStyle(fontSize: 24),
),
),
);
}
}
class Dialogpage extends StatelessWidget {
Dialogpage() {
print("dialogpage");
}
dialog(BuildContext context) {
Future.delayed(const Duration(seconds: 5), () {
showDialog(
context: context,
barrierDismissible: false,
builder: (context) {
return AlertDialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20.0)),
title: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(19.0),
topRight: Radius.circular(19.0),
),
),
padding: EdgeInsets.symmetric(vertical: 10, horizontal: 5),
child: Text(
'Error',
style: TextStyle(color: Colors.white),
),
),
content: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Container(
margin: EdgeInsets.only(top: 20.0, bottom: 20.0),
child: Icon(
Icons.error,
size: 50,
),
),
Text("dialog"),
],
),
titlePadding: EdgeInsets.all(0),
actions: <Widget>[
FlatButton(
child: Text('Aceptar'),
onPressed: () {
return Navigator.of(context).pop();
}),
],
);
});
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('dialog'),
),
body: Center(
child: RaisedButton(
child: Text("show dialog"),
onPressed: () {
dialog(context);
}),
),
);
}
}
use Globalkey in scaffold in then check the context in dialog method is it != null
then run dialog otherwise don't...
GlobalKey _scafolldKey = GlobalKey<ScaffoldState>();
#override
Widget build(BuildContext context) {
return Scaffold(
key: _scafolldKey,
appBar: AppBar(
title: const Text('dialog'),),
body: Center(
child: RaisedButton(
child: Text("show dialog"),
onPressed: () {
dialog(context);
}),
),
);
}
}
dialog(BuildContext context) {
Future.delayed(const Duration(seconds: 2), () {
if(_scafolldKey.currentContext !=null){
showDialog();
}
});
}
Instead of Future.delayed, you should use Timer, which can be cancelled in onDispose method.
Working solution:
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
print("main");
return MaterialApp(
title: 'Provider Example',
initialRoute: '/',
routes: {
'/': (context) => Home(),
'home': (context) => Home(),
'dialogpage': (context) => Dialogpage(),
},
);
}
}
class Home extends StatelessWidget {
Home() {
print("home");
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('home'),
actions: <Widget>[
IconButton(
icon: const Icon(Icons.add_alert),
tooltip: 'Show Snackbar',
onPressed: () {
Navigator.pushNamed(context, "dialogpage");
},
),
],
),
body: const Center(
child: Text(
'home',
style: TextStyle(fontSize: 24),
),
),
);
}
}
class Dialogpage extends StatefulWidget {
#override
_DialogpageState createState() => _DialogpageState();
}
class _DialogpageState extends State<Dialogpage> {
Timer _timer;
#override
void dispose() {
_timer?.cancel();
super.dispose();
}
dialog(BuildContext context) {
_timer = Timer(
const Duration(seconds: 3),
() {
showDialog(
context: context,
barrierDismissible: false,
builder: (context) {
return AlertDialog(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20.0)),
title: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(19.0),
topRight: Radius.circular(19.0),
),
),
padding: EdgeInsets.symmetric(vertical: 10, horizontal: 5),
child: Text(
'Error',
style: TextStyle(color: Colors.white),
),
),
content: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Container(
margin: EdgeInsets.only(top: 20.0, bottom: 20.0),
child: Icon(
Icons.error,
size: 50,
),
),
Text("dialog"),
],
),
titlePadding: EdgeInsets.all(0),
actions: <Widget>[
FlatButton(
child: Text('Aceptar'),
onPressed: () {
return Navigator.of(context).pop();
},
),
],
);
},
);
},
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('dialog'),
),
body: Center(
child: RaisedButton(
child: Text("show dialog"),
onPressed: () {
dialog(context);
},
),
),
);
}
}
Try this code
class Dialogpage extends StatelessWidget {
...
Timer t;
dialog(BuildContext context) {
t = Timer(Duration(seconds: 5), () {
showDialog(...);
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('dialog'),
leading: IconButton(
icon: Icon(Icons.arrow_back, color: Colors.black),
onPressed: () {
t?.cancel();
Navigator.of(context).pop();
},
),
),
body: Center(
child: RaisedButton(
child: Text("show dialog"),
onPressed: () {
dialog(context);
}),
),
);
}
}
Hope it helps.
import 'package:flutter/material.dart';
main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(primarySwatch: Colors.blue),
home: Scaffold(
appBar: AppBar(
title: Text("Page 1"),
),
body: Center(
child: Column(
children: <Widget>[
MaterialButton(
child: Text("Next Page"),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => nextPage()),
);
},
color: Colors.red,
)
],
),
),
),
);
}
}
class nextPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(primarySwatch: Colors.green),
home: Scaffold(
appBar: AppBar(
title: Text("Page 2"),
),
body: Center(
child: Column(
children: <Widget>[
MaterialButton(
child: Text("Go Back!"),
onPressed: () {
Navigator.pop(context);
},
color: Colors.red,
)
],
),
),
),
);
}
}
This is the code i am using to navigate to a new page but i am facing a problem even though compiler is not throwing any error
This is the error message i am getting
Another exception was thrown: Navigator operation requested with a context that does not include a Navigator.
I am getting this error when i am clicking on the button
You need home : Builder( builder: (context) => and Second Page remove MaterialApp
MaterialApp(
home: Builder(
builder: (context) => Scaffold(
appBar: AppBar(
title: Text("Page 1"),
),
body: Center(
child: Column(
children: <Widget>[
MaterialButton(
child: Text("Next Page"),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => nextPage()),
);
},
color: Colors.red,
)
],
),
),
),
),
full code
import 'package:flutter/material.dart';
main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Builder(
builder: (context) => Scaffold(
appBar: AppBar(
title: Text("Page 1"),
),
body: Center(
child: Column(
children: <Widget>[
MaterialButton(
child: Text("Next Page"),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => nextPage()),
);
},
color: Colors.red,
)
],
),
),
),
),
);
}
}
class nextPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Page 2"),
),
body: Center(
child: Column(
children: <Widget>[
MaterialButton(
child: Text("Go Back!"),
onPressed: () {
Navigator.pop(context);
},
color: Colors.red,
)
],
),
),
);
}
}
try this
import 'package:flutter/material.dart';
main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(primarySwatch: Colors.blue),
home: Scaffold(
appBar: AppBar(
title: Text("Page 1"),
),
body: Center(
child: Column(
children: <Widget>[
MaterialButton(
child: Text("Next Page"),
onPressed: () {
Navigator.of(context).pushNamed(NextPage.routeName),
);
},
color: Colors.red,
)
],
),
),
),
);
}
}
class NextPage extends StatelessWidget {
static const routeName = '/next-page';
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Page 2"),
),
body: Center(
child: Column(
children: <Widget>[
MaterialButton(
child: Text("Go Back!"),
onPressed: () {
Navigator.of(context).pop();
},
color: Colors.red,
)
],
),
),
);
}
}
You cant use the Navigator (in MyApp class) with the context you got before creating MaterialApp(). Wrap your MaterialButton with a Builder like so:
children: <Widget>[
Builder(
builder: (BuildContext innerContext) {
MaterialButton(
child: Text("Go Back!"),
onPressed: () {
Navigator.pop(innerContext);
},
color: Colors.red,
)
}
);
],
Besides this, some more issues:
i would also think about using onWillPop property for all those android use cases.
never ever use another MaterialApp() while navigating your app, use Scaffold() for example for childs.
You can also just move the MaterialApp Widget to the main()
main() => runApp(
MaterialApp(
theme: ThemeData(primarySwatch: Colors.blue),
home: MyApp(),
),
);
and remove it from the other classes.
You only need one MaterialApp widget for your app. It's like "root" widget which configures the top-level Navigator, as you can see in documentation.
I want to have the top half of by screen appear static when navigating between pages in Flutter.
To try to make this happen I put use the Hero widget and use it on a column that contains an AppBar and some other content that I want to appear static when pushing a new page.
The App Bar itself remains static but the back arrow disappears when the animation starts and reappears when the animation is done.
How can I have the back arrow remain visible the entire time while the rest of the page is animating into place?
class FirstScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
Hero(
tag: 'top',
child: Column(
children: <Widget>[
AppBar(
title: Text('First'),
backgroundColor: Color.fromARGB(255, 50, 64, 182),
),
Container(
height: 80.0,
)
],
),
),
RaisedButton(
child: Text('Next'),
onPressed: () {
Navigator.pushNamed(context, '/second');
},
),
],
),
);
}
}
class SecondScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
Hero(
tag: 'top',
child: Column(
children: <Widget>[
AppBar(
title: Text('Second'),
),
Container(
height: 80.0,
// color: Colors.green,
),
],
),
),
RaisedButton(
child: Text('Back'),
onPressed: () {
Navigator.pop(context);
},
),
],
),
);
}
}
Things weren't quite set up right in your code. It should go Scaffold/Hero/your content. I've also used this simple fading page route when performing the navigation:
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('First'),
leading: Icon(null),
backgroundColor: Color.fromARGB(255, 50, 64, 182)),
body: Hero(
tag: 'top',
child: Column(
children: <Widget>[
Container(height: 80.0),
RaisedButton(
child: Text('Next'),
onPressed: () {
Navigator.push(context, MyCustomRoute(builder: (context) {
return SecondScreen();
}));
},
),
],
),
),
);
}
}
class SecondScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Second'),
leading: IconButton(icon: Icon(Icons.arrow_back), onPressed: () {
Navigator.pop(context);
},),
backgroundColor: Color.fromARGB(255, 50, 64, 182)),
body: Hero(
tag: 'top',
child: Column(
children: <Widget>[
Container(height: 80.0),
RaisedButton(
child: Text('Back'),
onPressed: () {
Navigator.pop(context);
},
),
],
),
),
);
}
}
class MyCustomRoute<T> extends MaterialPageRoute<T> {
MyCustomRoute({ WidgetBuilder builder, RouteSettings settings })
: super(builder: builder, settings: settings);
#override
Widget buildTransitions(BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child) {
if (settings.isInitialRoute)
return child;
// Fades between routes. (If you don't want any animation,
// just return child.)
return new FadeTransition(opacity: animation, child: child);
}
}
You could do automaticallyImplyLeading: false and then do
leading: IconButton(
icon: Icon(Icons.arrow_back),
onPressed: () => Navigator.of(context).pop(),
),
I have it done this way, by adding automaticallyImplyLeading: true,
Hope this solves your problem!
appBar: AppBar(
automaticallyImplyLeading: true,
),