I try to build cusom confirmation dialog. I tried many different options, but none of them worked for me as it should
Now I have stopped at this one:
Future<bool?> showConfirmationDialog(BuildContext context, String action, {String title = 'Confirmation'}) {
bool result = false;
var confirmDialog = AlertDialog(
title: Text(title),
content: SizedBox(
height: 50,
child: Text(
action,
),
),
actions: [
TextButton(
child: const Text("OK"),
onPressed: () {
Navigator.pop(context, true);
},
),
TextButton(
child: const Text("Cancel"),
onPressed: () {
Navigator.of(context).pop(false);
},
)
],
);
return showDialog<bool>(
context: context,
builder: (BuildContext context) {
return confirmDialog;
},
);
}
but I always get null in result:
confirmDismiss: (DismissDirection direction) async {
var result = await showConfirmationDialog(context, 'Are you sure?');
// here result is null always
if (result == true){
print('yes');
return true;
}
else if(result == false){
print('no');
return false;
}
}
How to make a confirmation dialog box in flutter correctly?
Related
I have a problem when I try to delete the item and it doesn't accept to delete the item. I am using ConfirmDismiss.
Error:
Unhandled Exception: type 'Null' is not a subtype of type 'bool'
_DisMissState.build..
(package:advanced_part_2/dismissible.dart:84:30)
_DismissibleState._handleDismissStatusChanged
(package:flutter/src/widgets/dismissible.dart:498:11)
Code:
confirmDismiss: (DismissDirection dir) async {
if (dir == DismissDirection.startToEnd) {
// AlertDialog ad =
final bool res = await showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
content: Text('Are You Sure you want to delete'),
actions:<Widget> [
ElevatedButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text(
'cancel',
)),
ElevatedButton(
onPressed: () {
setState(() {
genList.removeAt(index);
});
Navigator.of(context).pop();
},
child: Text(
'Delete',
style: TextStyle(color: Colors.red),
))
],
);
},
);
return res;
} else {
return true;
}
},
It is because when your calling the following code:
final bool res = await showDialog(...);
it will return a null value, see https://api.flutter.dev/flutter/material/showDialog.html
And, you also not giving any return value when clicking the button:
ElevatedButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text(
'cancel',
)),
You can't use Navigator.of(context).pop();. Instead, you need to use either:
Navigator.pop(context, false);
or
Navigator.pop(context, true);
Then, after that, you need to handle the nullable returned value from show dialog by giving a default value when null. Something like this:
bool res = await showDialog(...);
if(res == null) res = false;
showDialog can return null when the dialog is dismissed (e.g. user clicks outside the dialog) so you've to account for that by changing the type to:
final bool? res = await showDialog(/* your code */);
then in your logic below, you have to check for null:
if(res == null) {
// handle dismiss
} else if (res == false) {
// handle cancel
} else {
// handle confirm/true
}
As #Er1 mentioned in the comment, you'll also need to pass true or false when popping the dialog:
Navigator.of(context).pop(true);
// or
Navigator.of(context).pop(false);
The above still applies since barrierDismissible is true by default.
final bool res = await showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return AlertDialog(
content: Text('Are You Sure you want to delete'),
actions:<Widget> [
ElevatedButton(
onPressed: () {
Navigator.of(context).pop(false);
},
child: Text(
'cancel',
)),
ElevatedButton(
onPressed: () {
setState(() {
genList.removeAt(index);
});
Navigator.of(context).pop(true);
},
child: Text(
'Delete',
style: TextStyle(color: Colors.red),
))
],
);
},
);
Add false and true to the pop() to make the showDialog return a boolean.
But keep in mind if you don't set barrierDismissible in showDialog to false you can get null returned if you tap outside of the dialog.
Summarize the Problem:
My application sends information to a server and the server responds with success or failure. I am having trouble updating an AlertDialog with the result of network communication. I am sending multiple items to the server when the user saves their settings and I need to track if all the settings were successfully sent. So when all the settings were successfully sent, I can update the AlertDialog with success. The issue I am seeing with current implementation is it takes me two times to activate the TextButton before I see the correct message. AlertDialog should show the correct message after the first TextButton press labeled as "save". One of the cases I need to solve is if the server is down and the app's connection request times out. Then I need to use something like a CircularProgressIndicator so the user can wait while network communication is being done.
The variable successPrompt is what contains the message with the result of the network transaction. This needs to be updated to the correct message by the time the AlertDialog pops up.
2: What I've tried:
I've tried using FutureBuilder to create the AlertDialog but I got the same result. I need a way to bring up the AlertDialog when I know the result of the network transaction. What happens is the AlertDialog will be brought up but the application is still trying to connect to the server in the background. I want to bring up the widget once this step is done and the socket is closed.
3: Here's the relevant code. Please don't mind the debug prints and commented out code.
import 'package:flutter/material.dart';
import 'dart:io';
import 'globals.dart';
import 'dart:convert' show utf8;
import 'package:local_auth/local_auth.dart';
class SystemsSettingsPage extends StatefulWidget {
final int index;
SystemsSettingsPage({ required this.index});
#override
_SystemsSettingsPage createState() => _SystemsSettingsPage();
}
class _SystemsSettingsPage extends State<SystemsSettingsPage> {
bool tileValTemp = false;
bool tileValDetect = false;
bool tileValCamOff = false;
bool tileValSystem = false;
bool connected = false;
int successCount = 0;
String successPrompt = "";
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
appBar: AppBar(
backgroundColor: Colors.blueAccent,
title: Text("Base Station Settings"),
),
body: Column(
children: <Widget> [
SwitchListTile(value: tileValDetect,
onChanged: (bool val){ setState(() {
tileValDetect = val;
});},
title: Text('Detection notifications', style: TextStyle(color: Colors.white))
),
SwitchListTile(value: tileValTemp,
onChanged: (bool val){ setState(() {
tileValTemp = val;
});},
title: Text('Temperature threshold out of range', style: TextStyle(color: Colors.white))
),
TextButton(
child: const Text("save", style: TextStyle(fontSize: 20.0)),
style: ButtonStyle(foregroundColor: MaterialStateProperty.all<Color>(Colors.white),
padding: MaterialStateProperty.all<EdgeInsets>(EdgeInsets.all(10.0)),
backgroundColor: MaterialStateProperty.all<Color>(Colors.blueAccent)),
onPressed: () {
//successPrompt = "Loading.. Wait 5 seconds to update.";
successCount = 0;
Socket.connect(baseStationAddresses[0], baseStationPort,timeout: Duration(seconds: 5)).then(
(socket) {
print('Connected to: '
'${socket.remoteAddress.address}:${socket
.remotePort}');
String command = "SETSYSTEM," + baseStationNames[0] + ",detectMotion," + "$tileValDetect";
socket.write(command);
socket.listen((data) {
String socketData = utf8.decode(data);
if(socketData == "REQUEST_CONFIRMED") {
successCount += 1;
}
},
onDone: () {
socket.destroy();
},
);
},
).catchError((onError) {
print("here 1");
successPrompt = "There was a problem. Please retry.";
});
Socket.connect(baseStationAddresses[0], baseStationPort,timeout: Duration(seconds: 5)).then(
(socket) {
print('Connected to: '
'${socket.remoteAddress.address}:${socket
.remotePort}');
String command = "SETSYSTEM," + baseStationNames[0] + ",tempThreshold," + "$tileValTemp";
socket.write(command);
socket.listen((data) {
String socketData = utf8.decode(data);
if(socketData == "REQUEST_CONFIRMED") {
successCount += 1;
}
},
onDone: () {
print("SuccessCount $successCount");
if(successCount == 2)
{
print("here 2");
successPrompt = "Setting successfully saved.";
}
else
{
print("here 3");
successPrompt = "Couldn't save, please retry.";
}
socket.destroy();
},
);
}
).catchError((onError) {
print("here 4");
successPrompt = "There was a problem. Please retry.";
});
showDialog(context: context, builder: (context) =>
AlertDialog(
title: Text("Save results"),
content: Text(successPrompt),
actions: <Widget>[
TextButton(onPressed: () => Navigator.pop(context),
child: const Text("OK"),
)
]
)
);
/*
FutureBuilder<String>(
future: getSaveStatus(),
builder: (context, snapshot) {
String nonNullableString = snapshot.data ?? 'Error';
if(snapshot.hasData) {
return AlertDialog(
title: Text("Save results"),
content: Text(nonNullableString),
actions: <Widget>[
TextButton(onPressed: () => Navigator.pop(context),
child: const Text("OK"),
)
]
);
}
return Center(child: CircularProgressIndicator());
},
);*/
}
),
Center(
child:ClipRRect(
borderRadius: BorderRadius.circular(4),
child: Stack(
children: <Widget>[
Positioned.fill(
child: Container(
decoration: const BoxDecoration(
color: Colors.red,
),
),
),
TextButton(
style: TextButton.styleFrom(
padding: const EdgeInsets.all(16.0),
primary: Colors.white,
textStyle: const TextStyle(fontSize: 20),
),
onPressed: () {},
child: const Text('Remove System'),
),
],
),
),
)
],
)
);
}
Future<String> getSaveStatus() async {
return await new Future(() => successPrompt);
}
}
Any suggestion would be helpful.
Wrap the content of the dialog inside of a StatefulBuilder until that your AlertDialog behave as stateless widget Refer:
await showDialog<void>(
context: context,
builder: (BuildContext context) {
int selectedRadio = 0;
return AlertDialog(
content: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return Text(successPrompt);
},
),
);
},
);
I'm building a generalised flutter widget based on the flutter alertDialog, I want to have this as a separate widget which can be called with onPressed method in other widgets.
Currently the alertDialog opens with the onPressed method which is part of the current widget within ElevatedButton widget. I want to get rid of this ElevatedButton as the button to open alertDialog is part of other widget.
Class AppAlertDialog extends StatelessWidget {
const AppAlertDialog({
required this.buttonName,
required this.title,
required this.content,
required this.secondaryButtonName,
required this.primaryButtonName,
Key? key,
}) : super(key: key);
final String buttonName;
final String title;
final String content;
final String secondaryButtonName;
final String primaryButtonName;
#override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: () => _showAlertDialog(context),
child: Text(buttonName),
);
//Get rid of this ElevatedButton and call the _showAlertDialog method to open the
//alertDialog from other onPressed methods in other files
}
_showAlertDialog(BuildContext context) {
final titleTextStyle = Theme.of(context).textTheme.headline5!;
const buttonPadding = EdgeInsets.all(20);
showDialog(
context: context,
builder: (BuildContext context) => AlertDialog(
title: Text(
title,
style: titleTextStyle,
),
content: Text(content),
contentPadding: const EdgeInsets.fromLTRB(24, 24, 24, 24),
actions: <Widget>[
ElevatedButton(
onPressed: () => Navigator.pop(context),
style: ElevatedButton.styleFrom(
padding: buttonPadding,
primary: SharedColorsButton.secondaryButtonBgColor,
onPrimary: SharedColorsButton.secondaryButtonFgColor,
side: const BorderSide(
color: SharedColorsInputDecoration.borderColor,
),
),
child: Text(secondaryButtonName),
),
ElevatedButton(
style: ElevatedButton.styleFrom(
padding: buttonPadding,
),
onPressed: () => Navigator.pop(context),
child: Text(primaryButtonName),
),
],
actionsPadding: const EdgeInsets.fromLTRB(24, 16, 24, 16),
),
);
}
}
Please see the example.
showDialog(
context: context,
builder: (context) {
return StatefulBuilder(
builder: (context, setState) {
return AlertDialog(
insetPadding: EdgeInsets.symmetric(horizontal: 8, vertical: 24),
title: Text("New request"),
content: Container(
height: double.maxFinite,
width: double.maxFinite,
child: StreamBuilder(
stream: FirebaseFirestore.instance
.collection('requests')
.where('accepted', isEqualTo: 0)
.snapshots(),
builder: (context, snapshot) {
print("Second ride data ==> ${snapshot.hasData}");
if (snapshot.hasData) {
final requests = snapshot.data.docs;
if (User.homeModel == null) {
return Container(width: 0, height: 0);
}
if (User.homeModel.vehicleDetails == null) {
return Container(width: 0, height: 0);
}
List<RequestCard> allTrains = [];
for (var doc in requests) {
print(
"Second ride data ==> ID ${doc['request_id']}");
if (Home.removeRequests.contains(doc['request_id']))
continue;
//seats compare
int seatCapacity =
User.homeModel.vehicleDetails.passengerCapacity;
print('seatCapacity => $seatCapacity');
var seatRequest = doc['seats'];
print('seatRequest => $seatRequest');
//baby_seats compare
var babySeatsCapacity =
User.homeModel.vehicleDetails.children;
print('babySeatsCapacity => $babySeatsCapacity');
var babySeatsRequest = doc['baby_seats'];
print('babySeatsRequest => $babySeatsRequest');
//WheelChair compare
var hasWheelChair =
User.homeModel.vehicleDetails.wheelchair == '1'
? true
: false;
print('hasWheelChair => $hasWheelChair');
var needWheelChair = doc['wheelchair'];
print('needWheelChair => $needWheelChair');
//compare vehicles with requests
if (seatRequest <= seatCapacity &&
babySeatsRequest <= babySeatsCapacity &&
(needWheelChair == hasWheelChair ||
hasWheelChair == true)) {
print('Vehicle Condition Satisfy');
final _rideReq = RideRequest(
userName: doc['user_name'],
currentLocation: doc['pick_up_location'],
destination: doc['drop_off_location'],
fare: doc['bid_amount'],
desLatitude: doc['drop_off_lat'].toString(),
desLongitude: doc['drop_off_long'].toString(),
distance: doc['distance'],
image: doc['user_image'],
latitude: doc['pick_up_lat'].toString(),
longitude: doc['pick_up_long'].toString(),
phone: doc['phone'],
pickUpPoint: doc['pick_up_location'],
userId: doc['user_id'],
requestId: doc['request_id']);
final requestCard = RequestCard(
onAcceptFunction: onAcceptRequest,
onRejectFunction: onRejectRequest,
rideRequest: _rideReq,
);
allTrains.add(requestCard);
}
}
if (allTrains.length > 0) {
return ListView(
children: allTrains,
);
} else {
Future.delayed(Duration.zero)
.then((value) => Navigator.pop(context));
}
}
return Container(width: 0, height: 0);
}),
),
actions: <Widget>[
FlatButton(
onPressed: () {
HomeBottomNavigationBar._isNewRequestOpenDialog = false;
Navigator.pop(context, true);
},
child: Text("Close"),
),
FlatButton(
onPressed: () {
changeTabs(0);
HomeBottomNavigationBar._isNewRequestOpenDialog = false;
Navigator.pop(context, true);
},
child: Text("Go to Home"),
),
],
);
},
);
},
)
I am using the flutter_reactive_ble_example to connect to my Bluetooth module by modifying the file device_list.dart.
and I wonder how do I re prompt the user if password is wrong.
I'm fairly new to flutter, please do ask more details if required.
here is the code snippet that I currently have:
Flexible(
child: ListView(
children: widget.scannerState.discoveredDevices
.map(
(device) => ListTile(
title: Text(tile.name),
subtitle: Text("${tile.name}\n: ${tile.sub}"),
leading: const ConnectIcon(),
onTap: () async {
//stop the scan
widget.stopScan();
//connect to the device
await widget.deviceConn.connect(device.id);
//prompt user for password
final inputData = await showDialog(
context: context,
barrierDismissible:
false, // prevent user from closing the dialog by pressing outside the dialog
builder: (_) {
String userData = "";
return AlertDialog(
title: new Text("Enter Password"),
content: new TextField(
onChanged: (value) {
userData = value;
},
),
actions: <Widget>[
ElevatedButton(
child: Text('Ok'),
onPressed: () async {
//on press subscribe and send the password
response = await ble.subscribeToCharacteristic(characteristic);
//if data failure check, how do I reshow this showDialog??
response.listen((event) {
if(event == 1){
//if return 1, password correct
Navigator.of(context).pop(userData);
}else{
//if not reshow Dialog
//howw?
}
}
//send password
ble.writeCharacteristicWithoutResponse(characteristic, value: userData);
},
)
],
);
},
);
Navigator.of(context).pop(
inputData); // pass data back to the previous page
},
),
)
.toList(),
),
),
You can use a recursion I think, here an example
Future _showPasswordDialog(){
return showDialog(
context: context,
barrierDismissible:
false, // prevent user from closing the dialog by pressing outside the dialog
builder: (_) {
String userData = "";
return AlertDialog(
title: new Text("Enter Password"),
content: new TextField(
onChanged: (value) {
userData = value;
},
),
actions: <Widget>[
ElevatedButton(
child: Text('Ok'),
onPressed: () async {
//on press subscribe and send the password
response = await ble.subscribeToCharacteristic(characteristic);
//if data failure check, how do I reshow this showDialog??
response.listen((event) {
if(event == 1){
//if return 1, password correct
Navigator.of(context).pop(userData);
}else{
//if not reshow Dialog
//howw?
Navigator.of(context).pop();
_showPasswordDialog();
}
}
//send password
ble.writeCharacteristicWithoutResponse(characteristic, value: userData);
},
)
],
);
},
);
}
separate the the alert prompting as another function, and return user details if login success else return null.
Future<String> promptAlert(BuildContext context){
return showDialog(
context: context,
barrierDismissible:
false, // prevent user from closing the dialog by pressing outside the dialog
builder: (_) {
String userData = "";
return AlertDialog(
title: new Text("Enter Password"),
content: new TextField(
onChanged: (value) {
userData = value;
},
),
actions: <Widget>[
ElevatedButton(
child: Text('Ok'),
onPressed: () async {
//on press subscribe and send the password
response = await ble.subscribeToCharacteristic(characteristic);
//if data failure check, how do I reshow this showDialog??
response.listen((event) {
if(event == 1){
//if return 1, password correct
Navigator.of(context).pop(userData);
}else{
Navigator.of(context).pop();
}
}
//send password
ble.writeCharacteristicWithoutResponse(characteristic, value: userData);
},
)
],
);
},
);
}
and check for the returned value is not null on the ListItem onTap
bool isLogin = (await promptAlert(context)) !=null;
while(isLogin ){
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
duration: Duration(seconds: 2),
content: Text('Login Failed Try again')));
String user= await Future.delayed(
Duration(seconds: 2), () => promptAlert(context));
isLogin = user !=null;
}
If you want to show a snackbar and delayed alert,
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
duration: Duration(seconds: 2),
content: Text('Login Failed Try again'),
));
Future.delayed(
Duration(seconds: 2), () => promptAlert(context));
In flutter, I have a showDialog() with cancel and confirm button. The confirm button will trigger a call to an API. What I need is to show a "loading..." in the showDialog window after the click and once the API call is finished to show a success or failure. How can I manage this? Or should I close the window, waiting for the reply and popup a new dialog window with success or false? Thx for any help or better suggestion. Here what I have so far:
void _showAlert(String textLabel, String action, String linkId) {
showDialog(
context: context,
//barrierDismissible: false, // on external click
builder: (_) => new AlertDialog(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10.0)),
title: new Text(
'Please confirm:',
style: TextStyle(color: Colors.deepOrange, fontWeight: FontWeight.bold),
textAlign: TextAlign.center,
),
content: new Text(
textLabel,
style: new TextStyle(fontSize: 20.0),
),
actions: <Widget>[
new FlatButton(
onPressed: () {
Navigator.pop(context);
},
child: new Text('CANCEL')),
new FlatButton(
onPressed: () {
_postAction(action, linkId).then((_) => setState(() {}));
Navigator.pop(context);
},
child: new Text('I CONFIRM')),
],
));
}
You can try this,this is just the idea..
class _SampleState extends State<Sample> {
bool isSuccessFromApi = false;
bool isLoading = false;
Widget popup() {
showDialog(context: context, builder: (builder) {
return AlertDialog(
content: !isSuccessFromApi ? Container(
child: Text('Are you Sure???'),
) : Container( child: isLoading ? CircularProgressIndicator() : Text('Success'),),
actions: <Widget>[
Text('Cancel'),
InkWell(child: Text('OK'), onTap: apiCall,)
],
);
});
}
void apiCall(){
setState(() {
isLoading = true;
});
//call the api
//after success or failure
setState(() {
isLoading = false;
isSuccessFromApi = true;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: popup(),
);
}
}