Skip to content

ListStore Guide: Introduction (WIP)

Oguz Kocer edited this page Jan 22, 2020 · 4 revisions

This guide is currently WIP and its contents might change significantly while the rest of the guide is fleshed out. If you have any questions/feedback, please reach out to @oguzkocer.

Table of Contents // TODO: Add links for each page

  1. Introduction
  2. Most important component: The ListDescriptor
  3. How to consume an existing list
  4. How to implement a new list
  5. Sectioning (link added in consumption)
  6. Consider this! (Common gotchas)
  7. Internals

ListStore and its components were implemented to address some common pain points for managing lists. To better understand what these pain points are, here is a crude summary of how a typical list without ListStore is currently implemented:

  1. Fetch the first page of items, then:
  • Delete all the existing items from the DB
  • Insert the new items in the DB
  • Refresh the whole list in UI
  1. When the user scrolls down, load more data by:
  • Making a network request with an offset parameter that's retrieved with a DB query
  • Inserting the new items into the DB
  • Refreshing the whole list in UI

This is a good enough approach with the following caveats:

  • It wastes user's bandwidth - This becomes especially important if each item in the list has significant amount of data.
  • Refreshing the whole list in the UI can be slow - This can be addressed using a diffing algorithm and only refreshing modified items or modified fields of existing items.
  • Since DB access is not paginated, if the row presentation has costly operations, the UI might be laggy.

For very simple lists, this approach or something similar to it is completely acceptable and a more complicated solution such as the one used by ListStore may be unnecessary. However, this kind of approach can create huge headaches if each model is fairly large and more importantly if the list needs to support filtering and/or searching. This is because:

  • Offset calculation is very error prone. It's no longer possible to keep track of it through simple DB queries. There are definitely ways around that, such as keeping each offset somewhere else, deleting all the items pretty much all the time. (each filter change, each search etc) However, not doing this correctly will result in hard to reproduce gaps.
  • A lot of wasted bandwidth. Each item will need to be re-downloaded for every filter & search query change, for each pull to refresh etc.
  • An item can show up in different states in different filters. This might not be a big problem depending on the solution to the offset issue, but having multiple instances of same data is always a concern for developers. (SSOT)

Overview of the solution

The solution ListStore uses is to separate the list data from the item data. We do this by creating a many-to-many relationship between lists and items. So, an item might be in multiple lists (filters) or a list might have multiple items. A list managed through ListStore will roughly have the following lifecycle:

  • Fetch the first page of the list: Although this will depend on how the feature is implemented, this typically means that we'll fetch the id and the modifiedDate of each item. ListStore only needs an id to work with.
  • Delete all the existing IDs for a specific list. This is very similar to the simple list approach, but instead of deleting all the data, we only delete ids for a list.
  • Insert the ids of each item into the DB.
  • Reload the ids for the list from the DB. The interesting bit here is that ListStore will give the consumer a way to make changes to the list at this stage. This is how local items or section headers are added, or some items are temporarily filtered out in the UI. (for an undo action for example)
  • Whenever the list or its items are changed, reload the visible portion of the list from the DB. During this stage, if the actual items are not in the DB, the consumer will be responsible for initiating a fetch for it.
  • Load more pages when necessary with an offset given by ListStore which it can easily figure out with a DB query.

The actual implementation is a little bit more complicated than this, but just understanding this rough overview will go a long way for working with lists managed by ListStore. Also, ListStore and its components do a lot of the heavy lifting of these operations and during consumption only a couple simple functions will need to be implemented.

ListStore managed lists have the following advantages/disadvantages:

Advantages:

  • Requests are faster and should consume much less data since only a few fields would be fetched instead of the entire model. The bigger the model, the bigger the speed and data consumption difference will be.
  • Easier to avoid gaps even if the API only allows pagination through offset or page number.
  • Individual items can be shown instead of waiting a whole page of them to be fetched.
  • More straightforward caching structure.
  • Guarantees that same item in different filters will show the same data. (SSOT)
  • State is managed internally by ListStore. (Fetching first page, loading more, fetched all data etc)

Disadvantages:

  • More complicated to understand & implement (for how much it does, it's really not too complicated)
  • Lists may require many more UI reloads, so being efficient in the UI calculations is almost a must. (pretty negligible if the API allows batch requests)
  • Initial loads are slower especially if the API doesn't allow batch requests. (negligible because initial load happens just once(ish) for each site)