I put in a PopScope for one of the pages in my flutter app so that users cannot navigate back to the previous screen when they are on this screen. It worked before, I made some other changes but nothing related to PopScope, so I’m a bit lost as to how this could have happened. The page will still allow a user to navigate back to the previous screen if they tap "Back" on the device.
class LoginScreenColor extends StatelessWidget {
const LoginScreenColor({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(
primaryColor: const Color(0xfff51957),
textSelectionTheme: const TextSelectionThemeData(
cursorColor: Color(0xfff51957),
),
inputDecorationTheme: const InputDecorationTheme(
labelStyle: TextStyle(
color: Color(
0xfff51957),
),
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(color: Color(0xfff51957)),
),
),
),
home: LoginScreen(),
);
}
}
class LoginScreen extends StatelessWidget {
LoginScreen({Key? key}) : super(key: key);
final FirebaseAuth _auth = FirebaseAuth.instance;
final GoogleSignIn googleSignIn = GoogleSignIn();
// Email and Pass focusNodes
final FocusNode emailFocusNode = FocusNode();
final FocusNode passwordFocusNode = FocusNode();
// Controllers for Email and Pass
final TextEditingController emailController = TextEditingController();
final TextEditingController passwordController = TextEditingController();
// Continue with Google
Future<UserCredential> signInWithGoogle() async {
final GoogleSignInAccount? googleSignInAccount =
await googleSignIn.signIn();
final GoogleSignInAuthentication googleSignInAuthentication =
await googleSignInAccount!.authentication;
final AuthCredential credential = GoogleAuthProvider.credential(
accessToken: googleSignInAuthentication.accessToken,
idToken: googleSignInAuthentication.idToken,
);
final UserCredential userCredential =
await _auth.signInWithCredential(credential);
// Set a flag to indicate the user has just signed in
SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.setBool('justSignedIn', true);
return userCredential;
}
// Sign in with Email and Password
Future<UserCredential> signInWithEmail() async {
final String email = emailController.text;
final String password = passwordController.text;
final UserCredential userCredential =
await _auth.signInWithEmailAndPassword(
email: email,
password: password,
);
return userCredential;
}
@override
Widget build(BuildContext context) {
return PopScope(
canPop: false,
onPopInvoked: (bool didPop) => showDialog<void>(
context: context,
builder: (BuildContext context) {
return AlertDialog(
backgroundColor: Colors.white,
title: const Text(
'Please Complete Setup',
style: TextStyle(
fontWeight: FontWeight.bold,
fontFamily: 'Montserrat',
),
),
content: const Text(
'Please complete the setup process first, any and all settings can be changed after initial setup.',
style: TextStyle(
fontSize: 20,
fontFamily: 'Montserrat',
),
),
actions: <Widget>[
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text(
'Ok',
style: TextStyle(
color: Color(0xfff51957),
fontFamily: 'Montserrat',
fontSize: 20,
),
),
),
],
);
},
),
child: Scaffold(
body: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Column(
children: <Widget>[
const SizedBox(
height: 30,
),
SizedBox(
height: 200,
width: 200,
child: Lottie.asset('assets/sign.json'),
),
const SizedBox(height: 40.0),
RichText(
text: TextSpan(
children: <TextSpan>[
TextSpan(
text: 'Discover your next ',
style: GoogleFonts.montserrat(
fontSize: 24.0,
fontWeight: FontWeight.bold,
color: Colors.black,
),
),
TextSpan(
text: 'adventure',
style: GoogleFonts.montserrat(
fontSize: 24.0,
fontWeight: FontWeight.bold,
color: const Color(0xfff51957),
),
),
],
),
),
const SizedBox(height: 40.0),
TextField(
controller: emailController,
focusNode: emailFocusNode,
decoration: InputDecoration(
labelText: 'Email',
prefixIcon: const Icon(Icons.email),
labelStyle: TextStyle(
color: emailFocusNode.hasFocus
? const Color(0xfff51957)
: null,
),
),
),
const SizedBox(height: 20.0),
TextField(
controller: passwordController,
focusNode: passwordFocusNode,
obscureText: true,
decoration: InputDecoration(
labelText: 'Password',
prefixIcon: const Icon(Icons.lock),
labelStyle: TextStyle(
color: passwordFocusNode.hasFocus
? const Color(0xfff51957)
: null,
),
),
),
const SizedBox(height: 20.0),
SizedBox(
height: 50,
width: MediaQuery.of(context).size.width - 40.0,
child: ElevatedButton(
onPressed: () async {
try {
await signInWithEmail();
Navigator.push(
context,
PageRouteBuilder(
pageBuilder:
(context, animation, secondaryAnimation) =>
const PreferencesScreen(),
transitionsBuilder: (context, animation,
secondaryAnimation, child) {
var begin = const Offset(1.0, 0.0);
var end = Offset.zero;
var curve = Curves.ease;
var tween = Tween(begin: begin, end: end)
.chain(CurveTween(curve: curve));
return SlideTransition(
position: animation.drive(tween),
child: child,
);
},
),
);
} on FirebaseAuthException catch (e) {
// Handle error
print(e.message);
}
},
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xfff51957),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(15),
),
),
child: Text(
'Sign in',
style: GoogleFonts.montserrat(
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
),
),
const SizedBox(height: 20.0),
Container(
margin: const EdgeInsets.only(top: 16.0),
child: OutlinedButton.icon(
onPressed: () async {
// Handle Google sign-in
final GoogleSignInAccount? googleUser =
await GoogleSignIn().signIn();
if (googleUser != null) {
final GoogleSignInAuthentication googleAuth =
await googleUser.authentication;
final OAuthCredential credential =
GoogleAuthProvider.credential(
accessToken: googleAuth.accessToken,
idToken: googleAuth.idToken,
);
try {
final UserCredential userCredential =
await _auth.signInWithCredential(credential);
// Check if sign-in was successful
if (userCredential.user != null) {
// Navigate to PreferencesScreen
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
const PreferencesScreen()),
);
}
} on FirebaseAuthException catch (e) {
// Handle error
print(e.message);
}
}
},
icon: Image.asset('images/google_logo.png',
width: 24.0, height: 24.0),
label: const Text('Continue with Google'),
style: OutlinedButton.styleFrom(
foregroundColor: Colors.black,
side: const BorderSide(color: Colors.grey)),
),
),
const SizedBox(height: 5.0),
Container(
margin: const EdgeInsets.only(top: 16.0),
child: OutlinedButton.icon(
onPressed: () {
// Handle Apple sign-in
},
icon: Image.asset('images/apple.png',
width: 40.0, height: 40.0),
label: const Text('Continue with Apple'),
style: OutlinedButton.styleFrom(
foregroundColor: Colors.black,
side: const BorderSide(color: Colors.grey)),
),
),
],
),
const SizedBox(height: 20.0), // Add this line
Column(
children: <Widget>[
const Divider(color: Colors.black),
const SizedBox(height: 20.0),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'Don't have an account? ',
style: GoogleFonts.montserrat(
fontSize: 16.0,
color: Colors.black.withOpacity(0.6),
),
),
GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const RegisterScreenColor(),
),
);
},
child: Text(
'Register',
style: GoogleFonts.montserrat(
fontSize: 16.0,
fontWeight: FontWeight.bold,
),
),
),
],
),
],
),
],
),
),
),
),
);
}
}
I tried changing the code like below by adding async, but nothing changed
return PopScope(
canPop: false, // Prevent user from navigating back
onPopInvoked: (bool didPop) async {
return showDialog<void>(
context: context,
builder: (BuildContext context) {
return AlertDialog(
backgroundColor: Colors.white,
title: const Text(
'Please Complete Setup',
style: TextStyle(
fontWeight: FontWeight.bold,
fontFamily: 'Montserrat',
),
),
content: const Text(
'Please complete the setup process first, any and all settings can be changed after initial setup.',
style: TextStyle(
fontSize: 20,
fontFamily: 'Montserrat',
),
),
actions: <Widget>[
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text(
'Ok',
style: TextStyle(
color: Color(0xfff51957),
fontFamily: 'Montserrat',
fontSize: 20,
),
),
),
],
);
},
);
},
child: Scaffold(
// ...
2
Answers
Short-Answer:
PopScope does not work if you replace the class it is in with a MaterialApp class and then set the class that the
PopScope
is in as thehome:
attribute.Example of where PopScope would NOT work:
Solution/Reason:
I found the issue and fixed it. Excuse any wrong terminology used, I'm still very new to Flutter and Dart. My LoginScreen class did not have a MaterialApp in its main widget tree, it stemmed from my previous WelcomeScreen's MaterialApp. But I tried making a MaterialApp for my LoginScreen so that I could set a specific theme for it's fields. Meaning I called
LoginScreenColor
class which then as I understood called the LoginScreen class, and somehow that caused the PopScope widget not to work anymore, I left all PopScope code unchanged, removed the LoginScreenColor class, and added my Theme I wanted to add to my previous WelcomeScreen's MaterialApp.Flutter has deprecated WillPopScope and instead suggests that the new widget is used instead:
The replacement is
PopScope
which uses a completely different API. It’s a pain to migrate but it’s an Android thing so Flutter is stuck having to change how it works:Here is the migration guide