Search code examples
android-roomandroid-lifecycleandroid-viewmodelrx-java3

Why doesn't my Android ViewModel's Room RxJava3 Flowable publish any result when my Activity is paused?


I'm aware it's a complex question that cannot have a definite answer without posting a few hundreds of lines of code, which is why I'm looking for help through general ideas and pointers.

I have a Room @Query returning a RxJava3 Flowable<List<...>> which I subscribe to on RxJava thread Schedulers.io(), and observe from an activity-scoped ViewModel on RxJava thread AndroidSchedulers.mainThread(). The data is then stored in my ViewModel as LiveData, which plays better than RxJava when it comes to handle Android components' lifecycle.

The idea is to have a clean and immediate data update pattern, not to have to handle disposal and re-subscription separately on each activity or fragment lifecycle event such as onPaused and onResumed, and being updated in the background even when my activity is hidden in order to avoid that awful refresh lag when returning to my activity. I was pretty amazed at that design pattern. I still am, but I'm beginning to have doubts.

When starting another activity with the same design pattern, I do change a value and immediately get an updated List<...> from the other ViewModel. Different Activity, different ViewModel, same design, same database table. When returning to the first Activity, I find that the new data does never get updated: Room did not emit any update even though the data set has changed. I have to dispose and subscribe again in order to see the new data.

So my question is: any pointer on where the source of my problem might be?! Is there something rotten in the core of this design pattern? Something I misunderstood about all those things are supposed to work together? Is it just a mistake of mine due to some threading issue? Or should I fill a bug report for Room?

I tried to observe another non-Room RxJava3 observable from the ViewModel of my first Activity, and it does get updates when its data set is updated.

By the way, I also use Hilt in order to inject eveything as @Singleton.

Thank you for your time :-)


Solution

  • After a week of headaches, I have finally stumbled upon a solution, which happens to be clean and elegant.

    The issue was RxJava, which, I just learnt, is not supposed to seamlessly handle multiple subscriptions to the same Observable. The solution is supposedly to make use of the publish(), connect(), refcount() operators, or better use the shortcut share(). I tried every way I could think of, without success (it actually made it worse). I also tried to subscribe() to the Room Flowable from my repository and proxy it through a BehaviorSubject.

    There was this weird org.reactivestreams.Publisher in Room's documentation, whose added value I wouldn't know, and whose origin wasn't even my familiar io.reactivex.rxjava3. It turns out it that was the solution. Edit: It turns out Publisher is an interface that Flowable happens to implement.

    build.gradle

    implementation 'android.arch.lifecycle:reactivestreams:+'
    

    Dao.java

    @Query("...")
    Flowable<List<...>> getFlowable();
    

    ViewModel.java

    public liveData;
    
    @Inject
    public ViewModel(@NonNull RoomDatabase roomDatabase) {
        liveData = LiveDataReactiveStreams.fromPublisher(roomDatabase.dao().getFlowable());
    }
    

    It seems too easy to be true, but as far as I can see it seems to work perfectly better this way.

    Edit: It turns out the root of this issue was a slight little bit more vicious than I thought. I assumed @InstallIn(SingletonComponent.class) in my dependency injection @Module was enough, but apparently a @Singleton annotation on each @Provides method is also required.

    @Module
    @InstallIn(SingletonComponent.class)
    public abstract class DependencyInjection
    {
        @Provides
        @NonNull
        @Singleton // do not omit this
        public static DataDao provideDataDao(@NonNull RoomDatabase roomDatabase) {
            return roomDatabase.dataDao();
        }
    
        @Provides
        @NonNull
        @Singleton // do not omit this
        public static RoomDatabase provideRoomDatabase(@ApplicationContext Context applicationContext) {
            return
                    BuildConfig.DEBUG ?
                            Room.databaseBuilder(applicationContext, RoomDatabase.class, "playground.db").fallbackToDestructiveMigration().build() :
                            Room.databaseBuilder(applicationContext, RoomDatabase.class, "playground.db").build() ;
        }
    }