Flutter : how to conditionally repeat a showDialog inside a ListView - flutter

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));

Related

Flutter Web - after password reset success, unable to login unless refresh

I'm using Flutter and Firebase Auth to have a password recovery screen in Flutter Web that takes the users email 'oobCode' to verify the email via URL parameters.
Then set their new password and allow them to login with their new password.
This works very well with one problem - once the new password is set, I send them to a new route to login but they cannot login unless they refresh the page manually or start a new browser.
There is no error of any kind BUT if they "refresh" the page then they can login without problems with the new password.
I'm using an ElevatedButton.icon to trigger the changing of the password and then using Navigator.pushAndRemoveUntil to send them to the login screen with no way to 'back' to the reset screen.
Here's the button:
ElevatedButton.icon(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.teal.shade900,
minimumSize: const Size.fromHeight(50),
),
icon: processingReset
? const Center(
child: SizedBox(
height: 20,
width: 20,
child: CircularProgressIndicator(color: kQptColorLightGrey200),
),
)
: const Icon(Icons.lock_open, size: 32),
label: const Text('Reset Password'),
onPressed: () async {
String? submitStatus = "";
ScaffoldMessengerState scaffoldState = ScaffoldMessenger.of(context);
if (_formKey.currentState!.validate()) {
/// show a progress indicator on the button icon
try {
setState(() {
processingReset = true;
_hasException = false;
});
await resetPassword();
/// with success, then redirect to the login page
submitStatus = 'Password reset successfully';
if (!mounted) return;
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(
builder: (BuildContext context) {
return const LoginScreen();
},
),
(route) => false,
);
} on FirebaseAuthException catch (e) {
submitStatus = "Error: ${e.message}";
setState(() {
processingReset = false;
_hasException = true;
});
}
scaffoldState.showSnackBar(
SnackBar(
backgroundColor: _hasException ? kQptColorRed : kQptColorGreen,
duration: const Duration(seconds: 3),
content: Text(submitStatus),
),
);
}
},
),
then, the login screen has a Column with several controls that take the users email and new password:
children: <Widget>[
LoginTextFormField(
inputTextInputAction: TextInputAction.next,
inputTextEditingController: _emailTextFormFieldController,
inputLabelText: "Enter your Email here",
inputObscureText: false,
inputValidatorText: "Please enter your Email",
),
const SizedBox(height: 20),
LoginTextFormField(
inputTextInputAction: TextInputAction.done,
inputTextEditingController: _passwordTextFormFieldController,
inputLabelText: "Enter your Password here",
inputObscureText: true,
inputValidatorText: "Please enter your Password",
),
const SizedBox(height: 20),
ElevatedButton.icon(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.teal.shade900,
minimumSize: const Size.fromHeight(50),
),
icon: const Icon(Icons.lock_open, size: 32),
label: const Text('Sign In'),
onPressed: signIn,
),
const SizedBox(
height: 24,
),
GestureDetector(
child: const Text(
"Forgot Password?",
style: TextStyle(
decoration: TextDecoration.underline,
),
),
onTap: () => Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => const ForgotPasswordScreen(),
),
),
),
],
),
This calls a signin function that processes the login (works very well and produces no errors)
Future signIn() async {
String feedbackText = 'Processing your login...';
if (_formKey.currentState!.validate()) {
print('login screen form is valid');
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
//duration: const Duration(seconds: 2),
content: Text(feedbackText),
),
);
try {
print('login screen starting login process');
print('login screen: ${_passwordTextFormFieldController.text}');
await FirebaseAuth.instance.signInWithEmailAndPassword(
email: _emailTextFormFieldController.text.trim(),
password: _passwordTextFormFieldController.text.trim(),
//password: 'bad pass',
);
// disable automatic login by disabling persistence on web platforms
await FirebaseAuth.instance.setPersistence(Persistence.LOCAL);
print('login screen setting persistence');
} on FirebaseAuthException catch (e) {
print('login screen: we have an error');
if (e.code == 'user-not-found' || e.code == 'invalid-email') {
print('No user found for that email or badly formatted email');
feedbackText = 'No user found for that email or badly formatted email';
} else if (e.code == 'wrong-password') {
print('Wrong password provided for that user.');
feedbackText = 'Wrong password provided for that user.';
} else {
print('general error: ${e.message}');
feedbackText = 'General error: ${e.message}';
}
/// display an error message
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
backgroundColor: Colors.red,
content: Text(feedbackText),
),
);
}
}
/// end of validate
}
and then the main.dart is called to route the user to main navigation:
class _MyAppState extends State<MyApp> {
User tempUser = User();
/// TODO: change this to false before deploy
bool resetFlag = false;
String inputCode = '';
String inputMode = 'resetPassword';
#override
void initState() {
/// look to see if we have a queryString parameter and then set the flag for reset or not
print('In Main initState');
if (Uri.base.queryParameters['oobCode'] != null) {
/// we have a parameter
print('In Main initState, oobCode found');
if (Uri.base.queryParameters['oobCode']!.isNotEmpty) {
/// it's not empty - assign the value
inputCode = Uri.base.queryParameters['oobCode']!;
print('In Main initState, oobCode is not null');
/// set the reset flag
resetFlag = true;
print('In Main initState, resetFlag is set to $resetFlag');
}
}
super.initState();
}
#override
Widget build(BuildContext context) {
return MaterialApp(
showPerformanceOverlay: false,
debugShowCheckedModeBanner: false,
title: 'Scheduler',
theme: ThemeData(
visualDensity: VisualDensity.adaptivePlatformDensity,
primarySwatch: Colors.teal,
textTheme: const TextTheme(
/// for Small headlines
headlineSmall: TextStyle(
fontSize: 11,
color: Colors.black,
),
/// for Form elements
bodyMedium: TextStyle(
fontSize: 17,
color: kQptColorTeal,
),
titleLarge: TextStyle(fontSize: 22, color: kQptColorTeal, fontWeight: FontWeight.bold),
titleSmall: TextStyle(fontSize: 13, color: Colors.black87, fontWeight: FontWeight.bold),
),
),
/// if we have a URI Parameter it will be for a password reset, branch based on that
home: resetFlag
? UserManagementScreen(
inputCode: inputCode,
inputMode: inputMode,
)
: StreamBuilder<User?>(
stream: FirebaseAuth.instance.authStateChanges(),
builder: (context, snapshot) {
if (snapshot.hasData) {
/// we have a user, grab the Id and create a
/// User from the Uid value
/// then pass this into the CalendarScreen and ProfileScreen
print('in main screen: StreamBuilder snapshot has data');
return FutureBuilder<SchedulerUser>(
future: SchedulerUtils.getSchedulerUser(FirebaseAuth.instance.currentUser!.uid),
builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
print('in main screen: FutureBuilder entry');
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.hasData) {
tempUser = snapshot.data;
print('in main screen future builder tempuser: $tempUser');
return MainNavScreen(inputSchedulerUser: tempUser);
} else if (snapshot.hasError) {
print("we have an error while getting the snapshot:${snapshot.error}");
return const ErrorScreen();
} else {
print('in main screen future builder no data: $tempUser');
// no data?...test, should never be hit.
return const ErrorScreen();
}
} else {
return const Center(child: CircularProgressIndicator());
}
},
);
} else {
return const LoginScreen();
}
},
),
routes: {
/// Primary Calendar Screen
CalendarScreen.id: (context) => CalendarScreen(inputSchedulerUser: tempUser),
/// When navigating to the "/login" route, build the LoginScreen.
LoginScreen.id: (context) => const LoginScreen(),
/// Profile Screen
//ProfileScreen.id: (context) => ProfileScreen(inputSchedulerUser: tempUser),
/// Main Navigation screen
MainNavScreen.id: (context) => MainNavScreen(inputSchedulerUser: tempUser),
/// Reset Password
UserManagementScreen.id: (context) => UserManagementScreen(inputMode: inputMode, inputCode: inputCode),
},
);
}
}
So, my question is, is there something I'm missing or a way "refresh" the web page app programmatically after the user has changed their password?

DisMissState.build.<anonymous closure>.<anonymous closure>

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.

Update an AlertDialog with success or fail message after network communication

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);
},
),
);
},
);

showDialog always returns null

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?

Query in button error on the first click flutter

I have problem in button to search user by phone number. When I press button for the first time, query can't retrieve the data, like this : Debug Console 1. But if I press button again for the second time or more, query can retrieve the data, like this : Debug Console 2.
This is my button code :
Widget tmblKonfirm() {
return Center(
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
),
child: FlatButton(
onPressed: () async{
await pr.show();
print('NoHP : ' + nohp);
final dataUser = FirebaseDatabase.instance
.reference()
.child("users")
.orderByChild("pengguna_nomor")
.equalTo(nohp);
print('Data User : ' + dataUser.toString());
dataUser.once().then((DataSnapshot snapshot) {
Map<dynamic, dynamic> data = snapshot.value;
setState(() {
testUser = snapshot.value.toString();
});
data.forEach((key, values) {
lists.add(values);
});
});
print('User 1 : ' + lists.toString());
pr.hide();
if (_formKeyKonfirm.currentState.validate()) {
print('User 2 : ' + testUser.toString());
if (testUser != 'null'){
// setState(() {
sttsUser = true;
// });
// showWidgetTopup();
}
else {
showDialog(
context: context,
builder: (context) {
return AlertDialog(
content: Text(
'Nomor HP tidak terdaftar'),
actions: <Widget>[
FlatButton(
onPressed: () => Navigator.of(context).pop(),
child: Text('OK'),
)
],
);
},
);
}
}
},
child: Text('Konfirmasi',
style: TextStyle(color: Colors.black, fontSize: 25)),
color: Colors.lightBlue,
),
));
}
dataUser is a Query so you always have to fetch the data snapshot (once() or onValue)
Instead of dataUser.once().then use await dataUser.once(), otherwise the snapshot callback can be actually called after the onPressed function finishes