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 :-)
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() ;
}
}