viseator
viseator

Reputation: 91

Livedata returned by Room won't be triggered after GC

Our team are building a project with benefits from Android Jetpack.

There are demo code showing the question we are facing. These code can be found at https://github.com/viseator/TestRoomLivedata

I create a UserDao:

@Dao
interface UserDao {
    @Query("SELECT * FROM user WHERE uid = :uid LIMIT 1")
    fun findUserById(uid: String?): Single<User>

    @Query("SELECT * FROM user WHERE state = 1 LIMIT 1")
    fun findLoginUserWithObserve(): LiveData<User>

    @Query("SELECT * FROM user WHERE state =1 LIMIT 1")
    fun findLoginUser(): Single<User>

    @Update
    fun update(vararg user: User)
}

I also created a kotlin object to manage user's state.

I'm observing the livedata returned by findLoginUserWithObserve() to get notified when login user changed:

object AccountManager {
    private const val DATA_BASE_NAME = "users"
    val TAG = "AccountManager"
    fun init(context: Application) {
        sDb = databaseBuilder(context, UserDataBase::class.java, DATA_BASE_NAME).build()
        sDao = sDb.userDao()
        sDao.findLoginUserWithObserve().observeForever {
            Log.d(TAG, "notified: $it")
        }
    }

    private lateinit var sDb: UserDataBase
    private lateinit var sDao: UserDao

    fun findLoginUserWithObserve() = sDao.findLoginUserWithObserve()

    fun logoutFlowable(): Single<Boolean> = sDao.findLoginUser().subscribeOn(
            Schedulers.io()).map { user ->
        user.state = User.STATE_NOT_LOGIN
        sDao.update(user)
        true
    }

    fun login(user: User) = logoutFlowable().subscribe({ doLogin(user) }, { doLogin(user) })

    private fun doLogin(user: User) = sDao.findUserById(user.uid).subscribeOn(
            Schedulers.io()).subscribe({ origin ->
        origin.userName = user.userName
        origin.state = User.STATE_HAVE_LOGIN
        sDao.update(origin)
        user.state = User.STATE_HAVE_LOGIN
    }, {
        user.state = User.STATE_HAVE_LOGIN
        sDao.insert(user)
    })

}

I initialize the AccountManager in the Applicaiton by calling it's init method and create a demo activity:

class MainActivity : AppCompatActivity() {
    private var i = 0

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        AccountManager.login(User().apply {
            userName = "user1"
            uid = System.currentTimeMillis().toString()
        })
        button.setOnClickListener {
            AccountManager.login(User().apply {
                userName = "user${i++}"
                uid = System.currentTimeMillis().toString()
            })
        }
    }
}

I suppose once AccountManager.login() be called, I will get notified and it will print a log message. But we found that we won't be notified anymore after GC. (We trigger GC by Android Studio Profiler)

Log message

After exploring the UserDao_Impl class generated by room, we found it create a observer and link with database by calling addWeakObserver():

  @Override
  public LiveData<User> findLoginUserWithObserve() {
    final String _sql = "SELECT * FROM user WHERE state = 1 LIMIT 1";
    final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 0);
    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);
        }

So We wonder why room using WeakObserver here, which makes livedata returned by room unreliable?

PS: We are using Flowable to emit livedata in it's onNext() now to work around this, onNext() will be triggered every time as expected.

Upvotes: 2

Views: 708

Answers (1)

viseator
viseator

Reputation: 91

After post this issue to the google issue tracker(https://issuetracker.google.com/issues/114833188), I got reply:

We don't want to leak the LiveData if it is not used anymore. We could technically keep adding and removing the observer when LiveData is in use / not in use; but that might mean missing some events that happens when LiveData is inactive. We used to do that in the initial prototypes but became harder to maintain. You should keep a reference to the LiveData to keep using it. This is the patter we use in all examples.

So just keep a reference to the livedata returned by room instead of just observing it, everything works well now.

Upvotes: 7

Related Questions