zendesk / android-db-commons

Some common utilities for ContentProvider/ContentResolver/Cursor and other db-related android stuff
Apache License 2.0
222 stars 28 forks source link

ComposedCursorLoader misses a notification #85

Open C2H6O opened 8 years ago

C2H6O commented 8 years ago

In my application I have multiple ComposedCursorLoaders that have the same query within the same screen (business logic dictates how to filter that data for each view). There's a race condition that happens when data arrives from the server and gets inserted into the database. Here's a scenario when that happens:

1) Data is requested 2) Cursors are initialized and begin the process of querying the ContentResolver for data 3) Data is inserted into db and cursor is notified 4) Since queries for ComposedCursorLoaders happen on the background thread, but the cursor is registered to receive notifications happen on the UI thread, there's a window of opportunity for missed notifications, which is what I saw in my application.

The following is the refactored ComposedCursorLoader which fixes this race condition. The solution is to move the observer registration into the background thread and register the Cursor as soon as it is acquired from the ContentResolver.

  @Override
  public T loadInBackground() {

    final T result;

    final Cursor cursor = loadCursorInBackground();

    try {
      cursor.getCount();
      cursor.registerContentObserver(observer);
      if (!additionalUrisRegistered) {
        ContentResolver resolver = getContext().getContentResolver();
        for (Uri notificationUri : notificationUris) {
          resolver.registerContentObserver(notificationUri, true, observer);
        }

        additionalUrisRegistered = true;
      }

      result = cursorTransformation.apply(cursor);
      Preconditions.checkNotNull(result, "Function passed to this loader should never return null.");

      if (cursorsForResults.get(result) != null) {
        releaseCursor(cursor);
      } else {
        cursorsForResults.put(result, cursor);
      }

    } catch (RuntimeException ex) {
      cursor.close();
      throw ex;
    }

    return result;
  }