Caching and pre-rendering DOM for Mobile performance


#1

When building “ambitious” applications with Ember, sometimes there are a few routes that get trafficked heavily and often.

On mobile, these routes start to kill your application’s performance.

##Problematic Route Example One The iOS app I currently work on has a few such routes. The worst offender is the main index. It’s a beast to re-render, even with HTMLBars. Worse, it’s a long list with data that goes stale often.

I’ve significantly improved it’s performance by using ember-cloaking (I’m maintaining an ember-cli-addon branch), by refreshing data in the background and not in the model hook, unbinding as much as I could, and ensuring that no app tasks would interfere with rendering or animation via carefully managed use Ember.run.schedule (I’ve added a few custom queues). GPU acceleration is used wisely, and WebWorkers are used for a lot of intensive stuff.

I even thought about jumping into work on the MagickCollectionView with @ebryn and @mmun to see if that would help (it would for the second case below, not this one). Even improving re-renders though isn’t going to solve this route’s issues, because the crux of the problem is that this is a short lived but highly complex route.

Even with all the optimizations, this route simply does not render quickly and easily enough to allow a user to quickly navigate around the app in a “just passing by” manner.

e.g.

index > conversation > index > settings > index > customer lookup > customer info > index > conversation

With as heavily trafficked as index is, I’ve started looking at ways to keep it rendered. I suspect better . improved solutions to this problem will become available as the FastBoot and Glimmer mods land.

Some of the approaches I’ve considered:

  1. Trying to build a CacheableRoute that saves it’s DOM in a fragment and “fastboots” itself when re-entered.
  2. The Index is actually conversations/index. Move it’s template onto the resource template to keep it rendered, then only show it if the current route is the Index. Could possibly be left in a document fragment until needed (do bindings still work in fragments?).
  3. Using a routable-via-queryParms off-canvas component to render index on the application view and keep it out of site when unneeded. This could combined with cloaking to protect the DOM from getting to overburdened.

I don’t think (1) is possible just yet.

The trouble with (2) is that the conversations resource is left often, so this would only improve the experience half of the time.

The other problem with (2) and (3) is that a conversations/single is also a long list with it’s own rendering challenges. Cluttering the DOM is not ideal, unless a fragment approach will work.

I’m working on implementing (3), so we’ll find out soon enough if bindings will still work in elements removed from the primary DOM tree or not.

##Problematic Route Example 2

The conversations.single route’s complexity ranges from simple ( a couple of messages) to highly complex with:

  • a long conversation history
  • infinite scroll into previous conversations with this customer
  • lots of images
  • dynamic data messages (mostly tabular, also with images)
  • viewport resizing (when the keyboard opens/closes)

This view would definitely be helped by MagickCollectionView. I suspect it would be helped by it even after Glimmer lands, and as soon a some time frees up it’s near the top of my contributions-to-make list (a robust liquid-fire-esque canvas solution is coming first, and a series of improvements to ember-cloaking, yup both are part of the solution from above :wink:

I’ve read as much as is available about how Sencha built FastBook, because their NewsFeed solution is I think as close an example of complexity as I can give.

The challenge this view presents is two-fold. First, during an active conversation, it’s entered and left a lot.

Example (A and B are both conversations).

A > index > B (send/get message) > customer info > B > index > A > customer info > A (send/get message) (scroll back in history) (send message)

So the trouble here isn’t just re-renders (which Glimmer will mostly solve), it’s also initial renders. In this app, a user navigates routes often and quickly, and liquid-fire is used to animate transitions. Constantly rebuilding a route’s view is expensive and detrimental to the experience.

Most of the solutions here also involve DOM cacheing, but unlike the index, we can’t leave these views alive by adding them to the resource or the application template. Hopefully the CacheableRoute idea becomes a thing, but until it does this pain got me investigating, and I think I’ve identified 4 areas in which I can improve ember-cloaking to help out.

  1. better element recycling
  2. cache view heights instead of storing them inDOM
  3. track what elements are visible and render only those elements on return to start
  4. push removed views into fragments first, don’t tear them down

The basic idea with (4) is

  • render X visible items
  • render Y items off screen in either direction
  • keep Z items in document fragments
  • tear down the rest

A parting note, this App was also an Android app at one time, but this precise pain point was a huge factor in eventually going native. All the other non-chrome-bug (partial fix coming btw) related pain points eventually got solved. With Glimmer and a readily available smart cacheing, building truly amazing mobile apps with Ember will be an ember create my-mobile-app away.


#2

I’ve only been working with ember for the last 8 months and currently building a app with this exact issue. Glad to see I’m not the only one. lol im just not smart enough to figure out how to solve it. But glad ember has a awesome community so I can start somewhere.


#3

I’ve solved this since the post and am close to publishing an addon with helpers for these sorts of cases. You can override a teardown of a view and it’s element, remove the element from the DOM, store a reference to the view, and then reuse that view later.


#4

You can see my work here: https://github.com/runspired/smoke-and-mirrors

The magic-collection-view and core-container-view are WIP versions of MagicCollectionView that I’m working on here for the moment.

the most complete code (and functional, both are now in use by me) are occlusion-collection and cacheable-view.

This is very VERY WIP and I don’t recommend installing it, but you can likely get some insights from it.


Tips on targeting both mobile and tablet
#5

Awesome I also found your other post about phonegap and dealing with list. Which is were my struggle with this thing started 4 months ago. I opted for the listView but ever since I have been looking at better ways to increase the performance. Now I’ve become one of those people that obsess about it. Thanks for the resource.