Ionic-framework: Navigation freezes sometimes when using the ionViewCanEnter hook

Created on 26 Sep 2017  路  15Comments  路  Source: ionic-team/ionic-framework

Ionic version: (check one with "x")
(For Ionic 1.x issues, please use https://github.com/ionic-team/ionic-v1)
[ ] 2.x
[ x] 3.x
[ ] 4.x

I'm submitting a ... (check one with "x")
[ x] bug report
[ ] feature request

Current behavior:
When using the hook "ionViewCanEnter" and returning a promise, the navigation can freeze sometimes when a back navigation is performed.
In that case, it is not possible to navigate back or forward.
I suppose that there is a timing problem when the promise takes too long?
This bug cannot be reproduced in every case.
Unfortunately it occurs often enough to be considered a blocker in our productive app.

By some debugging I could see the following:
When the naviagtion is stuck for the first time, the promise returned by method _transitionStart in NavControllerBase is never fulfilled. The call "transition.onFinish(resolve)" does not call the given resolve callback.

Expected behavior:
The navigation should work, no matter how long the promise returned by ionViewCanEnter needs to fulfill.

Steps to reproduce:

  • checkout the github project (see below)
  • add the iOS platform
  • run the app on an iPad (my OS is iOS 11)
  • do this until the navigation freezes

    • use the button to go the next page

    • on the next page, hit the back button as fast and often as you can ;)

Related code:
See https://github.com/chhe88/IonViewCanEnteExample
The first page has an implementation of ionViewCanEnter which causes a delay of 5 secs.

Ionic info: (run ionic info from a terminal/cmd prompt and paste output below):
cli packages: (/usr/local/lib/node_modules)

@ionic/cli-utils  : 1.12.0
ionic (Ionic CLI) : 3.12.0

global packages:

cordova (Cordova CLI) : 7.0.1

local packages:

@ionic/app-scripts : 2.1.4
Cordova Platforms  : ios 4.4.0
Ionic Framework    : ionic-angular 3.6.1

System:

ios-deploy : 1.9.2 
ios-sim    : 6.0.0 
Node       : v7.8.0
npm        : 4.6.1 
OS         : macOS Sierra
Xcode      : Xcode 9.0 Build version 9A235

Misc:

backend : legacy

screen shot 2017-09-26 at 09 27 30

screen shot 2017-09-26 at 09 27 40

Thanks in advance for your help! :)

stale issue

All 15 comments

Wow I have exactly the same issue, I didn't expect there to be anyone else with that problem. I can click back and forth a 100 times in my app without issues but sometimes after X times the navigation dies. I also use IonViewCanEnter hooks everywhere and it is totally possible the server takes a while to respond sometimes.

I tried adding logging to IonViewCanEnter but when the router died that code doesn't even seem to get called anymore, it's just dead.

An ugly workaround which may cause security issues:

  ionViewCanEnter(): Promise<boolean> {
    return Observable.of(true).do(() => {
      // do your magic here and navigate explicitly to somewhere else (e.g. to your login page) if necessary
    }).toPromise();
  }

Hello! Thank you for opening an issue with us!

I had time to take a quick look at this earlier. I noticed that in the browser, if I hit the back button more than once, it will toss an exception. The first click tries to go back, the second click causes the root page to go NULL and toss an error. Not ideal.

I'll look at this later, but in the meantime, I will share the strategy I use in my own apps:

  1. I don't like using promises in guards. I find it to be bad UX. Thus I only put stuff in functions like ionViewCanEnter that can be resolved synchronously and then return true or false. This is generally limited to checking a set of permissions or roles fetched on startup and/or login.
  2. I extend the Angular HTTP service like this to handle 401 errors coming back from my data service. In the linked case, which is from an Angular SPA and not an Ionic app, it redirects to the login page, but you can have it do whatever you need to do.
  3. In the page, rather than guarding against user not logged in, I assume they are logged in and attempt the main request for the page's data. If that returns 401, the user gets redirected. This make the app far less chatty than a "am I logged in" request followed by an "OK then give me data" request.

Sorry that the linked example of an extended HTTP service was not for an Ionic app, but the ones I have written for Ionic apps were all for a former employer rather than my own project.

Another idea I have is to perhaps override the back button event controlling everything yourself so you can't get multiple clicks while waiting. I have not had time to try that out yet. I may have time later today.

Hopefully, that can get you started thinking about things you can try, though.

Thank you for using Ionic

Hello @kensodemann,

Thank you for getting back to us this fast, I really appreciate it!

  1. We noticed the uncaught exception as well, this happens when spamming the back button a lot and also when spamming a button that pushes a component.
  2. We do have a use case for using promises in guards, for our application we need to retrieve data before the component is pushed on the stack, so we communicate with a service, get data and then return true. That way the component is pushed with all data their, no flash screens or placeholders needed. This might be a little bit abuse of ionViewCanEnter guards but it works for us beyond this issue.

Sorry I didn't had a look at your code, but do you redirect page in your guards or some functions called by the guards and reject the promise?
For example check if user is still authenticated, if not redirect to login page and reject guard.
Could this be?

@mburger81
Yes, this is exactly my intention.
In my app, every page that requires authentication extends from a class that implements the following method:

ionViewCanEnter(): Promise<boolean> {
  return authenticate()  // authenticate returns an Obervable<boolean>
    .do(authenticated => {
      if (!authenticated) {
        // navigate to the login page
      }
    }).toPromise();
}

It works well in most cases, but sometimes it produces this bug, although no re-authentication is performed.

This was a breaking change in a NOT major releasr, there is also an issue from me which is documenting this.
You can not redirect page and reject the guard.
Probably they would never resolve this, in there opinion this is not the right way to use a router, guards and security.

Adding code such as this to next.ts seems to alleviate the issue you are having:

export class NextPage {
  private navigating = false;

  @ViewChild(Navbar) navBar: Navbar;

  constructor(public navCtrl: NavController) { }

  ionViewDidLoad() {
    this.navBar.backButtonClick = (e: UIEvent) => {
      if (!this.navigating) {
        this.navigating = true;
        this.navCtrl.pop();
      }
    }
  }
}

Not sure if that will help the general flow of your app, but it will at least prevent over-clicking when the async bits are running long.

@mburger81 so the ionViewCanEnter no longer waits on a promise to either return true or false?

@kensodemann Is there a way that this could be handled in ionic framework itself. When using the built in back buttons there's no reason they should be able to be triggered twice. The same with screen pushes actually, as long as the push hasn't been resolved another push action should probably be discarded.

This seems like a bug that we need to fix.

Thanks,
Dan

Minor tweak to my workaround above after looking at the existing code more:

    this.navBar.backButtonClick = (e: UIEvent) => {
      if (!this.navigating) {
        this.navigating = true;
        e.preventDefault();   // added these two lines.
        e.stopPropagation();
        this.navCtrl.pop();
      }
    }

@kensodemann
Thanks for your workaround suggestion.
I already tried this in our productive app when I noticed the issue, but like you said, it just alleviates the problem.

This problem can also occur when you hit the back button just once.
I just got the notion that it is better to reproduce when you hit the button repeatedly.

Looking at the Ionic Framework code that handles this, so long as your async ionViewCanEnter() implementation returns a Promise that is eventually fulfilled in some way, this should work. The only time it would freeze is if your Promise is never fulfilled, in which case it isn't really frozen so much as just waiting.

So that leads me to these questions:

  1. does it ever freeze on the first click in your sample app?
  2. is there anything in the console log when this happens?
  3. basically what (if anything) is different between when it works and when it freezes?

@chhe88 Not sure if my post doesn't matter with your bug put for clarification.
You can still use Promise, which works.
But you can not REJECT the promise and redirect to another page.

BTW I read in the forum from some moderators and also developers that Promise in feature would be deprecated, so perhaps it could be better to use boolean instead of Promise in guards. But the problem mentioned by me is the same. You can also not redirect in guards and return false, in this case the navigation would not be redirected.

Thanks for the issue! This issue is being closed due to inactivity. If this is still an issue with the latest version of Ionic, please create a new issue and ensure the template is fully filled out.

Thank you for using Ionic!

Was this page helpful?
0 / 5 - 0 ratings