Nativescript: Question: how to troubleshoot delays on page-to-page navigation

Created on 17 Jun 2017  路  11Comments  路  Source: NativeScript/NativeScript

I am observing significant delays on page to page navigation forwards and backwards. I am using the NS Core, so no Angular to blame it on. At this point all of the data is mocked in the modules or as static assets, so no backends and network delays are involved. I am also navigating to the same page as is currently loaded, as it avoids loading the module (confirmed by logging).

The pages use fairy complex layouts with 4 levels of nesting. It appears that loading XML and preparing layouts is the bottleneck. What tools does NS have to help me with solving the issue?

I already looked into "bruteforcing" the issue through "pre-fetching" next page before user initiates navigation, but as the documentation states, Workers can't pass Javascript objects, so pre-fetching would need to be done in the main thread, and it would freeze the UI. Are any feasible options to do pre-fetching?

question

Most helpful comment

Hi @sserdyuk,

I can't offer a solution but perhaps a workaround as I had a similar issue (with an Angular app, but the same approach/problem applies).

The view I want to navigate to has a very complex nested structure that's being dynamically built based on the data it receives. Takes 1 - 3 seconds to render based on the data, and that would delay navigation.

So what I did is add this to the root node of the nested structure:

<StackLayout *ngIf="listLoaded">
  <!-- complex nested structure here -->
</StackLayout>

And a spinner:

<ActivityIndicator [busy]="!listLoaded" [visibility]="listLoaded ? 'collapse' : 'visible'"></ActivityIndicator>

And in my component there's this:

  ngAfterContentInit(): void {
    // a little delay so the spinner has time to show up
    setTimeout(() => {
      this.listLoaded = true;
    }, 500);
  }

So that delays rendering the complex structure, which means navigation is instant and a spinner is shown, then when rendering finishes the spinner hides and the content is shown.

All 11 comments

Hi @sserdyuk,

I can't offer a solution but perhaps a workaround as I had a similar issue (with an Angular app, but the same approach/problem applies).

The view I want to navigate to has a very complex nested structure that's being dynamically built based on the data it receives. Takes 1 - 3 seconds to render based on the data, and that would delay navigation.

So what I did is add this to the root node of the nested structure:

<StackLayout *ngIf="listLoaded">
  <!-- complex nested structure here -->
</StackLayout>

And a spinner:

<ActivityIndicator [busy]="!listLoaded" [visibility]="listLoaded ? 'collapse' : 'visible'"></ActivityIndicator>

And in my component there's this:

  ngAfterContentInit(): void {
    // a little delay so the spinner has time to show up
    setTimeout(() => {
      this.listLoaded = true;
    }, 500);
  }

So that delays rendering the complex structure, which means navigation is instant and a spinner is shown, then when rendering finishes the spinner hides and the content is shown.

@sserdyuk - Two other options.

  1. Simplify your layout; combine the nested layouts into gridlayouts; this can make a large difference the more layouts you can eliminate into one or two gridlayouts. Some times you need to do a bit of math to re-setup your grid layout to be able to handle your layout. The less the number of Layouts, the faster it will render, each additional Layout will complicate everything.
  1. I wrote the NativeScript-DynamicLoader for PAN (Plain Awesome NativeScript) applications because I had a really complex part of a layout that wasn't actually viewed at that point in time, and the screen was taking forever to build. By splitting out that super complex part of the screen out and then Dynamically loading it into the DOM when needed the navigation time went back to instant, and the Dynamic Loading of that part of the screen was also instant when they hit the point it needed to be visible.

Same problem here.. I have a ListView where the template is a GridLayout with eight Label and the back button navigation isn't smooth.

@EddyVerbruggen Thank you for the idea. I was thinking about something like this, and while there's no "if" attribute in NS Core, something similar can be implemented by taking the complex part into a subpage and using Builder to load and add to the page from the code. It appears that I am moving towards this sort of solution without an alternative.

@NathanaelA Unfortunately, refactoring the layouts to into grids didn't yield much. Specifically, it reduced recorded navigation time from 2973ms to 2959ms. Basically zero effect at a cost of making my views hard to read and maintain, and loosing some UI design freedom. Basically, NS needs to measure and layout the components, and as long as the number of components and the design is the same, grid or nested stacks makes no difference. And I've got logs to prove my point.

Out of the 3 seconds it takes from the start to finish on my low-end LG-VS450, NS spends 1450ms loading the XML and 950ms processing native view events after the page is formed into a fragment. Everything else is peanuts.

A note on how I has been measuring these details: I built a custom trace writer (using some prototypes from the web) that sets "0" timestamp upon first trace point and reports all future lines as difference with it, so the logs are pretty easy to read, they report as "time +1567" for 1567ms into navigation process.

Did I find any room for improvement? Hard to say, but it appears that NS tears down native UI for the page it is navigating from right at the tail of the navigation process. Maybe it could be delayed, but the gain would be only about 200ms in my case.

I am hoping that Telerik's team can find some inspiration on performance improvements from this post. As I added the issue on Friday night, they haven't had a chance to review it. Let's see what they say.

In case someone wants to have look at the logs, I am attaching them and the tracer here. Also below is the code to initialize the tracer.

// in app.ts before application.start
import * as trace from "tns-core-modules/trace";
trace.setCategories(trace.categories.concat(
    // trace.categories.Layout, // add these two for detailed
    // trace.categories.ViewHierarchy, // add these two for detailed
    trace.categories.NativeLifecycle, // these two are for brief
    trace.categories.Navigation // these two are for brief
    ));
// trace.setCategories(trace.categories.All); // this option is extremely detailed
import { setupTimestampConsoleWriter } from "./shared/traceWriter";
setupTimestampConsoleWriter();
trace.enable();

traceWriter.ts.zip

Trace-06-17-17.pdf

@sserdyuk - Any chance you can share the layout. Looking at your log; you look like you are using Stack&Grid layouts pretty excessively. Even in 100% pure Java Code; it is highly recommended to use a few Layouter type items as you can.

I'm really surprised that you can't get more perf out of it; most the time I can condense three or more layers into a single gridlayout layer which improves the perf drastically. However you do lose some readability. I typically add to the XML to help maintain readability. It would be a lot easier to help with perf if we could see the layout.

My DynamicLoader was designed because of this issue; I did have a layout that was a killer; and it ate a lot of time during navigation; but by pushing off part of its load until later it was nearly instant at both the init and at the point I needed to dynamically load this part of the layout to show it. Part of your startup time even though you are attributing it to xml reading is the phone is doing more than just loading (you are also removing the old layout and GC'ing).

@sserdyuk How did you overcome this issue. I do not want to try the plugin because that is commercial and I cannot escape the complex layout right now

I ended up extracting big chunks of the view into separate xml files to lighten up the main file. As the view loaded, I would load the remaining files in this sort of fashion:

let subview = builder.load({
                path: "~/views/folder",
                name: name
            });
parent.addChild(subview);

@JoelAH I responded above.

@sserdyuk Thanks for your reply. Ive implemented your solution but there still a 3 second delay for me

setTimeout seems to work better with it. I am now seeing this error in console: "Error: CSS file found but no page specified. Please specify page in the options"

This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.

Was this page helpful?
0 / 5 - 0 ratings