I'm trying to create a Login Page for my app. I want it to say 'Logging In' then update if there's an issue.
When run, the app doesn't appear to do anything for the timeout duration. I'm guessing the 'Logging In' status is appearing microseconds before the AlertDialog shows as both the status and AlertDialog seem to appear at the same time. Once I click OK, the status updates to 'Retry Login'.
What I want is the 'Logging In' to appear as the text for my status box as soon as the button is pressed.
Button onPressed
ElevatedButton(
onPressed: () async {
setState((){
statusText = 'Logging In';
});
LoginNow();
TimeOutCheck();
},
child: Text('Log In'),
),
TimeOutCheck()
Future<void> TimeOutCheck() async{
hasTimedOut = false;
Duration timeoutDuration = GLOBAL_CONFIG.DEFAULT_TIMEOUT;
final endTime = DateTime.now().add(timeoutDuration);
while (!isComplete && DateTime.now().isBefore(endTime)){
sleep(Duration(milliseconds: 500));
print('Waiting ${DateTime.now()}');
}
hasTimedOut = !isComplete;
if (hasTimedOut){
await _showAlert('Timeout', 'Login Attempt Timed Out.\n\nPlease try again.');
setState(() {
_StatusText = 'Retry Login';
});
}
}
LoginNow()
Future<void> LoginNow(BuildContext context) async {
final String funcName = 'LoginNow';
bool doLogin = false;
_LoginForm.currentState!.validate();
setState(() {
if (LoginInfo['username'] == null || LoginInfo['username'] == ''){
_StatusText = 'Please Enter your User Name';
isComplete = true;
}
else if (LoginInfo['password'] == null || LoginInfo['password'] == '') {
_StatusText = 'Please Enter your Password';
isComplete = true;
}
else {
logger.wtf('[${className}.${funcName}]\t Setting Status to:\t "Logging In"\n');
//_StatusText = 'Logging In';
doLogin = true;
}
});
if (doLogin){
logger.d('[$className.$funcName\t Do Login: $doLogin');
logger.d('[$className.$funcName\t _StatusText Value: $_StatusText');
String URL = GetLoginRequest();
Uri uri = Uri.parse(URL);
var response = await http.get(uri);
isComplete = true;
// Do the rest of login stuff
}
I think what you need is to simply await the functions.
...
await LoginNow();
await TimeOutCheck();
// Make sure to update the status text at the end.
setState(() {
statusText = "Finished";
}
The problem is in the TimeOutCheck(). During the while, I'm using Sleep. Sleep is a synchronous function. Sleep is holding up all changes that may be completed by other async processes.
while (!isComplete && DateTime.now().isBefore(endTime)){
sleep(Duration(milliseconds: 500));
}
Instead of Sleep, I should've used Future.delayed(). Future.delayed() allows other threads to run while the thread it's called in waits for the delay.
while (!isComplete && DateTime.now().isBefore(endTime)){
await Future.delayed(Duration(milliseconds: 500));
}
The working code looks like:
Future<void> TimeOutCheck() async{
this.hasTimedOut = false;
Duration timeoutDuration = GLOBAL_CONFIG.DEFAULT_TIMEOUT;
final endTime = DateTime.now().add(timeoutDuration);
while (!(this.isComplete) && DateTime.now().isBefore(endTime)){
await Future.delayed(Duration(milliseconds: 500));
print('Waiting ${DateTime.now()}\n\t isComplete\t ${this.isComplete}');
}
this.hasTimedOut = !this.isComplete;
if (this.hasTimedOut && !this.isComplete){
await _showAlert('Timeout', 'Login Attempt Timed Out.\n\nPlease try again.');
setState(() {
this._StatusText = 'Retry Login';
});
}
}
Related
So for some background, I implemented a function that reads from Firebase's real-time database and returns a child node. I have built a button that is meant to check if that function returns the object or null if the function returns an object I want the snack bar to display a message.
ElevatedButton(
onPressed: () {
if (validateUsername() != null) {
print("conditional: test");
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
content: Text(
"Username has been taken please try a different one"),
duration: Duration(seconds: 5)));
} else {
return null;
}
},
I had some success with the function by turning it into an async function
validateUsername() async {
final database = FirebaseDatabase.instance.ref().child("/takenUsernames");
await database
.child(_usernameController.text.trim())
.once()
.then((DatabaseEvent event) {
final snapShot = event.snapshot;
final value = snapShot.value;
print("function result: $value");
return value;
});
}
When I turn it to an async function the snack bar displays the message but unfortunately even when the conditional is equal to a null, it for some reason continues to display the message and prints the "test"output. But if I were to try taking away the async the snack bar doesn't print and the "test" in the conditional doesn't print.non-async output
Any help would be appreciated and thanks for your time.
Try this approach, using the await in a variable will wait for the value then the if will evaluate what the result.
ElevatedButton(
onPressed: () async {
String validation = await validateUsername(); // I used type String but you should use the type that will be return.
if (validation != null) {
print("conditional: test");
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
content: Text(
"Username has been taken please try a different one"),
duration: Duration(seconds: 5)));
} else {
return;
}
},
)
add try/catch
validateUsername() async {
try {
final database = FirebaseDatabase.instance.ref().child("/takenUsernames");
await database
.child(_usernameController.text.trim())
.once()
.then((DatabaseEvent event) {
final snapShot = event.snapshot;
final value = snapShot.value;
print("function result: $value");
return value;
});
} catch(e) {
print("err $e");
return null;
}
}
Thanks to some help from #WilsonToribio, I was able to use the information he gave and also implement a few changes to the validateUsername() function
as seen here
validateUsername() async {
try {
final database = FirebaseDatabase.instance.ref().child("/usernames");
final response = await database
.child(_usernameController.text.trim())
.once()
.then((event) {
final dataSnapshot = event.snapshot;
if (dataSnapshot.value != null) {
return dataSnapshot.value;
}
});
return response;
} catch (e) {
print("err $e");
return null;
}
}
I have been wanting to find a solution to create a rounded search bar in flutter which also shows the list of recent searches underneath it. How is it possible to create the previous widget?
Using the package below, you can save the information you want to the device memory and then withdraw it from there (username, password, search history, etc.). The sample code is in the document.
https://pub.dev/packages/shared_preferences
like this:
void handleRememberMe(bool? value) {
_isChecked = value!;
SharedPreferences.getInstance().then(
(prefs) {
prefs.setBool("remember_me", value);
prefs.setString('userName', userNameController.text);
prefs.setString('password', passwordController.text);
},
);
setState(() {
_isChecked = value;
});
}
void loadUserEmailPassword() async {
try {
SharedPreferences _prefs = await SharedPreferences.getInstance();
var _email = _prefs.getString("userName") ?? "";
var password = _prefs.getString("password") ?? "";
var _remeberMe = _prefs.getBool("remember_me") ?? false;
if (_remeberMe) {
setState(() {
_isChecked = true;
});
userNameController.text = _email;
passwordController.text = password;
} else {
userNameController.text = "";
passwordController.text = "";
setState(() {
_isChecked = false;
});
}
} catch (e) {
debugPrint(e.toString());
}
}
I am not understanding how do i check if getLogin() function worked or not and if user exists or not.
where do i write the code to display the message. When I checked by assigning a variable to this statement
res=dbService.getLogin(_email, _password); then it shows datatype mismatch. Can u plzz help me with this. I am developing flutter app in android studio and using sqlite. Please do help me I am stuck from 2 days.
I call _submit() on login button
bool validateAndSave() {
final form = _formKey.currentState;
if (form.validate()) {
form.save();
return true;
}
return false;
}
void _submit(){
if (validateAndSave()) {
setState(() {
dbService.getLogin(_email, _password);
});
}
}
And this is db_service.dart code for getLogin()
Future<RegisterUser> getLogin(String user, String password) async {
await DB.init();
var res = await DB.rawQuery("userDetails WHERE emailId = '$user' and password = '$password'");
if (res.length > 0) {
return RegisterUser.fromMap(res.first);
}
return null;
}
You could wait for the result of the login method.
void _submit() async {
if (validateAndSave()) {
var user = await dbService.getLogin(_email, _password);
if (user != null) {
Navigator.of(context).pushNamed('/home');
} else {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text("Sign in error")));
}
}
}
In this case I pushed to the home screen if successfull and showed a snackbar in case of error. You could adjust this to your intended use case.
How can I disable a a button while in processing? I have made a like button, but it takes some delay to register the like on db. How can I prevent the button press while processing is done. ??
onPressed: () {
setState(() {
_color = Colors.green;
_icon = Icon(Icons.favorite);
final like = LikeData(
campaingID: donation.campaignID,
dateTime: Timestamp.now(),
userName: FirebaseAuth.instance.currentUser.displayName,
userId: user.uid,
);
likeService.newLike(donation, like);
how can i prevent double tap on this button event??
try this
bool isProcessing = false; // in your class before build method
onPressed: !isProcessing ? () {
setState(() {
isProcessing = true;
_color = Colors.green;
_icon = Icon(Icons.favorite);
final like = LikeData(
campaingID: donation.campaignID,
dateTime: Timestamp.now(),
userName: FirebaseAuth.instance.currentUser.displayName,
userId: user.uid,
);
likeService.newLike(donation, like).then((val) {
setState() {
isProcessing = false;
}
});
});
} : null,
Think of it as three different states, e.g. "initial", "pressedConfirmationPending" and "pressedAndConfirmed". The button can then be enabled depending on this status, e.g.:
String buttonStatus = 'initial';
...
MyButton(
...
enabled: buttonStatus == 'initial' || buttonStatus == 'pressedAndConfirmed',
...
);
Set the status to 'pressedConfirmationPending' just before you start the input processing, and then to 'pressedAndConfirmed' after your receive confirmation that processing is finished.
Also, the button's design and child widget will most likely be different, and possibly also it's onPressed functionality.
You can do adding a variable between processing.
I think that 'newLike' is synchronous method.
So you set checking variable true and processing 'newLike'.
After that set checking variable false to enable button.
bool isProcessing = false;
...
onPressed: isProcessing ? null : () async {
setState(() {
isProcessing = true;
final like = LikeData(
campaingID: donation.campaignID,
dateTime: Timestamp.now(),
userName: FirebaseAuth.instance.currentUser.displayName,
userId: user.uid,
);
await likeService.newLike(donation, like);
});
setState(() {
_color = Colors.green;
_icon = Icon(Icons.favorite);
isProcessing = false;
});
firstly you need to create bool variable like this
'
bool _isworking=false;
onPressed:(){
if(_isworking==false){
setState(() {
_isworking=true;
});
{{ here your function }}
setState(() {
_isworking=false;
});
}
else
{
print('the Function is working now please wait ');
//you can edit this and show the user a dialog that the process is going on and he need to wait
}
}
In a Flutter integration test, I want to wait for a button to become enabled before pressing it. Is this possible?
This function gets you whether a widget is enabled, and you could then wait for it to change state:
Future<bool> isEnabled(FlutterDriver driver, SerializableFinder widgetFinder) async {
Map widgetDiagnostics = await driver.getWidgetDiagnostics(widgetFinder);
return widgetDiagnostics["properties"]
.firstWhere((property) => property["name"] == 'enabled')["value"];
}
Update: here's that wait- friend
Future<void> waitForEnabled(
FlutterDriver driver, SerializableFinder widgetFinder,
{Duration timeout,
Duration interval,
bool value = true,
String reason}) async {
await driver.waitFor(widgetFinder, timeout: timeout);
timeout ??= Duration(seconds: 5);
interval ??= Duration(milliseconds: 250);
var endTime = DateTime.now().add(timeout);
while (true) {
if (await isEnabled(driver, widgetFinder) == value) {
return;
} else if (DateTime.now().isAfter(endTime)) {
throw TimeoutException(reason ??
'widget did not become ${value ? 'enabled' : 'disabled'} within timeout');
} else {
await Future.delayed(interval);
continue;
}
}
}
You can pass value: false to have it wait for not-enabled