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

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();
}
});
}

Related

Infinite loop when navigating between pages

I'm trying to get my website logout done in Flutter to work. I am using auto_route in my project to navigate between pages. The problem is found when I try to redirect from the current page (Dashboard for example) to the Login page, it goes into an endless loop, verifying the guard associated with the Dashboard over and over again. This is my code.
AutoRoutes:
#MaterialAutoRouter(
replaceInRouteName: 'Page,Route',
routes: <AutoRoute>[
AutoRoute(
path: RouteGlobals.root,
name: "Root",
page: AuthLayout,
children: [
AutoRoute(path: "", page: LoginView, guards: [AccessToLogin]),
],
),
AutoRoute(
path: RouteGlobals.dashboard,
name: "DashBoard",
page: DashboardLayout,
guards: [IsAuthenticated, RoleGuard],
(...)
Guards:
class AccessToLogin extends AutoRouteGuard {
final AuthProvider authProvider;
AccessToLogin(this.authProvider);
#override
void onNavigation(NavigationResolver resolver, StackRouter router) async {
if (!await authProvider.isAuthenticated()) {
resolver.next(true);
}
else {
router.replaceNamed(RouteGlobals.dashboard);
}
}
}
class IsAuthenticated extends AutoRouteGuard {
IsAuthenticated();
#override
void onNavigation(NavigationResolver resolver, StackRouter router) async {
if (SharedPreferencesManager().getIsAuthenticated()) {
resolver.next(true);
} else {
router.navigateNamed(RouteGlobals.root);
}
}
}
These are the methods that are called to log in and log out respectively:
login(String email, String password, BuildContext context) async {
setAuthStatus(AuthStatus.checking);
final res = await _logInUseCase(
LogIn.Params(LogInEntity(email: email, pass: password)));
res.fold((l) {
showException(message: l.message);
setAuthStatus(AuthStatus.notAuthenticated);
}, (r) {
setAuthStatus(AuthStatus.authenticated);
this._sharedPreferencesManager.setIsAuthenticated(true);
context.router.replaceNamed(RouteGlobals.dashboard);
});
}
logout(BuildContext context) async {
setAuthStatus(AuthStatus.checking);
var resp = await _logOutUseCase(NoParams());
resp.fold((l) {
showException(message: l.message);
setAuthStatus(AuthStatus.authenticated);
}, (r) {
_sharedPreferencesManager.setIsAuthenticated(false);
setAuthStatus(AuthStatus.notAuthenticated);
context.router.replaceNamed(
RouteGlobals.root,
);
});
}
Note. When the login is performed, the infinite recursion does not occur and the Dashboard is accessed, but in both Guards (AccessToLogin and IsAuthenticated) the onNavigation method is called, which I do not understand because if I am not mistaken, it should only be called in the guard IsAuthenticated.
What am I doing wrong?

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
}
},
));
}
}

Flutter webview plugin onUrlChanged is not working

I am trying to use the web view plugin in the flutter with the following code. However, I am only receiving the first 2 debug messages to the console. Does anyone know why this is? Could someone be able to point me in the direction of the answer? I found out about putting the listener to initState but that didn't work either.
Any solutions?
Here is my code for the widget.
(Installing on android btw)
Widget webView() {
// OPEN WEBVIEW ACCORDING TO URL GIVEN
print("debug");
flutterWebviewPlugin.launch(instagram.url);
// LISTEN CHANGES
print("debug1");
flutterWebviewPlugin.onUrlChanged.listen((String url) async {
print("debug2");
// IF SUCCESS LOGIN
if (url.contains(instagram.redirectUri)) {
instagram.getAuthorizationCode(url);
instagram.getTokenAndUserID().then((isDone) {
if (isDone) {
instagram.getLongLivedToken().then((isDone) {
if(isDone){
prefs.setString('token', instagram.longLivedAccessToken);
instagram.getUserProfile().then((isDone){
if(isDone){
instagram.getAllMedias();
setState(() {
images = instagram.imageUrls;
});
}
});
}
});
}
});
}
print("Login successful");
// NOW WE CAN CLOSE THE WEBVIEW
flutterWebviewPlugin.close();
Navigator.pop(context);
});
return WebviewScaffold(
resizeToAvoidBottomInset: true,
url: instagram.url,
);
}

How do I open a specific page on onesignal notification click on flutter?

I am using OneSignal push notification service and I want to open the app directly to specific page on notification click. I am sending the page through data. I tried navigator.push but it didn't work i guess because of context issue. I am calling _initializeonesignal() after login which contains onesignal init and the following code.
OneSignal.shared.setNotificationOpenedHandler((notification) {
var notify = notification.notification.payload.additionalData;
if (notify["type"] == "message") {
//open DM(user: notify["id"])
}
if (notify["type"] == "user") {
//open Profileo(notify["id"])
}
if (notify["type"] == "post") {
//open ViewPost(notify["id"])
}
print('Opened');
});
You will need to register a global Navigator handle in your main application scaffold -- then you can use it in your notification handlers..
So -- in our app in our main App we have :
// Initialize our global NavigatorKey
globals.navigatorKey = GlobalKey<NavigatorState>();
...
return MaterialApp(
title: 'MissionMode Mobile',
theme: theme,
initialRoute: _initialRoute,
onGenerateRoute: globals.router.generator,
navigatorKey: globals.navigatorKey,
);
The key is the navigatorKey: part and saving it to somewhere you can access somewhere else ..
Then in your handler:
OneSignal.shared.setNotificationOpenedHandler(_handleNotificationOpened);
...
// What to do when the user opens/taps on a notification
void _handleNotificationOpened(OSNotificationOpenedResult result) {
print('[notification_service - _handleNotificationOpened()');
print(
"Opened notification: ${result.notification.jsonRepresentation().replaceAll("\\n", "\n")}");
// Since the only thing we can get current are new Alerts -- go to the Alert screen
globals.navigatorKey.currentState.pushNamed('/home');
}
That should do the trick -- does for us anyway :)
It's simple, by using onesignal, you can create system call from kotlin to flutter
In my case, I had to take the data in the URL from a notification that comes from onesignal in WordPress:
package packageName.com
import android.os.Bundle
import androidx.annotation.NonNull;
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugins.GeneratedPluginRegistrant
// import io.flutter.plugins.firebaseadmob.FirebaseAdMobPlugin;
private val CHANNEL = "poc.deeplink.flutter.dev/channel"
private var startString: String? = null
class MainActivity: FlutterActivity() {
override fun configureFlutterEngine(#NonNull flutterEngine: FlutterEngine) {
GeneratedPluginRegistrant.registerWith(flutterEngine);
MethodChannel(flutterEngine.dartExecutor, CHANNEL).setMethodCallHandler { call, result ->
if (call.method == "initialLink") {
if (startString != null) {
result.success(startString)
}
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val intent = getIntent()
startString = intent.data?.toString()
}
}
This I'm taking data from onCreate, yet only when clicking on the notification, I will take the "intent" data and then I will send it to my flutter code in the following class:
import 'dart:async';
import 'package:flutter/services.dart';
class MyNotificationHandler {
//Method channel creation
static const platform =
const MethodChannel('poc.deeplink.flutter.dev/channel');
//Method channel creation
static String url;
static String postID;
static onRedirected(String uri) {
url = uri;
postID = url.split('/').toList()[3];
}
static Future<String> startUri() async {
try {
return platform.invokeMethod('initialLink');
} on PlatformException catch (e) {
return "Failed to Invoke: '${e.message}'.";
}
}
//Adding the listener into contructor
MyNotificationHandler() {
//Checking application start by deep link
startUri().then(onRedirected);
}
}
Here I'm taking data from a WordPress URL, the last word after the 4ed '/' which is the id of the post.
now how to use it and call it, as I created it static I will use it in my code when the first page loads,
import 'package:com/config/LocalNotification.dart';
class MyLoadingPage extends StatefulWidget {
MyLoadingPage() {
MyNotificationHandler.startUri().then(MyNotificationHandler.onRedirected);
}
#override
_MyLoadingPageState createState() => _MyLoadingPageState();
}
...
This page will load the data from my WordPress API.
so after loading the data from the database, I will check if a value of the id, and navigate to the article page, the example in my home page:
....
#override
void initState() {
MyViewWidgets.generalScaffoldKey = _scaffoldKey;
myWidgetPosts = MyPostsOnTheWall(MyPost.allMyPosts, loadingHandler);
MyHomePAge.myState = this;
super.initState();
if (MyNotificationHandler.postID != null) {
Future.delayed(Duration(milliseconds: 250)).then((value) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => MyArticlePage(MyPost.allMyPosts
.firstWhere((element) =>
element.id == MyNotificationHandler.postID))));
});
}
}
....
The secrete is in kotlin or Java by using that call from kotlin to fluter or from java to flutter, I think you will have to do the same with ios, I will leave an article that helped me.
https://medium.com/flutter-community/deep-links-and-flutter-applications-how-to-handle-them-properly-8c9865af9283
I resolved the same problems, as below:
In the main screen file MyApp.dart
#override
void initState() {
OneSignalWapper.handleClickNotification(context);
}
OneSignalWapper.dart :
static void handleClickNotification(BuildContext context) {
OneSignal.shared
.setNotificationOpenedHandler((OSNotificationOpenedResult result) async {
try {
var id = await result.notification.payload.additionalData["data_id"];
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => PostDetailsScreen.newInstance('$id')));
} catch (e, stacktrace) {
log(e);
}
});
}
You can use this Code:
final GlobalKey<NavigatorState> navigatorKey = new GlobalKey<NavigatorState>();
OneSignal.shared.setNotificationOpenedHandler((result) {
navigatorKey.currentState.push(
MaterialPageRoute(
builder: (context) => YourPage(),
),
);
});
MaterialApp(
home: SplashScreen(),
navigatorKey: navigatorKey,
)
I find the solution:
On your home screen, set the handler. And, before this, set on your configuration notification this way
First:
Map<String, dynamic> additional = {
"route": 'detail',
"userId": widget.userId
};
await OneSignal.shared.postNotification(OSCreateNotification(
playerIds: userToken,
content: 'your content',
heading: 'your heading',
additionalData: additional,
androidLargeIcon:'any icon'));
Second:
OneSignal.shared.setNotificationOpenedHandler(
(OSNotificationOpenedResult action) async {
Map<String, dynamic> dataNotification =
action.notification.payload.additionalData;
if (dataNotification.containsValue('detailPage')) {
await Navigator.push(
context,
new MaterialPageRoute(
builder: (context) => new DetailScreen(
userId: dataNotification['userId'],
),
).catchError((onError) {
print(onError);
});
}

What is Causing AngularFire authState behavior?

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.