How to handle progress indicator upon canceling google sign-in dialog? - flutter

When user clicks on Google Sign in button, the progress indicator is set to true and the app shows dialog to select user accounts from. If user clicks outside of the dialog or system back button, the progress indicator keeps showing.
Where and how do I set the _isLoading = false so that the progress indicator goes away ?
Container(
padding: EdgeInsets.all(_isLoading ? 20.0 : 0.0),
width: 75,
height: 75,
child: _isLoading
? CircularProgressIndicator()
: IconButton(
icon: Image.asset('assets/google.png'),
onPressed: () => _handleGoogleSignIn()),
)
...
Future<Null> _handleGoogleSignIn() async {
setState(() {
_isLoading = true;
});
LoginUtils().handleGoogleSignIn().then((firebaseUser) {
_postSignInAction(firebaseUser);
}).catchError((exception) {
_handleLoginException(exception);
});
}

This depends on what exactly LoginUtils().handleGoogleSignIn() does. Given that it is a Future, if what it does is display the dialog to select the Google account, it should complete as soon as the dialog is dismissed. I would expect it to complete successfully, with firebaseUser being null - this is the default behavior when popping routes by pressing back as well.
You should therefore simply have to add the following:
if (firebaseUser == null) {
setState(() {
_isLoading = false;
});
}
If however that Future does not complete at all when the dialog is dismissed, then that's the problem you need to solve.

Related

Flutter - Confirm closing of Dialog on Back button?

I'm currently trying to figure out how to prevent a dialog from closing directly by the back button.
For Dialogs, I have a base class, which I give a Widget with content.
Now I have a Screen, where the user gets to enter something in a dialog, and when the user presses the cancel button, a second Dialog pops up on top of the existing dialog.
With this second dialog, which also uses the base class, I can confirm or abort closing the previous dialog.
But when I use the back button of my phone, the dialog instantly closes, without asking to confirm.
Already tried wrapping the Dialog in the base class with WillPopScope, but that only resulted in the Dialog not closing anymore (maybe I did something wrong there, but I don't have the code of that try anymore)
Here is the code of the base class (I shortened it a bit by removing styling etc.):
class DialogBase {
final bool isDismissable;
final Function()? doAfterwards;
final Function(bool)? onConfirmClose;
DialogBase(
{required this.isDismissable, this.doAfterwards, this.onConfirmClose});
show(BuildContext context, Widget content) async {
await showDialog(
barrierDismissible: isDismissable,
context: context,
builder: (context) {
return Dialog(
child: Padding(
padding: const EdgeInsets.all(20),
child: content,
),
);
}).then((value) {
if (onConfirmClose != null && value != null) {
onConfirmClose!(value);
}
}).whenComplete(() async {
if (doAfterwards != null) {
doAfterwards!();
}
});
}
}
In a Dialog Content widget, the abort button does the following onPressed:
onPressed: () async {
await DialogBase(
isDismissable: false,
onConfirmClose: (bool result) {
if (result) {
Navigator.of(context).pop();
}
}).show(context, const ConfirmAbortDialogContent());
},
And the dialog itself gets called this way (which is also called on a button pressed:
void openAddNewPostDialog(BuildContext context) {
DialogBase(
isDismissable: false).show(context, const AddThreadPostDialogContent());
}
Any way on how to prevent a dialog from closing when the back button is used (and instead ask user to confirm)?
Try WillPopScope
Using onWillPop value I think you can achieve what you want

how to disable user to go back after successful login in flutter

I have this login function and it's working like charm , but the problem that the user can press the back button and return to the login screen and I want to disable that.
void _login() async {
setState(() {
_isLoading = true;
});
var data = {'email': email, 'password': password};
print("Data =" + data.toString());
var res = await Network().authData(data, '/login');
var body = json.decode(res.body);
print(body);
if (body['success']) {
SharedPreferences localStorage = await SharedPreferences.getInstance();
localStorage.setString('access_token', json.encode(body['access_token']));
localStorage.setString('user', json.encode(body['user']));
if (body['user']['verified'] == 1) {
// OPEN THE HOME PAGE AND BLOCK THE BACK ACTION
Navigator.push(
context,
MaterialPageRoute(builder: (context) => HomeScreen()),
);
} else {
showAnimatedDialog(
context: context,
barrierDismissible: true,
builder: (BuildContext context) {
return AlertDialog(
backgroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
content: Container(
width: 100,
height: 50,
child:
Center(child: Text("Your account isn't verified yet!")),
));
},
animationType: DialogTransitionType.fadeScale,
curve: Curves.fastOutSlowIn,
duration: Duration(seconds: 1),
);
}
} else {
_showMsg(body['message']);
}
setState(() {
_isLoading = false;
});
}
Plus , is there a way also to keep him logged even after closing the app once the login is done ? (Until the user press a logout button).
Firstly: Let us understand how Screens are organized in Flutter Framework:
The screens in Flutter are organized in a Stack fashion, so if you are on Screen A and you pushed the Screen B, then from Screen B you pushed Screen C, your stack would look like the following:
After Pushing Screen B:
Screen A
Screen B (Current Screen)
After Pushing Screen C:
Screen A
Screen B
Screen C (Current Screen)
So let's say Screen B is your Login Screen, and you want to block the user from going back, Then you don't have to just Push the screen into the stack, instead, you should Replace the screen in the stack with your Home Screen.
Secondly: Now after understanding the flow, let us talk about the code:
In your scenario, instead of using Navigator.push() you have to use Navigator.pushReplacement() to replace the screen in the stack as mentioned above.
Bounce:
If you are in a scenario that needs all the screens to be removed from the stack and keep only the screen that would be pushed, as an example, if you have a multi-step registration flow, and after successful registration, you want to pop all the screens of the process from the stack and pushing the home screen, then you may use the following trick:
Navigator.popUntil((route) => route.isFirst); // Execute it before pushing the screen in order to keep only one route (screen) in the stack.
Navigator.pushReplacement(); // Now you are replacing the only screen in the stack with the new one so the stack will only contain your wanted screen.
I kind of find a way to do it , I don't know if it's the good way to do it but this did the job .
Wrapping the scaffold with WillPopScope like this :
WillPopScope(
onWillPop: () async => false,
)
I got it from here
Here first you need to check whether the user is authenticated....
You have to check this condition's into material app's home : parameter.
if (snapShot.connectionState == ConnectionState.waiting) {
return const SplashScreen(); // If it's in waiting state it will show splash screen
}
if (snapShot.hasData) {
return const homeScreen(); // If user is authenticated it will show home screen
}
return const LoginScreen(); // If user is not authenticated then it will show login screen
here, snapShot is all about user data.
Thanks in advance.
To block the user from moving back to the previous screen, try Navigator.pushReplacement instead of Navigator.push.

Closing ModalBottomSeet before time on Flutter causes black screen

I'm trying to create a modal bottom sheet using showModalBottomSheet, which wil display a form to register a todo item. The idea is that once the todo item is registered, I want to display a check icon from some seconds and then automatically close the sheet.
here is the snippet:
FloatingActionButton _floatingActionButton(BuildContext context) {
return FloatingActionButton(
child: Icon(Icons.add),
onPressed: () async {
await _showBottomSheet(
context: context,
content: CreateTodoForm(
onClose: () {
...
Navigator.pop(context);
},
),
);
},
);
}
and inside the CreateTodoForm widget:
class _CreateTodoFormState extends State<CreateTodoForm> {
TextEditingController titleController = TextEditingController();
bool completed = false;
#override
Widget build(BuildContext context) {
return BlocProvider(
create: (_) => getIt<TodoFormBloc>(),
child: BlocBuilder<TodoFormBloc, ITodoFormState>(
builder: (context, state) {
...
if (state is SubmittedTodo) {
Future.delayed(Duration(seconds: 2), widget.onClose);
return Container(
height: 127,
child: Icon(Icons.check, size: 50, color: Colors.white),
);
}
...
},
),
);
}
Has you can see, when the state is SubmittedTodo (todo was submitted successfully) I return a container with the check icon, and after 2 seconds I call the onClose Function which is a call to Navigator.pop(context) to close the sheet.
This works great but it has a problem... if the user taps the < button on the device, or swipe the sheet down to dismiss it, before the 2 seconds are completed, then the sheet closes due to the user action, and then the future completes and it basically closes the app (the app get full black screen).
So my quiestion is how can I close the sheet automatically after some time safely without worring about what the user does.
Probably this is happening because of Navigator.pop(context); getting called after you click the back button which cause two pop. and the black screen is shown because there is no other screen to navigate back to.
As a solution i propose wrapping your form widget by WillPopScope and then you will get notified that the user clicked on the back button. here you can close your form by calling onClose

Flutter buttons not hiding on first click

I am writing a flutter application and in it, I have join/leave event buttons when the stream comes in it sets the button to either true or false, eg: if a user is in the event it is set to true so the leave button shows up but when I click the leave button I have to click it twice to change before it changes back to the join button when I print the value i can see it change back to true the first click but the UI doesn't change the button back until the second click does anyone know why this is
child: eventStatus
? FlatButton(
child: Text('Leave Event'),
color: Colors.redAccent,
onPressed: () async {
int st = int.parse(eventData.votes);
int newVotes = --st;
await DatabaseService.instance.removeVote(
AuthService.instance.user.uid,
newVotes.toString(),
this.widget.event);
_snackbar.showSnackBarSuccess('OK LEAVE THEN !');
setState(() {
eventStatus = !eventStatus;
print(eventStatus);
});
})
: FlatButton(
child: Text('Join event'),
color: Colors.greenAccent,
onPressed: () async {
int st = int.parse(eventData.votes);
int newVotes = ++st;
await DatabaseService.instance
.vote(newVotes.toString(), this.widget.event);
_snackbar.showSnackBarSuccess('Hey you have friends !');
setState(() {
eventStatus = !eventStatus;
print(eventStatus);
});
}),
Use async/await in other function with Future, and call it in onPressed

How do I interact with UI and async functions by a button press?

So I have a login button that would initiate an async function logMeIn() for logging in, which returns a Future<bool> indicating if logging in was successful.
What I want to achieve is after pressing the button, the text within the button would change into a CircularProgressIndicator, and become plain text when logMeIn() has finished.
I know that I could use a FutureBuilder for one-time async tasks, but I simply can't think of a way to use it here.
How am I supposed to achieve this?
Thanks in advance.
Edit
I didn't provide more information because I'm pretty sure it wasn't how it's supposed to be done. But I'd share what I tried whatsoever.
My original button looks somewhat like this:
RaisedButton(
child: Text("Login")
)
I have a async login function that returns a Future<bool> which indicates if the login is successful or not.
Future<bool> logMeIn() async {
// login tasks here
}
What I want to do is to change the button to the following and run logMeIn() when I press the button, and change it back to the plain text version after it's finished:
RaisedButton(
child: CircularProgressIndicator()
onPressed: () {}
)
What I tried
I tried adding a logging_in boolean as a flag to control it, here I call the button with plain text StaticButton(), the other named ActiveButton:
Then I wrapped the ActiveButton with a FutureBuilder:
logging_in ? FutureBuilder(
future: logMeIn(),
builder: (_, snapshot) {
if(snapshot.hasData)
setState(() {
logging_in = false;
});
else
return ActiveButton();
}
) : StaticButton();
and when I pressed the button it would setState and set logging_in = true.
StaticButton:
RaisedButton(
child: Text("Login"),
onPressed: () {
setState(() {
logging_in = true;
});
}
)
The above is what I tried. Should accomplish the effect, but is definitely not elegant. Is there a better way to achieve this?
What you have isn't that far off. You have a logging_in variable that tells you when to show the CircularProgressIndicator already, which is a good start. The only thing is that FutureBuilder has no use here. It's meant for retrieving data from Futures that need to be displayed in the UI.
What you need to do is call logMeIn in the onPressed of your "Static button" as shown here:
StaticButton:
RaisedButton(
child: Text("Login"),
onPressed: () async {
setState(() {
logging_in = true;
});
await logMeIn();
setState(() {
logging_in = false;
});
}
)
You don't need to switch out the whole button when logging_in changes, just the child of the button.
StaticButton(with child switching):
RaisedButton(
child: logging_in ? CircularProgressIndicator() : Text("Login"),
onPressed: () async {
setState(() {
logging_in = true;
});
await logMeIn();
setState(() {
logging_in = false;
});
}
)
You also probably want to add another bool to prevent multiple clicks of the button while its loading. As a note in using FutureBuilder and essentially any other builder in flutter, you always need to return a widget from the builder function, which your current implementation does not do.