When I sign out of my app, I can press the back button to return to the last page I was on before I signed out. However, weirdly, this unintended functionality only occurs if I navigate to another page besides the home page while I’m signed in.
I am using Firebase to manage my user authentication and I have setup a class to manage authentication as follows:
class AuthService {
// creating a member of class that represents an instance of firebase authentication
final FirebaseAuth _auth = FirebaseAuth.instance;
// create user object based on firebase User class
AppUser? _userFromFirebaseUser(User user) {
// ternary operator
return user != null ? AppUser(uid: user.uid) : null;
}
// auth change user stream
Stream<AppUser> get user {
// mapping firebase User to User
return _auth.authStateChanges().map((User? user) => _userFromFirebaseUser(user!)!);
}
}
// sign in with email & password
Future signIn(String email, String password) async {
try {
UserCredential result = await _auth.signInWithEmailAndPassword(email: email, password: password);
User? firebaseUser = result.user;
return _userFromFirebaseUser(firebaseUser!);
} catch(e) {
print(e.toString());
return null;
}
}
// register with email & password
Future registerWithEmailAndPassword(String email, String password) async {
try {
UserCredential result = await _auth.createUserWithEmailAndPassword(email: email, password: password);
User? firebaseUser = result.user;
return _userFromFirebaseUser(firebaseUser!);
} catch(e) {
print(e.toString());
return null;
}
}
// sign out
Future signOutFunc() async {
try {
_auth.signOut();
} catch(e) {
print('Failed to sign out');
print(e);
}
}
}
This class is constantly listening to user auth changes and will show the sign in or register page depending on if the user is logged in.
class Wrapper extends StatelessWidget {
const Wrapper({super.key});
@override
Widget build(BuildContext context) {
final user = Provider.of<AppUser?>(context);
print(user);
// return either HomePage or Athenticate Widget
// listening to auth changes from stream
if (user == null) {
return Authenticate();
} else {
return MyHomePage();
}
}
}
This widget directs the user to the register page when they sign out.
class Authenticate extends StatefulWidget {
const Authenticate({super.key});
@override
State<Authenticate> createState() => _AuthenticateState();
}
class _AuthenticateState extends State<Authenticate> {
@override
Widget build(BuildContext context) {
return RegisterPage();
}
}
This is how I generally manage navigation in my app once the user is logged in:
class RouteGenerator {
// static function to generate route
static Route<dynamic> generateRoute(RouteSettings settings) {
// getting arguments passed in while callling Navigator.pushNamed()
final args = settings.arguments as Map<String, dynamic>?;
// checking if the name of the route is the home route
switch (settings.name) {
case '/':
return MaterialPageRoute(builder: (_) => MyHomePage());
case 'ReceiptExplorer':
return MaterialPageRoute(builder: (_) => ReceiptExplorer());
case 'SignInPage':
return MaterialPageRoute(builder: (_) => SignInPage());
case 'PicturePreviewPage':
final imagePath = args!['imagePath'] as String;
return MaterialPageRoute(
builder: (_) => PicturePreviewPage(imagePath: imagePath));
case 'RegisterPage':
return MaterialPageRoute(builder: (_) => RegisterPage());
default:
return _errorRoute();
}
}
}
Route<dynamic> _errorRoute() {
return MaterialPageRoute(builder: (_) {
return Scaffold(
appBar: AppBar(
title: Text("Error"),
),
body: Center(
child: Text('ERROR'),
));
});
}
Here is my main.dart where I use the stream provider to provide a stream of AppUser (a custom class that abstracts away all the unecessary information returned when I retrieve a firebase User) objects to its child widgets.
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(MyApp());
}
// ignore: use_key_in_widget_constructors
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return StreamProvider<AppUser?>.value
( initialData: null,
catchError: (User, AppUser) => null,
value: AuthService().user,
child: const MaterialApp(
debugShowCheckedModeBanner: false,
home: Wrapper(),
initialRoute: "/",
onGenerateRoute: RouteGenerator.generateRoute,
),
);
}
}
I have tried returning the SignInPage in the Authenticate Widget instead of the RegisterPage, however this didn’t change anything. I have also updated the Authenticate Widget with the following code to try to replace the top of the widget stack with a new page however this also didn’t change anything.
class Authenticate extends StatefulWidget {
const Authenticate({Key? key});
@override
State<Authenticate> createState() => _AuthenticateState();
}
class _AuthenticateState extends State<Authenticate> {
@override
void initState() {
super.initState();
WidgetsBinding.instance?.addPostFrameCallback((_) {
Navigator.of(context).pushReplacementNamed('SignInPage');
});
}
@override
Widget build(BuildContext context) {
return const Scaffold(
body: Center(
child: CircularProgressIndicator(),
),
);
}
}
I have seen some other posts where people use:
Navigator.of(context).popUntil(ModalRoute.withName('/')); // pops the stack back to the home page
or
Navigator.of(context).pushNamedAndRemoveUntil('/', (route) => false)
…but I’m not sure how I can implement these since my home (/) route is the home page of my app and not the sign in or register page so these methods would only return me back to the home page of the app when I sign out of the app which is undesirable.
To summarise, I would like some guidance on why there is even a back button shown on the screen when I sign out of the app and it takes me to the register page and why when I press this back button, it takes me to the picture preview page from when I was signed in.
Here is a video to more easily demonstrate what my issue is:
https://youtu.be/Kl4KOwRgHS0
2
Answers
I extended the
initState
function of theAuthenticateState
widget and usedNavigator.pushNamedAndRemoveUntil()
which has solved my problem.To remove the backbutton from your registration page.
And if you need to show it (in case you want to navigate to register page from another page, by using Navigator.push(…)) then add it manually using (leading:) parameter or AppBar.