A dialog has a delay to open, I get an error when navigating to another view before the dialog is generated. How can I make it not be generated? - flutter

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.

Related

The device's exit button does not exit the app in flutter

i added willpopscope with back function In this project i added the exit code, but the app didn't end; instead it went to the homepage and I made this initial screen to start the project with all the details running in the homepage screen.
I want to exit from device but its not working Can somebody tell me what the problem is?
#override
Widget build(BuildContext context) {
Future<bool> _back() {
return showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text("Do you want to exit this app?"),
actions: <Widget>[
TextButton(
child: const Text('ok',style: TextStyle(fontSize: 15,color: Colors.indigo),),
onPressed: () {
Navigator.of(context).pop(true);
},
),
TextButton(
child: const Text('no',style: TextStyle(fontSize: 15,color: Colors.indigo),),
onPressed: () {
Navigator.of(context).pop(false);
},
),
],
);
}
).then((value) => value ?? false);
}
return WillPopScope(
onWillPop: _back,
child: SafeArea(
child: MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Container(
child: Padding(
padding: const EdgeInsets.all(80.0),
child: Column(
children: <Widget>[
Container(
Row(
children: <Widget>[
SizedBox(
height: 80,
width: 150,
child: OutlinedButton(
style: OutlinedButton.styleFrom(
side: BorderSide(
width: 3.0, color: Colors.indigo.shade900),
backgroundColor: Colors.transparent,
shape: const StadiumBorder()),
child: Text(
"LETS START",
),
onPressed: () {
Navigator.of(context).push(MaterialPageRoute( builder: (context) => homepage(langname: '',))); },
),
),
],
),
],
),
)),
)),
),
);
}
}
You can do Like this:
var currentBackPressTime;
Future<bool> onWillPop() {
DateTime now = DateTime.now();
if (currentBackPressTime == null ||
now.difference(currentBackPressTime) > Duration(seconds: 2)) {
currentBackPressTime = now;
Fluttertoast.showToast(msg: "Press Again to Exit!");
return Future.value(false);
} else {
exit(0);
// SystemChannels.platform.invokeMethod('SystemNavigator.pop');
return Future.value(false);
}
}
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: onWillPop,
child: Scaffold(
--------------

Why Navigator.of(context).pop() back to previous page instead close EndDrawer?

I have 2 page, page1 and page2. page1 navigate to page2. page2 has endDrawer inside scaffold, but i must wrap scaffold with materialApp. When endDrawer open and i call Navigator.of(context).pop() or Navigator.pop(context), it will back to page1 instead close endDrawer. How to close the endDrawer and not back to page1? all i know to close drawer is use pop
I think you are using wrong context:
Check the code below which can complete your task:
import 'package:flutter/material.dart';
void main() {
runApp(const MaterialApp(
title: 'Navigation Basics',
home: FirstRoute(),
));
}
class FirstRoute extends StatelessWidget {
const FirstRoute({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('First Route'),
),
body: Center(
child: ElevatedButton(
child: const Text('Open route'),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const SecondRoute()),
);
},
),
),
);
}
}
class SecondRoute extends StatelessWidget {
const SecondRoute({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text("Second Route"),
),
endDrawer: Builder(
builder: (context) {
return Container(
width: 100,
color: Colors.black,
alignment: Alignment.center,
child: GestureDetector(
child: const Text(
"close drawer",
style: TextStyle(
color: Colors.white,
),
),
onTap: () {
Navigator.pop(context);
},
),
);
},
),
body: Builder(builder: (context) {
return Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
ElevatedButton(
onPressed: () {
Scaffold.of(context).openEndDrawer();
},
child: const Text('open drawer'),
),
ElevatedButton(
onPressed: () {
Navigator.pop(context);
},
child: const Text('Go back!'),
),
],
),
);
}),
),
);
}
}

How to call alertdialog from another file? using flutter

how do I call the alert dialog from another dart file, when the user clicks the button in addstudents.dart, I want to make the alert dialog in another file just in case it can be reused? In my addstudents.dart i have this Container, please see the code below, thanks.
addstudents.dart
Container(
padding: EdgeInsets.only(top: 10),
width: (globals.screenWidth * 0.48),
height: (globals.screenHeight * 0.10),
decoration:
BoxDecoration(borderRadius: BorderRadius.circular(10)),
child: RaisedButton(
shape: new RoundedRectangleBorder(
borderRadius: new BorderRadius.circular(10.0),
),
onPressed: () {
// calling another file dart
},
color: Colors.green,
child: RichText(
text: TextSpan(
text: "Confirmed!",
style: TextStyle(
fontWeight: FontWeight.bold,
fontFamily: "Nunito",
color: Colors.white,
fontSize: globals.fontsize_19)),
),
),
),
this is my alertdialog.dart
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter',
home: Scaffold(
appBar: AppBar(
title: Text('Flutter'),
),
body: MyLayout()),
);
}
}
class MyLayout extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: RaisedButton(
child: Text('Show alert'),
onPressed: () {
showAlertDialog(context);
},
),
);
}
}
showAlertDialog(BuildContext context) {
Widget okButton = FlatButton(
child: Text("OK"),
onPressed: () {},
);
AlertDialog alert = AlertDialog(
title: Text("My title"),
content: Text("This is my message."),
actions: [
okButton,
],
);
showDialog(
context: context,
builder: (BuildContext context) {
return alert;
},
);
}
Dunno if this is the best practice but here's what I'm doing
class Alerts {
static showAlertDialog(BuildContext context) {
Widget okButton = FlatButton(
child: Text("OK"),
onPressed: () {},
);
AlertDialog alert = AlertDialog(
title: Text("My title"),
content: Text("This is my message."),
actions: [
okButton,
],
);
// show the dialog
showDialog(
context: context,
builder: (BuildContext context) {
return alert;
},
);
}
}
I call them using Alerts.showAlertDialog(context);

How do I navigator.push a showModalBottomSheet for flutter?

How do I navigator.push a showModalBottomSheet for flutter?
class SetRepeatButton extends StatefulWidget {
#override
_SetRepeatButtonState createState() => _SetRepeatButtonState();
}
class _SetRepeatButtonState extends State<SetRepeatButton> {
void _repeatMenu(BuildContext context) {
showModalBottomSheet(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20.0),
),
backgroundColor: Colors.white,
context: context,
builder: (builder) => Padding(
padding: const EdgeInsets.all(20.0),
child: Container(
height: MediaQuery.of(context).copyWith().size.height / 3,
child: Column(
children: <Widget>[
],
),
),
)
);
}
#override
Widget build(BuildContext context) {
return Builder(
builder: (builder) => FlatButton(
color: Colors.white,
textColor: Colors.grey,
disabledColor: Colors.grey,
disabledTextColor: Colors.black,
onPressed: () async {
final result = await Navigator.push(
context,
MaterialPageRoute(builder: (context) => _repeatMenu()),
);
},
child: Row(
),
),
);
}
}
what do you want to achieve?
if just to show the bottomsheet, just call the function in on press.
but it will be a little different if you want to catch the variable passed from the bottomsheet to the main context
you can try like this to show bottomsheet
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: SetRepeatButton(),
);
}
}
class SetRepeatButton extends StatefulWidget {
#override
_SetRepeatButtonState createState() => _SetRepeatButtonState();
}
class _SetRepeatButtonState extends State<SetRepeatButton> {
void _repeatMenu(BuildContext context) {
showModalBottomSheet(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20.0),
),
backgroundColor: Colors.white,
context: context,
builder: (builder) => Padding(
padding: const EdgeInsets.all(20.0),
child: Container(
height: MediaQuery.of(context).copyWith().size.height / 3,
child: Column(
children: <Widget>[
Text('Text'),
],
),
),
));
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Text'),
),
body: Center(
child: RaisedButton(
color: Colors.white,
textColor: Colors.grey,
disabledColor: Colors.grey,
disabledTextColor: Colors.black,
onPressed: () async {
_repeatMenu(context);
},
child: Text('Text'),
),
),
);
}
}

Scaffold Drawer to showModalBottomSheet

At the homescreen of myApp() I have a stateless widget, it contains a MaterialApp and a Scaffold. Scaffold have a property of drawer and I passed I created a drawer, and one of the item in my drawer needs to open the showModalBottomSheet while closing the drawer. How can I achieve this? I've tried passing the context itself, and as globalKey.currentContext (after GlobalKey<ScaffoldState> globalKey = GlobalKey();) but the drawer sometimes closes, other time gives me a NoMethodFoundException (or something like that)
In short, how to have a Scaffold drawer that have one of the item, when tapped closes the drawer and showModalBottomSheet?
Current code:
class Timeline extends StatelessWidget {
#override
Widget build(BuildContext context) {
GlobalKey<ScaffoldState> homeScaffoldKey = GlobalKey();
return MaterialApp(
title: "Test",
theme: ThemeData(
appBarTheme: AppBarTheme(iconTheme: IconThemeData(color: Colors.black)),
),
home: Scaffold(
key: homeScaffoldKey,
drawer: showDrawer(homeScaffoldKey.currentContext),
backgroundColor: Colors.grey[100],
body: Stack(
children: <Widget>[
HomePageView(),
AppBar(
elevation: 0,
backgroundColor: Colors.transparent,
),
],
),
),
);
}
}
Drawer showDrawer(BuildContext context) {
void showCalendarsModalBottom() {
showModalBottomSheet(
context: context,
builder: (BuildContext builder) {
return ListView.builder(
itemCount: repo.calendars.length,
itemBuilder: (builder, index) {
return StatefulBuilder(
builder: (builder, StateSetter setState) => ListTile(
leading: Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Checkbox(
value: repo.getIsEnabledCal(repo.getCal(index)),
onChanged: (value) {
setState(() {
repo.toggleCalendar(repo.getCal(index));
});
},
),
Container(
height: 14,
width: 14,
margin: EdgeInsets.only(left: 2, right: 6),
decoration: BoxDecoration(
color: Colors.redAccent,
shape: BoxShape.circle,
),
),
Text(
repo.getCal(index).name,
style: TextStyle(
fontSize: 16,
),
),
],
),
onTap: () {
setState(() {
repo.toggleCalendar(repo.getCal(index));
});
},
),
);
},
);
},
);
}
return Drawer(
child: ListView(
children: <Widget>[
DrawerHeader(
child: Align(
child: Text('Timeline', textScaleFactor: 2),
alignment: Alignment.bottomLeft,
),
),
ListTile(
title: Text('Dark Mode'),
onTap: () => Navigator.pop(context),
),
ListTile(
title: Text('Calenders'),
onTap: () {
Navigator.pop(context);
showCalendarsModalBottom();
},
)
],
),
);
}
Updated working code based on your code snippet:
You'll need to have statefulwidget that will help to pass the context from drawer to bottomsheet and pass the context as an argument in showCalendarModalBottomSheet() method.
void main() {
runApp(new MaterialApp(home: Timeline(), debugShowCheckedModeBanner: false));
}
class Timeline extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: "Test",
theme: ThemeData(
appBarTheme: AppBarTheme(iconTheme: IconThemeData(color: Colors.black)),
),
home: MyHomePage()
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
drawer: AppDrawer(),
backgroundColor: Colors.grey[100],
body: Stack(
children: <Widget>[
//HomePageView(),
AppBar(
elevation: 0,
backgroundColor: Colors.transparent,
)
],
)
);
}
Widget AppDrawer() {
return Drawer(
child: ListView(
children: <Widget>[
DrawerHeader(
child: Align(
child: Text('Timeline', textScaleFactor: 2),
alignment: Alignment.bottomLeft,
),
),
ListTile(
title: Text('Dark Mode'),
onTap: () => Navigator.pop(context),
),
ListTile(
title: Text('Calenders'),
onTap: () {
Navigator.of(context).pop();
showCalendarsModalBottom(context);
},
)
],
),
);
}
Future<Null> showCalendarsModalBottom(context) {
return showModalBottomSheet(context: context, builder: (context) => Container(
color: Colors.red,
// your code here
));
}
}
And the output is: When app drawer menu Calendar is tapped, it closes and opens the bottomsheet seamlessly. If you tap on app drawer again and repeat steps, you see smooth transition between drawer and bottomsheet. Hope this answers your question.