@Override public LiveData<User> findByLogin(String login) { final String _sql = "SELECT * FROM user WHERE login = ?"; final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 1); int _argIndex = 1; if (login == null) { _statement.bindNull(_argIndex); } else { _statement.bindString(_argIndex, login); } return new ComputableLiveData<User>(__db.getQueryExecutor()) { private Observer _observer;
@Override protected User compute() { if (_observer == null) { _observer = new Observer("user") { @Override public void onInvalidated(@NonNull Set<String> tables) { invalidate(); } }; __db.getInvalidationTracker().addWeakObserver(_observer); } final Cursor _cursor = DBUtil.query(__db, _statement, false); try { final int _cursorIndexOfLogin = _cursor.getColumnIndexOrThrow("login"); final int _cursorIndexOfAvatarUrl = _cursor.getColumnIndexOrThrow("avatarUrl"); final int _cursorIndexOfName = _cursor.getColumnIndexOrThrow("name"); final int _cursorIndexOfCompany = _cursor.getColumnIndexOrThrow("company"); final int _cursorIndexOfReposUrl = _cursor.getColumnIndexOrThrow("reposUrl"); final int _cursorIndexOfBlog = _cursor.getColumnIndexOrThrow("blog"); final User _result; if(_cursor.moveToFirst()) { final String _tmpLogin; _tmpLogin = _cursor.getString(_cursorIndexOfLogin); final String _tmpAvatarUrl; _tmpAvatarUrl = _cursor.getString(_cursorIndexOfAvatarUrl); final String _tmpName; _tmpName = _cursor.getString(_cursorIndexOfName); final String _tmpCompany; _tmpCompany = _cursor.getString(_cursorIndexOfCompany); final String _tmpReposUrl; _tmpReposUrl = _cursor.getString(_cursorIndexOfReposUrl); final String _tmpBlog; _tmpBlog = _cursor.getString(_cursorIndexOfBlog); _result = new User(_tmpLogin,_tmpAvatarUrl,_tmpName,_tmpCompany,_tmpReposUrl,_tmpBlog); } else { _result = null; } return _result; } finally { _cursor.close(); } }
/** * Creates a computable live data which is computed when there are active observers. * <p> * It can also be invalidated via {@link #invalidate()} which will result in a call to * {@link #compute()} if there are active observers (or when they start observing) */ @SuppressWarnings("WeakerAccess") publicComputableLiveData() { mLiveData = newLiveData<T>() { @Override protectedvoidonActive() { // TODO if we make this class public, we should accept an executor AppToolkitTaskExecutor.getInstance().executeOnDiskIO(mRefreshRunnable); } }; }
/** * Returns the LiveData managed by this class. * * @return A LiveData that is controlled by ComputableLiveData. */ @SuppressWarnings("WeakerAccess") @NonNull public LiveData<T> getLiveData() { return mLiveData; }
@VisibleForTesting finalRunnablemRefreshRunnable=newRunnable() { @WorkerThread @Override publicvoidrun() { boolean computed; do { computed = false; // compute can happen only in 1 thread but no reason to lock others. if (mComputing.compareAndSet(false, true)) { // as long as it is invalid, keep computing. try { Tvalue=null; while (mInvalid.compareAndSet(true, false)) { computed = true; value = compute(); } if (computed) { mLiveData.postValue(value); } } finally { // release compute lock mComputing.set(false); } } } while (computed && mInvalid.get()); } };
// invalidation check always happens on the main thread @VisibleForTesting finalRunnablemInvalidationRunnable=newRunnable() { @MainThread @Override publicvoidrun() { booleanisActive= mLiveData.hasActiveObservers(); if (mInvalid.compareAndSet(false, true)) { if (isActive) { // TODO if we make this class public, we should accept an executor. AppToolkitTaskExecutor.getInstance().executeOnDiskIO(mRefreshRunnable); } } } };
/** * Invalidates the LiveData. * <p> * When there are active observers, this will trigger a call to {@link #compute()}. */ publicvoidinvalidate() { AppToolkitTaskExecutor.getInstance().executeOnMainThread(mInvalidationRunnable); }
@SuppressWarnings("WeakerAccess") @WorkerThread protectedabstract T compute(); }
/** * Called by RoomDatabase before each beginTransaction call. * <p> * It is important that pending trigger changes are applied to the database before any query * runs. Otherwise, we may miss some changes. * <p> * This api should eventually be public. */ voidsyncTriggers() { if (!mDatabase.isOpen()) { return; } syncTriggers(mDatabase.getOpenHelper().getWritableDatabase()); }
voidsyncTriggers(SupportSQLiteDatabase database) { if (database.inTransaction()) { // we won't run this inside another transaction. return; } try { // This method runs in a while loop because while changes are synced to db, another // runnable may be skipped. If we cause it to skip, we need to do its work. while (true) { LockcloseLock= mDatabase.getCloseLock(); closeLock.lock(); try { // there is a potential race condition where another mSyncTriggers runnable // can start running right after we get the tables list to sync. finalint[] tablesToSync = mObservedTableTracker.getTablesToSync(); if (tablesToSync == null) { return; } finalintlimit= tablesToSync.length; try { database.beginTransaction(); for (inttableId=0; tableId < limit; tableId++) { switch (tablesToSync[tableId]) { case ObservedTableTracker.ADD: startTrackingTable(database, tableId); break; case ObservedTableTracker.REMOVE: stopTrackingTable(database, tableId); break; } } database.setTransactionSuccessful(); } finally { database.endTransaction(); } mObservedTableTracker.onSyncCompleted(); } finally { closeLock.unlock(); } } } catch (IllegalStateException | SQLiteException exception) { // may happen if db is closed. just log. Log.e(Room.LOG_TAG, "Cannot run invalidation tracker. Is the db closed?", exception); } }
privatevoidstartTrackingTable(SupportSQLiteDatabase writableDb, int tableId) { finalStringtableName= mTableNames[tableId]; StringBuilderstringBuilder=newStringBuilder(); for (String trigger : TRIGGERS) { stringBuilder.setLength(0); stringBuilder.append("CREATE TEMP TRIGGER IF NOT EXISTS "); appendTriggerName(stringBuilder, tableName, trigger); stringBuilder.append(" AFTER ") .append(trigger) .append(" ON `") .append(tableName) .append("` BEGIN INSERT OR REPLACE INTO ") .append(UPDATE_TABLE_NAME) .append(" VALUES(null, ") .append(tableId) .append("); END"); writableDb.execSQL(stringBuilder.toString()); } } privatestaticfinal String[] TRIGGERS = newString[]{"UPDATE", "DELETE", "INSERT"};
publicvoidendTransaction() { mOpenHelper.getWritableDatabase().endTransaction(); if (!inTransaction()) { // enqueue refresh only if we are NOT in a transaction. Otherwise, wait for the last // endTransaction call to do it. mInvalidationTracker.refreshVersionsAsync(); } }
publicvoidrefreshVersionsAsync() { // TODO we should consider doing this sync instead of async. if (mPendingRefresh.compareAndSet(false, true)) { ArchTaskExecutor.getInstance().executeOnDiskIO(mRefreshRunnable); } }
if (!mPendingRefresh.compareAndSet(true, false)) { // no pending refresh return; }
if (mDatabase.inTransaction()) { // current thread is in a transaction. when it ends, it will invoke // refreshRunnable again. mPendingRefresh is left as false on purpose // so that the last transaction can flip it on again. return; }
mCleanupStatement.executeUpdateDelete(); mQueryArgs[0] = mMaxVersion; if (mDatabase.mWriteAheadLoggingEnabled) { // This transaction has to be on the underlying DB rather than the RoomDatabase // in order to avoid a recursive loop after endTransaction. SupportSQLiteDatabasedb= mDatabase.getOpenHelper().getWritableDatabase(); try { db.beginTransaction(); hasUpdatedTable = checkUpdatedTable(); db.setTransactionSuccessful(); } finally { db.endTransaction(); } } else { hasUpdatedTable = checkUpdatedTable(); } } catch (IllegalStateException | SQLiteException exception) { // may happen if db is closed. just log. Log.e(Room.LOG_TAG, "Cannot run invalidation tracker. Is the db closed?", exception); } finally { closeLock.unlock(); } if (hasUpdatedTable) { synchronized (mObserverMap) { for (Map.Entry<Observer, ObserverWrapper> entry : mObserverMap) { entry.getValue().checkForInvalidation(mTableVersions); } } } }
mTableVersions[tableId] = version; hasUpdatedTable = true; // result is ordered so we can safely do this assignment mMaxVersion = version; } } finally { cursor.close(); } return hasUpdatedTable; } };