Mobile Content Loading Pattern and Why Pull To Refresh is Dead

We’ve been thinking a lot about loading content on mobile recently. Things like loading speed, content size, and content freshness always come to mind when you start to think about this subject.

I want to go a bit deeper on these three aspects of delivering content to users and ultimately, make a solid case against the “Pull to Refresh” pattern.

 Loading Content

Before I start, let’s define the end goal when it comes to our philosophy for loading content:

We should deliver the least and latest contextually relevant content within a reasonable timeframe to the user.

This statement makes it very easy for us to make the right UX and engineering decisions. It may not be universally correct, but it’s definitely a statement that describes the experience we want very well.

Let’s jump in.

 Context

Context, if thought about properly and done right, is core to making the experience delightful for the user.

The premise is very simple: load the smallest amount of content that’s needed right now.

Here’s a simple example to demonstrate this concept from one of the recently loved Silicon Valley companies: Secret.

photo.PNG

In this context (a collection of objects), we should be loading – at maximum – what we need to show the user. In this case, it would probably be the following:

Example of an API response:

[
  {
    id: 1,
    image_url: "http://cdn.com/path-to-image",
    secret_text: "Text for secret 1",
    comments_count: 3,
    favorites_count: 10
  },
  {
    id: 2,
    image_url: "http://cdn.com/path-to-image-2",
    secret_text: "Text for secret 2",
    comments_count: 1,
    favorites_count: 5
  }
]

This makes sure we don’t load extra content that’s not needed (example: the comments themselves) and also follows REST convensions. On the device side, it also minimizes the size of the packets we have to process.

As the user seeks to go deeper into the content, we can start loading the rest of the object (example: actual comments).

After loading this data, we would save it to a local database on the device (different per platform, but use-cases are the same). This is to avoid reloading the same content over and over again.

With this approach, we have to now solve two other problems: keeping the existing content fresh and loading new data.

 Updating and keeping the content fresh

We need to make sure that the content is always upto date. This is to make sure that external changes to the data are communicated back to the user.

In our example, if someone favorites, comments on, or creates a new Secret – we should be able to see that change.

The easiest way to do this would be reload all of the data again. It’s usually an attractive option, since it’s easily achievable.

However, if we want to stay true to our mantra of “loading the least and latest contextually relevant data”, we have to approach this problem differently.

What we should be doing is loading only changes in the content. We should almost think of it as a “difference” between what we had loaded before and what the current state of the content is.

In our example, it would mean that we would only load the changes to the collection we had loaded before. Same would go for any other object or collection that we had loaded previously and want to get the latest changes for.

An API response for getting an update on the previously loaded collection (as per our example) would be something like this:

[
  {
    id: 1,
    status: 'destroyed'
  },
  {
    id: 2,
    comments_count: 1,
    status: 'updated'
  },
  {
    id: 3,
    image_url: "http://cdn.com/path-to-image-3",
    secret_text: "Text for secret 3",
    comments_count: 15,
    favorites_count: 200,
    status: 'created'
  }
]

In this case, we would find those objects (using their IDs) in our local storage and update them accordingly. There’s three cases that need to be handled:

  1. Status = ‘created’: This is a new addition to the collection and should be treated as such.
  2. Status = ‘updated’: This is an existing item in the collection (previously loaded) and its attributes should be updated accordingly.
  3. Status = ‘destroyed’: This is an item that existed before but should no longer be part of the collection (example: it was deleted by the creator).

Note that we’re not taking “difference” fully seriously, but rather providing the attributes that changed on the updated objects, newly introduced objects and deleted objects in the collection so we can properly update our local database. This simplifies the process and does the job just as well.

This process keeps the amount of data loaded to a minimum and sets us up for a few things:

Updating content in the background – We can now update the content more efficiently in the background to make sure the next time the user comes back, they get the latest data.

Better UX & Design – We won’t have to reload all the content but rather update it live while the user is interacting with it or in the background, giving us the opportunity to put in some sexy animations and affordances.

 Case against Pull To Refresh

This pattern has been widely used in the past few years and a lot of users are used to it. Both on iOS and Android.

However, with the recent advancements of both operating systems (example: background refresh) as well as the loading speed possible by 3G and LTE, it’s becoming more and more obsolete.

Depending on how often your app needs to update the content, there’s two simple solutions to eliminate the need for this pattern all together:

 Update automatically, frequently

If the content needs to be updated regularly, but not live-within-seconds – updating it frequently is a good option.

If we’re using the loading pattern above, the sizes of these pulls are very minimal and can be done as frequently as every few/several minutes (if user is inside the app) without causing any real performance and data issues.

This is specially a good approach for apps loading simple objects, like Secret and e-commerce apps.

 Keep constant communication

If we need live data at all times, as common in social networks or chat apps, then a live and constant communication between the server and the device is something to consider.

We can use the same loading pattern (more or less) as above using a socket and handle content being pushed (as opposed to pulled) to keep data upto date.


For both approaches, using the status bar (iOS), Croutons (Android) or inline loading indicators are all great ways to communicate the fact that we’re updating the content to the user.

Feel free to reach out to me on twitter if you have comments / questions!

Subscribe to my mailing list for more posts around mobile, experimentation and product management here.

Thanks to Michael Eilers Smith, Philippe Durocher-McBrearty and Tarek Abderrazik for inspiring me to write this post as they’ve been developing our mobile apps.

 
20
Kudos
 
20
Kudos

Now read this

Empathy and Human Values in Products

Recently, I did a quick talk at Real Fellowship, Real Ventures’ latest initiative to jump start the Montreal Startup scene. Pretty proud to be a part of it. Since SlideShare doesn’t allow for presenter notes to be shown with the slides... Continue →