Why Does Room Delete Operation(With RxJava) Gives UI Thread Error Even Specifying Different Subcribe Thread?
Asked Answered
W

2

6

So simply, the DAO

@Query("DELETE FROM Things WHERE someIdOfTheThing IN (:listOfId)")
abstract fun deleteThings(listOfId: MutableList<String>): Maybe<Int>

usage,

mDisposables.add(mThingsDao
    .deleteThings(listOfId)
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe({
         ...
     }, {
         ...
     })
)

and error,

// Cannot access database on the main thread since it may potentially lock the UI for a long period of time.

The simple idea i was thinking is to specify subscribeOn(Schedulers.io()) and then give all the job to Rx's magical hands, but failed.

So what's the thing i'm missing ?


After wrapping like below and using deleteThingsWrapped, started working. But still don't understand why first approach not worked

open fun deleteThingsWrapped(listOfId: MutableList<String>): Maybe<Int> {
    return Maybe.create(object : MaybeOnSubscribe<Int> {
        override fun subscribe(emitter: MaybeEmitter<Int>) {
            emitter.onSuccess(deleteThings(listOfId))
        }
    })
}

@Query("DELETE FROM Things WHERE someIdOfTheThing IN (:listOfId)")
abstract fun deleteThings(listOfId: MutableList<String>): Maybe<Int>
Wedgwood answered 17/2, 2019 at 0:54 Comment(2)
I don't think there is anything wrong about what you have. Maybe it has something to do with what you do in .subscribeGroovy
Already the subscribe callback is intended to be fired after it's done. I think its not the actual point that we review.Wedgwood
S
8

Much more interesting question than it seems <3

To solve your problem, we must look at the code generated by Room - for the following:

@Transaction
@Query("DELETE FROM plants WHERE id IN (:listOfId)")
abstract fun deleteThings(listOfId: MutableList<String>): Maybe<Int>

The generated code is:

  @Override
  public Maybe<Integer> deleteThings(final List<String> listOfId) {
    StringBuilder _stringBuilder = StringUtil.newStringBuilder();
    _stringBuilder.append("DELETE FROM plants WHERE id IN (");
    final int _inputSize = listOfId.size();
    StringUtil.appendPlaceholders(_stringBuilder, _inputSize);
    _stringBuilder.append(")");
    final String _sql = _stringBuilder.toString();
    SupportSQLiteStatement _stmt = __db.compileStatement(_sql);
    int _argIndex = 1;
    for (String _item : listOfId) {
      if (_item == null) {
        _stmt.bindNull(_argIndex);
      } else {
        _stmt.bindString(_argIndex, _item);
      }
      _argIndex ++;
    }
    return Maybe.fromCallable(new Callable<Integer>() {
      @Override
      public Integer call() throws Exception {
        __db.beginTransaction();
        try {
          final java.lang.Integer _result = _stmt.executeUpdateDelete();
          __db.setTransactionSuccessful();
          return _result;
        } finally {
          __db.endTransaction();
        }
      }
    });
  }

So we see that the operation itself IS inside a Maybe.fromCallable block, which is the part that will be affected by subscribeOn(Schedulers.io()). So if the executeUpdateDelete(); is executed on the background thread, why do you get an exception?


See this line:

SupportSQLiteStatement _stmt = __db.compileStatement(_sql);

If we check the inside of this method....

/**
 * Wrapper for {@link SupportSQLiteDatabase#compileStatement(String)}.
 *
 * @param sql The query to compile.
 * @return The compiled query.
 */
public SupportSQLiteStatement compileStatement(@NonNull String sql) {
    assertNotMainThread(); // <-- BOOM!
    return mOpenHelper.getWritableDatabase().compileStatement(sql);
}

So apparently even putting together the query is asserted to not be on the main thread, therefore having a Maybe is irrelevant; the block outside of the Maybe.fromCallable is executed on the current thread no matter what you do.



You can solve this by executing the "synchronously executed part" to execute on a background thread, too.

mDisposables.add(
    Maybe.defer { 
        mThingsDao.deleteThings(listOfId)
                         .subscribeOn(Schedulers.io())
    }.subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe({
       ...
    }, {
       ...
    })
)
Severable answered 17/2, 2019 at 15:42 Comment(2)
Do you think this is a bug in the generator? Why doesn't it put all that stuff into the body of the fromCallable? Also if I'd run the generated Maybe concurrently from multiple threads at one, will they clash on _stmt.executeUpdateDelete() - which breaks the isolation expectation of a cold sequence. Maybe worth reporting this as a bug to Room devs. Btw., there is no need for that convoluted flatMapping; just use Maybe.defer(() -> mThingsDao .deleteThings(listOfId)).subscribeOn(Schedulers.io())...Pregnancy
Yes, I do think it's a bug in the generator. The Maybe.defer() is a good idea, I kinda forgot after using .fromCallable for everything. I'll revise the post as such.Severable
B
6

This is a bug, is already fixed and will be released with 2.1.0-alpha05

Bouldin answered 18/2, 2019 at 19:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.