What is Causing AngularFire authState behavior? - ionic-framework

I am new to both AngularFire and Ionic. I followed this tutorial to add Firebase Auth to my ionic project.
HomePage is the root page. It checks the authState to determine if the user is logged in or not. If not, it redirects to the LoginPage. On successful login, it once again sets the HomePage as root. It is not working as expected.
Here are the logs from the console:
Not logged in. Navigating to login page.
login.ts:27 ionViewDidLoad LoginPage
home.ts:22 User logged in. UID: taiNC6n64BP4gD8jTcnXUu53npc2
home.ts:22 User logged in. UID: taiNC6n64BP4gD8jTcnXUu53npc2
2home.ts:27 Not logged in. Navigating to login page.
login.ts:27 ionViewDidLoad LoginPage
login.ts:27 ionViewDidLoad LoginPage
Relevant code on the home page:
constructor(private afAuth: AngularFireAuth, public navCtrl: NavController, public navParams: NavParams) {
this.afAuth.authState.subscribe(res => {
if (res && res.uid) {
console.log("User logged in. UID: " + res.uid);
//Do nothing
} else {
//Push them to the login page
console.log("Not logged in. Navigating to login page.");
this.navCtrl.setRoot('LoginPage');
}
});
}
Code from login page:
async login(user: User){
try {
const result = await this.afAuth.auth.signInWithEmailAndPassword(user.email, user.password);
if (result) {
this.navCtrl.setRoot('HomePage');
}
}
catch (e) {
console.error(e);
}
}
As you can see from the logs, it correctly shows that the user is logged out of the app on the initial load and redirects to the login page. The home page is then reset as root. The authStat.subscribe is being hit 4 times. And the second two times the user is no longer available. What is causing this and how can I make the login persistent? According to the AngularFire docs, the default behavior is persistent login.
--UPDATE--
I tried the solution below. Now my logs look like this:
ionViewDidLoad HomePage
app.component.ts:26 Not logged in.
login.ts:27 ionViewDidLoad LoginPage
app.component.ts:23 Logged in.
home.ts:24 ionViewDidLoad HomePage
home.ts:24 ionViewDidLoad HomePage
app.component.ts:26 Not logged in.
login.ts:27 ionViewDidLoad LoginPage
And the code in my app.component.ts file:
export class MyApp {
#ViewChild(Nav) nav: Nav;
rootPage:any = 'HomePage';
constructor(private afAuth: AngularFireAuth, platform: Platform, statusBar: StatusBar, splashScreen: SplashScreen) {
platform.ready().then(() => {
// Okay, so the platform is ready and our plugins are available.
// Here you can do any higher level native things you might need.
statusBar.styleDefault();
splashScreen.hide();
this.afAuth.auth.onAuthStateChanged(user => {
if (user){
console.log("Logged in.");
this.nav.setRoot('HomePage');
} else {
console.log("Not logged in.");
this.nav.setRoot('LoginPage');
}
});
});
}
}

This could be caused by multiple subscriptions to the authstate observable. Also make sure to unsubscribe onDestroy.

Try to check the login condition in app.component.ts file of your project and first set the root page to any. After checking the login condition, set the root page. Here is a screenshot for reference.

Related

Flutter firebase auth login is not updating to home page

I am trying to update the home of MaterialApp widget depending on whether the user has sign up or not.
Below is the code inside the state of ``MaterialApp```
String? _userUid;
#override
void initState() {
FirebaseAuth.instance.authStateChanges().listen((user) {
//print('changed');
print(user?.uid);
updateUser(user?.uid);
});
super.initState();
}
void updateUser(String? USER_UID) {
setState(() {
_userUid = USER_UID;
});
}
Below is the code for the home property
home: _userUid == null ? const Onbaording2() : const HomeScreen(),
Somewhere inside the widget tree
final user = await _firebaseAuth.createUserWithEmailAndPassword(
email: email!,
password: password!,
);
After running the code above, there is a user created in firebase but the screen does not change.
Also the signOut works perfectly by signing me out when I use firebaseAuth.instance.signOut();
even if you change your variable it will not be redirected to the home screen because materialapp is only called when you first load or restart the app so rather than adding this condtion in home page or other way can be
#override
void initState() {
FirebaseAuth.instance.authStateChanges().listen((user) {
//print('changed');
print(user?.uid);
if(user == null){ navigate to onboarding }
else{ navigate to home page }
});
super.initState();
}

How to handle redirects from initState in Flutter

I am using the plugin flutter_local_notifications to show a user notifications on the app. I have managed to show the notifications on the app and the user can click on the notifications, the user will be redirected to see all pending items from the notification in a notification page. I have set the function to detect the onclick on initstate. The whole function and method to show notification I have implemented it on the homescreen.
#override
void initState() {
super.initState();
initPlatformState();
getNotifications();
NotificationApi.init(initScheduled: true);
init();
_configureSelectNotificationSubject(); //function to redirect to Notifications page
}
which goes to
void _configureSelectNotificationSubject() {
print("clicked is the notification");
NotificationApi.onNotifications.stream.listen((String? payload) async {
await Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(builder: (context) => WANotifications(current: current)),
(Route<dynamic> route) =>
true);
});
}
The challenge am facing with this implementation is that when a user clicks to go to the home screen , the user gets redirected automatically to the notifications page from the home screen without his/her consent. The redirect should only occur when they click on the notifications.
How can I set the redirect to only occur when the user clicks the notification only and not when they click to go to home screen
You can achieve this with the help of didNotificationLaunchApp.
bool get didNotificationLaunchApp =>
notificationAppLaunchDetails?.didNotificationLaunchApp ?? false;
// Use here,
if (notificationAppLaunchDetails?.didNotificationLaunchApp ?? false) {
selectedNotificationPayload = notificationAppLaunchDetails!.payload;
initialRoute = SecondPage.routeName;
}
Pl check full example here : https://pub.dev/packages/flutter_local_notifications/example
For non main file :
#override
void initState() {
super.initState();
localNotification(); // call below localNotification() here.
}
localNotification() async {
final NotificationAppLaunchDetails? notificationAppLaunchDetails =
await flutterLocalNotificationsPlugin!.getNotificationAppLaunchDetails();
if (notificationAppLaunchDetails?.didNotificationLaunchApp ?? false) {
// redirect to new screen if true.
}
}

How to forbid navigating back to Login page after successful login?

I have a question. How to achieve this behavior using auto_route's AutoRedirectGuard:
User opens Flutter app in browser but session expired and is redirected to the Login Page.
User successfully logs in.
User is redirected to Home page.
User cannot click the "back" button and see Login page again.
1, 2, 3 is working. I just can't get the 4th step right. This is the code of the AuthGuard:
class AuthGuard extends AutoRedirectGuard {
final AuthService authService;
bool isLoggedIn;
AuthGuard(this.authService, this.isLoggedIn) {
authService.authChanges.listen((isLoggedIn) {
if (this.isLoggedIn != isLoggedIn) {
this.isLoggedIn = isLoggedIn;
reevaluate(strategy: const ReevaluationStrategy.rePushFirstGuardedRouteAndUp());
}
});
}
#override
Future<void> onNavigation(NavigationResolver resolver, StackRouter router) async {
if (await authService.isLoggedIn()) {
resolver.next();
} else {
redirect(const LoginRoute(), resolver: resolver);
}
}
}
If the user is logged-in and navigating to login redirect them to home page. In onNavigation function.
This is covered in a similar post that you can read the OP here.
You can set up a gate in main.dart conditioned on authentication, and use Navigator.pushReplacement when leaving the AuthScreen.
MaterialApp(
...
home: isLoggedIn ? HomeScreen() : AuthScreen(),
...
);
You can add an after login callback in LoginPage
On your LoginPage, add onLoginCallback parameter
final void Function(bool)? onLoginCallback;
const LoginPage({
Key? key,
this.onLoginCallback,
}) : super(key: key);
and then call it whenever user is done logging in
onLoginCallback?.call(true);
In your AuthGuard
#override
Future<void> onNavigation(NavigationResolver resolver, StackRouter router) async {
if (await authService.isLoggedIn()) {
resolver.next();
} else {
router.push(LoginRoute(
onLoginCallback: (success) {
if (success) {
resolver.next();
router.removeLast(); // <- here is the part so that the user can't go back to login
}
},
));
}
}

Ionic 4/5: exit from the app pressing back button

I need to exit from the app when the back button is pressed only in a certain page; in particular, let's say I have I have the app.component.ts and I have a page called HomePage. In app-routing.module.ts I have this routes:
const routes: Routes = [
{ path: '', redirectTo: 'home', pathMatch: 'full' },
{ path: 'home', loadChildren: './public/home/home.module#HomePageModule' }
];
So whever the user opens the app, it will see the HomePage as first page. If the user presses the back button, now it reloads the same page forever; I want to catch that pressing and exit the app. I've tried to use this in home.page.ts, but nothing happens:
backButtonSubscription: any;
this.backButtonSubscription = this.platform.backButton.subscribe(async () => {
console.log('lets exit!');
navigator['app'].exitApp();
});
Even the console.log is printed, as if the event is not caught. I've also tried with this:
this.platform.backButton.subscribeWithPriority(99999, () => {
navigator['app'].exitApp();
});
but the result is the same, so I'm wondering how it can be solved. I've found a lots of questions online, but there's no explanation that seems to solve this issue or to give a workaround.
Thanks for your help!
if you are using ionic 4 angular . try this
in AppComponent -> initializeApp
add
this.platform.backButton.subscribeWithPriority(0, () => {
navigator[‘app’].exitApp();
});
here is an example
initializeApp() {
this.platform.ready().then(() => {
this.statusBar.styleDefault();
this.splashScreen.hide();
this.platform.backButton.subscribeWithPriority(0, () => {
navigator['app'].exitApp();
});
});
}
in my case im using capacitor (ionic 5), try this if you're using capacitor too
import { Plugins } from '#capacitor/core';
const { App } = Plugins;
initializeApp() {
App.addListener('backButton', () => {
App.exitApp();
});
}
Ionic 5.
Another option its to use Platform, for me it woks well in case of using tabs:
p.s. better to use "init" service instead of app.component.ts to keep app init flow clean and obvious
constructor(
private platform: Platform,
private router: Router
) {
this.platform.backButton.subscribeWithPriority(-1, () => {
const url = this.router.url;
if (url === '/tabs/not-home') {
this.router.navigate(['/tabs/home']);
} else if (url === '/tabs/home') {
App.exitApp();
}
});
}

how is the best way to manage session and navigation in flutter web

I have 2 web pages on my proyect
I need to manage the user session. If the user is logged it appears the home page, and if the user is not logged it appears the login page.
I am using a provider to check the state of the session
#override
Widget build(BuildContext context) {
return Consumer<LoginGeneralNotifier>(
builder: (context, appGenaralNotifier, _) {
return appGenaralNotifier.getLogged()
? HomePage()
: LoginPage();
},
);
}
}
The problem is that the url does not change after I am logged
You may want to use Navigator.of(context).pushNamed(HomePageNamedRoute);
This can be done in initState as shown here:
Flutter Redirect to a page on initState
what I do is
I have created a class where I am keeping all the global variables
there I create a variable bool loginStatus;
and create two methods get and set for handling the value
and whenever login is successful I set the value to true loginStatus=true;
and on the new page in init() method I am checking the value of variable if value is false then throw the user to login page else continue
class GlobalValues{
static bool loginStatus;
static void setLoginStatus(bool val) {
loginStatus = val;
}
static bool getLoginStatus() {
return loginStatus == null ? false : loginStatus;
}
}
on login page if login is successful
GlobalValues.setLoginStatus(true);
on other class pages in init method
#override
void initState() {
if (GlobalValues.getLoginStatus()) {
// call your login page class here
//Navigator.of(context).pushNamed(LoginPageNamedRoute);
Navigator.pop(context);
}
super.initState();
}
Global variables are highly-frowned upon in the programming world (unless absolutely no other alternatives are available). Use user sessions instead. Check out FlutterSession. The package adds user session support in Flutter and is easy to use.
// Store value to session
await FlutterSession().set("token", myJWTToken);
// Retrieve item from session
dynamic token = await FlutterSession().get("token");