Reputation: 24130
I want to show alert dialog based on a condition. Not based on user interaction such as button press event.
If a flag is set in app state data alert dialog is shown otherwise its not.
Below is the sample alert dialog which I want to show
void _showDialog() {
// flutter defined function
showDialog(
context: context,
builder: (BuildContext context) {
// return object of type Dialog
return AlertDialog(
title: new Text("Alert Dialog title"),
content: new Text("Alert Dialog body"),
actions: <Widget>[
// usually buttons at the bottom of the dialog
new FlatButton(
child: new Text("Close"),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
},
);
}
I tried to call that method inside main screen widget's build method but it gives me error -
The context used to push or pop routes from the Navigator must be that of a widget that is a descendant of a Navigator widget.
E/flutter ( 3667): #0 Navigator.of.<anonymous closure> (package:flutter/src/widgets/navigator.dart:1179:9)
E/flutter ( 3667): #1 Navigator.of (package:flutter/src/widgets/navigator.dart:1186:6)
E/flutter ( 3667): #2 showDialog (package:flutter/src/material/dialog.dart:642:20)
Problem is I don't know from where I should call that _showDialog method?
Upvotes: 55
Views: 128185
Reputation: 11
for me i using this so each time the user click the page then user will show this disclaimer
void showDisclaimerPopup() {
showDialog(
context: context,
builder: (BuildContext context) => AlertDialog(
title: const Text(
'Q and A ?',
),
content: SizedBox(
width: double.maxFinite,
child: ListView.builder(
shrinkWrap: true,
itemCount: disclaimerPoints.length,
itemBuilder: (BuildContext context, int index) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4.0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'• ',
style: TextStyle(fontSize: 18),
),
Expanded(
child: Text(
disclaimerPoints[index],
style: const TextStyle(fontSize: 16),
),
),
],
),
);
},
),
),
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(25.0)),
),
actions: [
Center(
child: ElevatedButton(
style: ElevatedButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(25),
),
),
child: Text(
'okay',
style: txtSubheadBlack,
),
onPressed: () {
Navigator.pop(context);
},
),
),
],
),
); }
@override void initState() {
super.initState();
Future.delayed(Duration.zero, () => showDisclaimerPopup()); }
Upvotes: 0
Reputation: 5780
I would place it in initState
of a State
(of a StatefulWidget
).
Placing it in the build
method of a Stateless
widget is tempting, but that will trigger your alert multiple times.
In this example below, it displays an alert when the device is not connected to the Wifi, showing a [try again] button if it's not.
import 'package:flutter/material.dart';
import 'package:connectivity_plus/connectivity_plus.dart';
void main() => runApp(MaterialApp(title: "Wifi Check", home: MyPage()));
class MyPage extends StatefulWidget {
@override
_MyPageState createState() => _MyPageState();
}
class _MyPageState extends State<MyPage> {
bool _tryAgain = false;
@override
void initState() {
super.initState();
_checkWifi();
}
_checkWifi() async {
// the method below returns a Future
var connectivityResult = await (new Connectivity().checkConnectivity());
bool connectedToWifi = (connectivityResult == ConnectivityResult.wifi);
if (!connectedToWifi) {
_showAlert(context);
}
if (_tryAgain != !connectedToWifi) {
setState(() => _tryAgain = !connectedToWifi);
}
}
@override
Widget build(BuildContext context) {
var body = Container(
alignment: Alignment.center,
child: _tryAgain
? RaisedButton(
child: Text("Try again"),
onPressed: () {
_checkWifi();
})
: Text("This device is connected to Wifi"),
);
return Scaffold(
appBar: AppBar(title: Text("Wifi check")),
body: body
);
}
void _showAlert(BuildContext context) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text("Wifi"),
content: Text("Wifi not detected. Please activate it."),
)
);
}
}
Upvotes: 23
Reputation: 267514
Simply override initState
and call your _showDialog
method inside Future
or Timer
:
@override
void initState() {
super.initState();
// Use either of them.
Future(_showDialog);
Timer.run(_showDialog); // Requires import: 'dart:async'
}
Upvotes: 28
Reputation: 598
If you are using blocs then use a BlocListener
as suggested by @mirkancal in this answer: Flutter: bloc, how to show an alert dialog
Upvotes: 0
Reputation: 81
This is how I achieved this in a simple way:
Above the build method of your main screen (or any desired widget):
Future checkFirstRun(BuildContext context) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
bool isFirstRun = prefs.getBool('isFirstRun') ?? true;
if (isFirstRun) {
// Whatever you want to do, E.g. Navigator.push()
prefs.setBool('isFirstRun', false);
} else {
return null;
}
}
Then on your widget's initState:
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) => checkFirstRun(context));
}
This ensures the function is run after the widget is built.
Upvotes: 8
Reputation: 1111
I solved it using a package developed by Flutter Community. here https://pub.dev/packages/after_layout
Add this to your pubspec.yaml
after_layout: ^1.0.7+2
And try below example
import 'package:after_layout/after_layout.dart';
import 'package:flutter/material.dart';
class DialogDemo extends StatefulWidget {
@override
_DialogDemoState createState() => _DialogDemoState();
}
class _DialogDemoState extends State<DialogDemo>
with AfterLayoutMixin<DialogDemo> {
@override
void initState() {
super.initState();
}
@override
void afterFirstLayout(BuildContext context) {
_neverSatisfied();
}
@override
Widget build(BuildContext context) {
return SafeArea(
child: Container(
decoration: BoxDecoration(color: Colors.red),
),
);
}
Future<void> _neverSatisfied() async {
return showDialog<void>(
context: context,
barrierDismissible: false, // user must tap button!
builder: (BuildContext context) {
return AlertDialog(
title: Text('Rewind and remember'),
content: SingleChildScrollView(
child: ListBody(
children: <Widget>[
Text('You will never be satisfied.'),
Text('You\’re like me. I’m never satisfied.'),
],
),
),
actions: <Widget>[
FlatButton(
child: Text('Regret'),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
},
);
}
}
Upvotes: 1
Reputation: 21728
You have to wrap the content inside another Widget
(preferably Stateless).
Example:
Change From:
import 'package:flutter/material.dart';
void main() {
runApp(new MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Trial',
home: Scaffold(
appBar: AppBar(title: Text('List scroll')),
body: Container(
child: Text("Hello world"),
)));
}
}
to this:
import 'dart:async';
import 'package:flutter/material.dart';
void main() {
runApp(new MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Trial',
home: Scaffold(
appBar: AppBar(title: Text('List scroll')), body: new MyHome()));
}
}
class MyHome extends StatelessWidget { // Wrapper Widget
@override
Widget build(BuildContext context) {
Future.delayed(Duration.zero, () => showAlert(context));
return Container(
child: Text("Hello world"),
);
}
void showAlert(BuildContext context) {
showDialog(
context: context,
builder: (context) => AlertDialog(
content: Text("hi"),
));
}
}
Note: Refer here for wrapping show alert inside Future.delayed(Duration.zero,..)
Upvotes: 55