Implemented Caching by the following link:
https://blog.mindorks.com/implement-caching-in-android-using-rxjava-operators
fun getSavedAddressList(): Maybe<List<SavedAddress>?>? {
return Observable.concat(
getAddressListMemory(),
getAddressListDatabase(),
getAddressListNetwork()).firstElement()
}
fun getAddressListDatabase(): Observable<List<SavedAddress>?> {
return Observable.create(ObservableOnSubscribe { emitter: ObservableEmitter<List<SavedAddress>?> ->
val list: ArrayList<SavedAddress> = addressDao.getAddressList() as ArrayList<SavedAddress>
if (list.isNotEmpty()) {
emitter.onNext(list)
}
emitter.onComplete()
if (list.isNotEmpty())
getAddressListNetwork().subscribeOn(schedulerProvider.io())?.observeOn(schedulerProvider.io())?.subscribe()
})
}
items in the database are retrieving perfectly after storing into database
problem is network calling is not happening after getting a list from database
I want to get three data source sequentially one after another and store latest data in the database
First of all, you're leaking the getAddressListNetwork Disposable in there because you are trying to do too much inside the getAddressListDatabase.
I think what you want is this:
fun getSavedAddressList(): Observable<List<SavedAddress>> {
return Observable.concat(
getAddressListMemory(),
getAddressListDatabase(),
getAddressListNetwork()).distinctUntilChanged())
}
This will always try to fetch the addresses from the 3 sources, and only emitting if the data is different than the previous emission, meaning the data is "fresher".
To be honest with you, I think you need to have a look at the concept of "stale data" and "cache invalidation".
Related
I'm using an API that returns a text like this:
BW3511,HGP,ITP,Canceled,32.
I have to continue fetching until I get a response that is not "Canceled".
this code fetches the data:
val flightResponse = async {
println("Started fetching Flight info.")
client.get<String>(FLIGHT_ENDPOINT).also {
println("Finished fetching Flight info.")
}
}
the client.get can only be called within The coroutineScope body, also the flightResponse type is Deferred<String>.
check if it is canceled:
fun isCanceled(
flightResponse: String
) : Boolean {
val (_, _, _, status, _) = flightResponse.split(",")
return status == "Canceled"
}
how can I repeat client.get<String>(FLIGHT_ENDPOINT) until my condition is met using Functional Programming style?
I tried using takeIf but I have to get at least one result and it cannot be a nullable type.
As said in the comment by #Jorn, this looks like an overuse of functional style. It can be implemented by a simple loop and this way it will be probably more clear to the reader:
fun getNextNotCancelled() {
while (true) {
val response = client.get<String>(FLIGHT_ENDPOINT)
if (!isCanceled(response)) return response
}
}
If your real case is more complex, so you have several filters, etc. or for any other reason you really need to do this declaratively, then you need to create some kind of an infinite generator. For classic synchronous code that means sequence and for asynchronous - flow.
Example using a sequence:
generateSequence { client.get<String>(FLIGHT_ENDPOINT) }
.first { !isCanceled(it) }
Flow:
flow {
while (true) {
emit(client.get<String>(FLIGHT_ENDPOINT))
}
}.first { !isCanceled(it) }
As you said you use coroutines, I assume you would like to go for the latter. And as you can see, it is pretty similar to our initial loop-based approach, only more complicated. Of course, we can create a similar generateFlow() utility function and then it would be shorter.
I'm building a repository to retrieve data from a Room database. The Room dao returns a Flow<List<ObjectDto>>. However, I need to convert this to Flow<List<Object>>. What is the right way to do this?
This is the solution I've come up with. I have a mapper extension ObjectDto.toObject(). However, this solution doesn't seem right to me. I have no experience with flows, but collecting and emitting again can't be correct, right?
override fun getObjects(): Flow<List<Object>> {
return flow {
objectDao.getObjects().collect { objectDtoList ->
val objects = objectDtoList.map { it.toObject() }
emit(objects) }
}
}
I also found several operators to use on flows without collecting them, but while some of them are able to change the type, I'm not sure how to change the type of a list using these operators.
I think Flow.map is what you're looking for
override fun getObjects(): Flow<List<Object>> =
objectDao.getObjects().map { objectDtoList ->
objectDtoList.map { it.toObject() }
}
}
I have difficulties making sequential calls of RxJava Single observerable. What I mean is that I have a function that makes http request using retrofit that returns a Single.
fun loadFriends(): Single<List<Friend>> {
Log.d("msg" , "make http request")
return webService.getFriends()
}
and if I subscribe from several places at the same time:
loadFriends().subscribeOn(Schedulers.io()).subscribe()
loadFriends().subscribeOn(Schedulers.io()).subscribe()
I want that loadFriends() makes only one https request but in this case I have two http request
I know how to solve this problem in blocking way:
The solution is to make loadFriends() blocking.
private val lock = Object()
prival var inMemoryCache: List<Friends>? = null
fun loadFriends(): Single<List<Friend>> {
return Single.fromCallable {
if(inMemoryCache == null) {
synchronize(lock) {
if(inMemoryCache == null) {
inMemoryCache = webService.getFriends().blockingGet()
}
}
}
inMemoryCache
}
But I want to solve this problem in a reactive way
You can remedy this by creating one common source for all your consumers to subscribe to, and that source will have the cache() operator invoked against it. The effect of this operator is that the first subscriber's subscription will be delegated downstream (i.e. the network request will be invoked), and subsequent subscribers will see internally cached results produced as a result of that first subscription.
This might look something like this:
class Friends {
private val friendsSource by lazy { webService.getFriends().cache() }
fun someFunction() {
// 1st subscription - friends will be fetched from network
friendsSource
.subscribeOn(Schedulers.io())
.subscribe()
// 2nd subscription - friends will be fetched from internal cache
friendsSource
.subscribeOn(Schedulers.io())
.subscribe()
}
}
Note that the cache is indefinite, so if periodically refreshing the list of friends is important you'll need to come up with a way to do so.
Let's consider this function
#Transactional
fun conditionalInsertEntity(dbEntity: DBEntity): Mono<DBEntity> {
return fetchObjectByPublicId(dbEntity.publicId)
.switchIfEmpty {
r2DatabaseClient.insert()
.into(DBEntity::class.java)
.using(Flux.just(dbEntity))
.fetch()
.one()
.map { it["entity_id"] as Long }
.flatMap { fetchObjectById(it) }
}
}
while running above function with following driver code I get duplicate entry errors if the list contains duplicates. Ideally it shouldn't give that error because the above function is already handling the case for duplicate inserts!!
val result = Flux.fromIterable(listOf(dbEntity1, dbEntity1, dbEntity2))
.flatMap { conditionalInsertEntity(it) }
.collectList()
.block()
Realized that this is an issue of using flatMap instead of concatMap.
ConcatMap collects the result from individual publishers sequentially unlike flatMap. (more here)
Because I used flatMap, multiple publishers thought that the entity isn't already available in the DB
Goal: I want to repeatedly call a Retrofit service (GET) that returns paged data, until I've exhausted its pages. Going from page 0 to page n.
First, I've looked at these two answers already. The first actually works, but I'm not overly fond of the recursive solution as it could lead to stack overflow. The second fails the moment you try to use a scheduler.
Here's a sample of the second:
Observable.range(0, 5/*Integer.MAX_VALUE*/) // generates page values
.subscribeOn(Schedulers.io()) // need this to prevent UI hanging
// gamesService uses Schedulers.io() by default
.flatMapSingle { page -> gamesService.getGames(page) }
.takeWhile { games -> games.isNotEmpty() } // games is a List<Game>
.subscribe(
{ games -> db.insertAll(games) },
{ Logger.e(TAG, it, "Error getting daily games: ${it.message}") }
)
What I expect this to do is stop the moment that gamesService.getGames(page) returns an empty list. Instead, it continues hitting the endpoint for an indeterminate number of times, with incrementing page values. I have experimented a bit in unit tests with Single.just(intVal) and determined that the problem appears to be the fact that my service is automatically subscribed on Schedulers.io(). This is how I define my Retrofit services:
private inline fun <reified T> createService(okClient: OkHttpClient): T {
val rxAdapter = RxJava2CallAdapterFactory.createWithScheduler(Schedulers.io())
val retrofit = Retrofit.Builder()
.baseUrl(config.apiEndpoint.endpoint())
.client(okClient)
.addCallAdapterFactory(rxAdapter)
.addConverterFactory(moshiConverterFactory())
.build()
return retrofit.create(T::class.java)
}
It's really not an option to not use createWithScheduler() here.
Here's another idea I tried:
val atomic = AtomicInteger(0)
Observable.generate<Int> { it.onNext(atomic.getAndIncrement()) }
.subscribeOn(Schedulers.io())
.flatMapSingle { page -> gamesService.getGames(page) }
.takeWhile { games -> games.isNotEmpty() }
.subscribe(
{ games -> dailyGamesDao.insertAll(games) },
{ Logger.e(TAG, it, "Error getting daily games: ${it.message}") }
)
This is another case where it worked as expected right up until I introduced a Scheduler. The generator generates way too many values, when I'm expecting it to stop when the takeWhile discovers an empty list.
I've also tried various kinds of concat (concatWith, concatMap, etc).
At this point, I'm really just looking for someone to help me correct the obvious (to them) and completely basic misunderstanding I clearly have with RxJava operators.
I have found a partial solution. (I may edit this answer later if and when I find my "final" solution.)
tl;dr I should convert my Singles to Observables and use the flatMap overload that takes a maxConcurrency parameter. For example:
Observable.range(0, SOME_SUFFICIENTLY_LARGE_NUMBER)
.subscribeOn(Schedulers.io())
.flatMap({ page -> gamesService.getGames(page).toObservable }, 1 /* maxConcurrency */)
.takeWhile { games -> games.isNotEmpty() }
.subscribe(
{ games -> dailyGamesDao.insertAll(games) },
{ Logger.e(TAG, it, "Error getting daily games: ${it.message}") }
)
That basically does it. By limiting the number of concurrent threads to 1, I now have the "one after the other" behavior I was seeking. The only thing I don't like about this, and I suppose it's a minor gripe, is that my base Observable.range() can still emit a lot of values -- way more than ever get used by the downstream Singles/Observables.
PS: One reason I couldn't find this solution earlier is I was using RxJava 2.1.9. When I pushed it to 2.1.14, I had access to the new overloads. Oh well.