Best way to cache data in Android with ViewModel and RxJava2 - android

In my application I'm using RxJava2 and new class from Architecture Components ViewModel. In my case, I need to push SQL clause to ViewModel, which will do some magic and return Observable that will give me the data I need. Everything works fine, but I am not sure if I am using RX in the best way.
My data flow:
ViewModel has PublishSubject on which I am pushing SQL's. ViewModel has also Observable which is created by mapping subject. Also, I used distinctUntilChanged on Subject, to prevent from executing the same query again.
To cache data I used replay(1).autoconnect(1) on Observable, but that approach had a flaw. Sometimes my Subject pushed Sql when Observable wasn't yet connect, and my data never arrived to me. Should I use BehaviourSubject? Or maybe I shouldn't use replay(1).autoconnect(1) in the first place? Or maybe my whole flow is wrong? Example:
val listSubject: Subject<RawSql> = PublishSubject.create()
val sqlListEmitter: Observable<List<T>> =
listSubject
.subscribeOn(Schedulers.computation())
.map { // SOME MAGIC HERE }
.replay(1).autoConnect(1, { compositeDisposable.add(it) })

In your case autoConnect() just waits for the first subscription to connect() to your stream. Since your subject and your stream build an inherent entity, you might not want to wait for it at all and instead connect it directly.
val listSubject: Subject<RawSql> = PublishSubject.create()
val sqlListEmitter: Observable<List<T>> =
listSubject
.observeOn(Schedulers.computation())
.map { // SOME MAGIC HERE }
.replay(1)
.let {
it.connect(compositeDisposable::add)
it.publish()
}
Also you might need to change subscribeOn() to observeOn(). The subject emits on the same thread as the data is pushed to it and does not consider the thread it's subscribed on.

Related

Kotlin Flow Offline Caching

I am new with kotlin flow and I am working about this document. Kotlin Flows. In this code every five seconds datasource fetch data from api and emits it.
This is my example datasource class.
I am getting data and emitting it.
class RemoteDataSourceImpl #Inject constructor(
private val api:CryptoApi
): RemoteDataSource {
override suspend fun cryptoList(): Flow<List<CryptoCoinDto>> {
return flow {
while (true){
val data = api.getCoinList()
emit(data)
delay(5000L)
}
}
}
}
This is my example repository.
I am mapping data and saving it room database. I want to get data from room database and emit it because of single source of truth principle but I still have to return dataSource because if I open new flow{} I can't reach datasource's data. Of course I can fix the problem by using List instead of Flow<List> inside of RemoteDataSource class. But I want to understand this example. How can I apply here single source of truth.
class CoinRepositoryImpl #Inject constructor(
private val dataSource:RemoteDataSource,
private val dao: CryptoDao
):CoinRepository {
override fun getDataList(): Flow<List<CryptoCoin>> {
dataSource.cryptoList().map { dtoList ->
val entityList = dtoList.map { dto ->
dto.toCryptoEntity()
}
dao.insertAll(entityList)
}
return dataSource.cryptoList().map {
it.map { it.toCryptoCoin() }
}
}
This is actually more complicated than it seems. Flows were designed to support back-pressure which means that they usually only produce items on demand, when being consumed. They are passive, instead of pushing items, items are pulled from the flow.
(Disclaimer: this is all true for cold flows, not for hot flows. But cryptoList() is a cold flow.)
It was designed this way to greatly simplify cases when the consumer is slower than producer or nobody is consuming items at all. Then producer just stops producing and everything is fine.
In your case there are two consumers, so this is again more complicated. You need to decide what should happen if one consumer is slower than the other. For example, what should happen if nobody collects data from getDataList()? There are multiple options, each requires a little different approach:
Stop consuming the source flow and therefore stop updating the database.
Update the database all the time and queue items if nobody is collecting from getDataList(). What if there are more and more items in the queue?
Update the database all the time and discard items if nobody is collecting from getDataList().
Ad.1.
It can be done by using onEach():
return dataSource.cryptoList().onEach {
// update db
}.map {
it.map { it.toCryptoCoin() }
}
In this solution updating the database is a "side effect" of consuming the getDataList() flow.
Ad.2. and Ad.3.
In this case we can't passively wait until someone asks us for an item. We need to actively consume items from the source flow and push them to the downstream flow. So we need a hot flow: SharedFlow. Also, because we remain the active side in this case, we have to launch a coroutine that will do this in the background. So we need a CoroutineScope.
Solution depends on your specific needs: do you need a queue or not, what should happen if queue exceeded the size limit, etc., but it will be similar to:
return dataSource.cryptoList().onEach {
// update db
}.map {
it.map { it.toCryptoCoin() }
}.shareIn(scope, SharingStarted.Eagerly)
You can also read about buffer() and MutableSharedFlow - they could be useful to you.

Send data from repository to view model in MVVM architecture

I'm confused about using MVVM architecture! In some tutorials, LiveData is stored in a repository and then passed to the ViewModel. In many others, LiveData is defined within a function of repository and passed to the ViewModel using the result of the function. But Google says:
It may be tempting to work LiveData objects in your data layer class, but LiveDatais not designed to handle asynchronous streams of data. ........ If you need to use streams of data in other layers of your app, consider using Kotlin Flows and then converting them to LiveData in the ViewModel using asLiveData(). .... For codebases built with Java, consider using Executors in conjuction with callbacks or RxJava.
I prefer to use Kotlin coroutines or Flows. But I do not know if in my case is really needed or not. I'm working on a chat application. When a message is received, a listener is called in the repository and the message data is received in it. (The listener runs in the background thread) Now I want to send the message object to the ViewModel and add it to a LiveData that stores the list of messages.
object ChatsRepo {
fun listen(socket: Socket) {
socket.on(Config.ON_MESSAGE, onMessage)
}
fun sendMessage(socket: Socket, json: String) {
socket.emit(Config.ON_MESSAGE, json)
}
private val onMessage = Emitter.Listener { args: Array<Any> ->
//This message object must be sent to ViewModel
val message = Gson().fromJson(args[0].toString(), Message::class.java)
}
}
I can easily do this using the higher-order function:
object ChatsRepo {
lateinit var listener: (Message) -> Unit
private val onMessage = Emitter.Listener { args: Array<Any> ->
val message = Gson().fromJson(args[0].toString(), Message::class.java)
listener(message)
}
}
But is it better to use Kotlin coroutines or Flows? In some similar cases, a list needs to be sent to the ViewModel.
I can easily do this using the higher-order function.
Right, this is a callback you can use to notify ViewModel about new messages. Kotlin Coroutines help to avoid callbacks and to have a sequential code.
In your case the onMessage is a hot stream of data, we can convert it to a hot Flow using SharedFlow:
private val _messagesFlow = MutableSharedFlow<Message>(extraBufferCapacity = 64)
val messagesFlow: SharedFlow<Message> = _messagesFlow
private val onMessage = Emitter.Listener { args: Array<Any> ->
val message = Gson().fromJson(args[0].toString(), Message::class.java)
messagesFlow.tryEmit(message)
}
In ViewModel if need it is easy to convert it to LiveData using method asLiveData:
ChatsRepo.messagesFlow.asLiveData()
Dependency to use asLiveData() extension function:
def lifecycle_version = "2.4.0"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
If you are a Rxjava master, I will not recommend you to use flow/livedata. I think flow/livedata is designed for UI Reactive, not for underlying data transform. So you can use rxjava in data repository, and in viewmodel, you can convert it to livedata and use it.
Livedata was never design for reactive streams it was always been for last layer (viewmodel to view), there were/are workaround when livedata was use with retrofit(using calladapter) and room(which google did it).
And now since the rise of coroutines recommended ways is to use kotlin Flows when dealing with business logic but things to be notice:
1- regular flow are not observables but livedata is observable.
2- it's not good to manage UI state with regular flows but you can do it with livedata.
3- livedata is lifecycle aware but flows are not.
4- livedata is not reactive but flows are.
5- livedata gives you the only latest value what it receives but regular flows throws values from bucket one by one
you might have notice i said alot of regular flows not flow cause there are other mechanism related to flows you can manage UI state that are stateFlow and sharedFlow.
Stateflow is the replacement of livedata the downside is you have to code a little bit more to make it lifecycle aware cause it is not prebuilt and stateflow always gives you the last value it persists during the configuration changes or screen switching(fragment navigation).
sharedFlows is good for managing the one time event like toastMsgs, snackbar etc. sharedFlow was actually the replacement of BroadcastChannels.

Realm with live data and RxJava in Kotlin

I am trying to use Realm in a Kotlin based application.
I set up a basic project, which tested adding elements into realm, and observing them with live data and a view model.
Then I wanted to add in some other observables, and use combineLatest of RxJava to combine them.
I created Rx Observables for all observables. I observe the live data from Realm, and then call RxJava BehaviourSubject's onNext() method to set one of the Rx Observables to be the same value as the observed realm data. This works fine, but as soon as I added in a Throttle to the Rx Operators, I got the following error message:
Realm access from incorrect thread.
Realm objects can only be accessed on the thread they were created.
at io.realm.BaseRealm.checkIfValid(BaseRealm.java:442)
at io.realm.com_example_myapplication_ItemRealmProxy.realmGet$id(com_example_myapplication_ItemRealmProxy.java:105)
at io.realm.com_example_myapplication_ItemRealmProxy.toString(com_example_myapplication_ItemRealmProxy.java:661)
This is parts of my code:
private val itemViewModel: ItemViewModel by lazy {
ViewModelProviders.of(this).get(ItemViewModel::class.java)
}
val itemsSubject = BehaviorSubject.createDefault(listOf<Item>())
val intsSubject = BehaviorSubject.createDefault(4)
itemViewModel.getItemData().observe(this, Observer<RealmResults<Item>> { t ->
val items = t.map {
it
}
itemsSubject.onNext(items)
})
Observables.combineLatest(itemsSubject, intsSubject) { a, b ->
a
}.throttleLast(500, TimeUnit.MILLISECONDS).subscribe {
Log.i("Items", "Items combined read: ${it}")
}
I can't see how realm is coupled to the onNext call, nor the throttle. Seems very weird.
Ideally I would like to stick to just Live data, but I need to do operations with other observables, and RxJava is good for this.
I needed to observe the RxJava on the main thread, as follows:
Observables.combineLatest(itemsSubject, intsSubject) { a, b ->
a
}.throttleLast(500, TimeUnit.MILLISECONDS).observeOn(AndroidSchedulers.mainThread()).subscribe {
Log.i("Items", "Items combined read: ${it}")
}
}
In a sense, the Realm error was misleading, because it was ultimately an incorrect use of RxJava.

What is the right approach using rxkotlin or rxjava in android when communicating with server and db?

I've just learned rxjava, rxkotlin, dagger and retrofit, but I don't know the approach to take when communicating with server/db and how to store information locally.
There are 3 questions at the end. Please help
#Singleton
class MyInteractor #Inject constructor() {
#Inject lateinit var context: Context
#Inject lateinit var restClient: RestClient
private var subscriptions: MutableList<Disposable> = mutableListOf()
private var settingsSubject: BehaviorSubject<SettingsDTO> = BehaviorSubject.create()
fun initData() {
initSettings()
}
fun unsubscribeAll() {
subscriptions.forEach({ subscription -> subscription.dispose() })
}
private fun initSettings() {
val settingsObservable = restClient.getSettings()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
val settingsServerSubscription = settingsObservable.subscribe({ response ->
run {
settingsSubject.onNext(response.body())
}
}, {
//TODO handle errors
})
subscriptions.add(settingsServerSubscription)
//subscribe database to subject changes
val settingsDatabaseSubscription = settingsSubject.subscribe { setting ->
//TODO save or update db
}
subscriptions.add(settingsDatabaseSubscription)
}
fun getSettings(callback: SettingsCallback) {
callback.onFetchedSettings(settingsSubject.value)
}
}
Do I need to save the Disposable subjects and unsubscribe? Or is done automatically? (I'm saving all of the subscriptions in a list then undispose all at once)
Is this the right approach? I'm initializing all settings in the initData method, and call it at creation of an activity.
The data is stored in a behaviour subject (settingsSubject) and I subscribe on that subject to save changes in database,
so everytime I want to change a setting I'm calling the rest client and then update the subject (db will be updated because of the subscription)
On getSettings method, should I use the callback or just return the settingsSubject value?
Do I need to save the Disposable subjects and unsubscribe? Or is done automatically? (I'm saving all of the subscriptions in a list then undispose all at once)
Yes absolutely (It's not done automatically). Specially if your subscribers try to access some object that might no longer be there such as views when you send the app to the background. Although there's nothing wrong with your approach, Rx already has a CompositeDisposable that does exactly what you're doing. You can use CompositeDisposable.add(Disposable) to add disposables and then CompositeDisposable.clear() to dispose all the disposables. You also have CompositeDisposable.dispose(), but this renders the composite disposable unusable afterwards and might not be what you want.
Is this the right approach? I'm initializing all settings in the initData method, and call it at creation of an activity. The data is stored in a behaviour subject (settingsSubject) and I subscribe on that subject to save changes in database, so everytime I want to change a setting I'm calling the rest client and then update the subject (db will be updated because of the subscription)
Hard to say without the whole picture. What you have here looks like a database that can be observed for any change. I mean, looks like every time there's a change in the settings you could post something in the subject and trigger all its subscribers. If this is what you want then that seems ok. If you want to read the settings once and not care about changes until you come back to the screen or reload it, then maybe you could simply use an observable whenever you call the method getSettings. Something like:
fun getSettings(callback: SettingsCallback) {
restClient.getSettings()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ callback.onFetchedSettings(it) })
}
On getSettings method, should I use the callback or just return the settingsSubject value?
I would say this really depends on how you want to do things. I prefer to always return an observable and let the calling code decide what to do:
fun getSettings() =
restClient.getSettings()
.subscribeOn(Schedulers.io())
// In the calling code for example
interactor.getSettings()
.observeOn(AndroidSchedulers.mainThread())
.subscribe(/* use value */)
In your case if you want to keep the subject, you can still do it:
fun getSettings() = settingsSubject.subscribeOn(Schedulers.io())
// In calling code for example
interactor.getSettings()
.observeOn(AndroidSchedulers.mainThread())
.subscribe(/* use value */)
because you're using a BehaviorSubject the last emitted item will be emitted again once anyone subscribes, so this should still work.
Again, there's nothing wrong with the callback. I just personally think that it's cleaner to use this reactive interface.

call part of stream once with multiple subscribers?

I have custom Rx Adapter for socket communication.
Outside of it I observe Flowable with messages.
Then I have some manager that handles every message and then emit it further.
fun observeSocket() = socketManager
.observe()
.doOnNext{
insideMessageHandler.handle(it)
}
Then I have two subscribers that does observeSocket().subscribe()
The problem is that with every message insideMessageHandler.handle(it) is called twice. I want to find the way where part of stream will be common for every subscriber. Unfortunately .share() operator at the end of observeSocket() don't work.
I have something like this twice:
/onNextInside
Flowable/-onNextOutsideSubscriber1
Flowable\-onNextOutsideSubscriber2
\-onNextInside
And I want to have something like this:
/-onNextInside
Flowable/-onNextOutsideSubscriber1
\-onNextOutsideSubscriber2
In code it looks like
insideManager.observeSocket().subscribe({do something first})
insideManager.observeSocket().subscribe({do something second})
The problem is that in this case I have onNextInside called twice
Is it even possible?
The problem is the re-creation of the observable:
fun observeSocket() = socketManager
.observe()
.doOnNext{
insideMessageHandler.handle(it)
}
With every call to observeSocket() you create a new chain, so putting the share() there wouldn't make a difference.
Instead define this chain as a shared singleton:
private val _observeSocket = socketManager
.observe()
.doOnNext{
insideMessageHandler.handle(it)
}
.share()
fun observeSocket() = _observeSocket
For this case you have Subjects.
var yourSubject = PublishSubject<Type>.create()
fun observeSocket() = socketManager.doOnNext(yourSubject::onNext)
Then you can observe on your observeSocket() and another/multiple subscriber on the subject.
If you want to share the emitting of one Observables you can use the share() operator which makes sure that even when multiple subscribers are subscribed it only create one instance and share the emitted data using
fun observeSocket() = socketManager
.doOnNext(insideMessageHandler::handle)
.share()
Anyway, it's not a good usecase for reactive stuff to flat/emit to another observables in doOnNext for this purpose.
Better share one Flowable and subscribe multiple times and flatmap the values to your need instead of putting everything in one flowable since if there's an error in the stream the whole stream may not emit any more data.

Categories

Resources