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:
- Trying to build a
CacheableRoute
that saves it’s DOM in a fragment and “fastboots” itself when re-entered. - 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?). - Using a routable-via-queryParms
off-canvas
component to renderindex
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
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.
- better element recycling
- cache view heights instead of storing them inDOM
- track what elements are visible and render only those elements on return to start
- 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.