I'm currently using the algorithm networkboundresource provided by google, it has it's limitations and I wanted to know what is the best way to go about it?
I want to fetch data, cache it and to update the cached data whenever new data has been added to the server/remote database
override suspend fun getSelectRoute(
id: String,
date: String,
date2: String
): Flow<Resource<List<SelectRoute>>> {
return networkBoundResource(
query = {
selectRouteEntityMapper.mapToDomainModelFlowList(selectRouteDao.getSelectRoute())
},
fetch = {
selectRouteDtoMapper.mapToDomainModelList(
selectRouteService.getSelectRoute(
id = id,
date = date,
date2 = date2
)
)
},
saveFetchResult = {
actraDB.withTransaction {
selectRouteDao.deleteAllSelectRoute()
completedRoutesDao.deleteCompletedRoutes()
it.forEach {
selectRouteDao.insertSelectRoute(
selectRouteEntityMapper.mapFromDomainModel(it)
)
completedRoutesDao.insertCompletedRoute(
completedRoutesEntityMapper.mapFromDomainModel(it)
)
Log.d("repolist", "${it.address1}")
}
}
}
)
inline fun <ResultType, RequestType> networkBoundResource(
crossinline query: () -> Flow<ResultType>,
crossinline fetch: suspend () -> RequestType,
crossinline saveFetchResult: suspend (RequestType) -> Unit,
crossinline shouldFetch: (ResultType) -> Boolean = { true }
) = flow {
val data = query().first()
val flow = if (shouldFetch(data)) {
emit(Resource.Loading(data))
try {
saveFetchResult(fetch())
query().map { Resource.Success(it) }
} catch (throwable: Throwable) {
query().map { Resource.Error(throwable.message, it) }
}
}else{
query().map { Resource.Success(it) }
}
emitAll(flow = flow)
}
Related
I'm instantiating following variable:
phoneViewModel = ViewModelProvider(this).get(PhoneViewModel::class.java).also {it.initialRead()}
The initialRead() calls another function which retrieves data async. When I use the phoneViewModel variable in my application, the application crashes because initialRead() hasn't finished yet. How can I execute another function for example usePhoneViewModel() after the "async" instantiation has been finished?
public fun initialRead(onError: ((errorMessage: String) -> Unit)? = null) {
if (!isDownloadError) {
repository.initialRead(
Action0 { isDownloadError = false},
Action1 { error ->
isDownloadError = true
onError?.let {
val resources = getApplication<Application>().resources
onError.invoke(resources.getString(R.string.read_failed_detail))
}
}
)
}
}
and initialRead in the repo
fun initialRead(successHandler: Action0?, failureHandler: Action1<RuntimeException>) {
relatedEntities.clear()
if (initialReadDone && entities.size > 0) {
observableEntities.setValue(entities)
return
}
var dataQuery = DataQuery().from(entitySet)
if (orderByProperty != null) {
dataQuery = dataQuery.orderBy(orderByProperty, SortOrder.ASCENDING)
}
zGW_EXT_SHIP_APP_SRV_Entities.executeQueryAsync(dataQuery,
Action1 { queryResult ->
val entitiesRead = convert(queryResult.entityList)
entities.clear()
entities.addAll(entitiesRead)
initialReadDone = true
observableEntities.value = entitiesRead
successHandler?.call()
},
failureHandler,
httpHeaders)
}
Given this function I don't think you can. Add an onSuccess argument to your initialRead, e.g.:
public fun initialRead(onSuccess: (() -> Unit)? = null, onError: ((errorMessage: String) -> Unit)? = null) {
if (!isDownloadError) {
repository.initialRead(
Action0 {
isDownloadError = false
onSuccess?.invoke()
},
Action1 { error ->
isDownloadError = true
onError?.let {
val resources = getApplication<Application>().resources
onError.invoke(resources.getString(R.string.read_failed_detail))
}
}
)
}
}
and then pass what you want to do there:
ViewModelProvider(this).get(PhoneViewModel::class.java).also {
it.initialRead(onSuccess = { usePhoneViewModel() })
}
I have such code and i want to collect results of api call inside coroutine and then join this job and return it from function
private suspend fun loadRangeInternal(offset: Int, limit: Int): List<Activity> {
// load data from Room database
// calling Retrofit request
// and caching them if there is no data available
var activities: List<Activity> = listOf()
val job = GlobalScope.launch {
repo.loadActivitiesOf(settings.companyId, offset, limit)
.collect {
networkError.send(it.error)
when (it.status) {
Resource.Status.SUCCESS -> {
activities = it.data ?: listOf()
isLoading.send(false)
}
Resource.Status.LOADING -> {
isLoading.send(true)
}
Resource.Status.ERROR -> {
isLoading.send(false)
}
}
}
}
job.join()
Timber.d("Activities loaded: $activities")
return activities
}
I've also tried async instead of launch, and await instead of join
Calculation execution time by using measureTimeMillis{} and delay your function at that time.
private suspend fun loadRangeInternal(offset: Int, limit: Int): List<Activity> {
// load data from Room database
// calling Retrofit request
// and caching them if there is no data available
var activities: List<Activity> = listOf()
val executionTime = measureTimeMillis {
async {
repo.loadActivitiesOf(settings.companyId, offset, limit)
.collect {
networkError.send(it.error)
when (it.status) {
Resource.Status.SUCCESS -> {
activities = it.data ?: listOf()
isLoading.send(false)
}
Resource.Status.LOADING -> {
isLoading.send(true)
}
Resource.Status.ERROR -> {
isLoading.send(false)
}
}
}
}.await()
}
delay(executionTime)
Timber.d("Activities loaded: $activities")
return activities
}
Try in rxJava2 Kotlin combine Single with Flowable but nothing not happening:
Does not undrstand what wrong
Flowable.create<Int>({ emmit ->
loadNewListener = object :Listener {
override fun onEmit(id: Int) {
emmit.onNext(id)
}
}
}, BackpressureStrategy.LATEST)
.debounce(500, TimeUnit.MILLISECONDS)
.flatMapSingle {
loadNew(id = it.id)
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ (data:Data) ->
}, {
Timber.e("Failed load data ${it.message}")
})
my method is returning Single:
private fun loadNew(id: Int): Single<Data> {
return when (pdfType) {
CASE_0 -> {
Single.create<Data> { emmit ->
service.get("data")
.enqueue(
object : Callback<Void> {
override fun onFailure(call: Call<Void>?, t: Throwable?) {
// failure
}
override fun onResponse(call: Call<Void>?, response: Response<Void>?) {
emmit.onSuccess(it.data)
}
}
}//single
}//case_0
CASE_1 -> 1Repository.loadsome1Rx(id = id).map { it.getData() }
CASE_2 -> 2Repository.loadsom2LocalRx(id = id).map { it.getData() }
else -> {
throw java.lang.RuntimeException("$this is not available type!")
}
}
What is wrong im my code?
Need Maby call Single in Flowable subscribe() seppurate
like this?
Flowable.create<Int>({ emmit ->
loadNewListener = object :Listener {
override fun onEmit(id: Int) {
emmit.onNext(id)
}
}
}, BackpressureStrategy.LATEST)
.debounce(500, TimeUnit.MILLISECONDS)
.subscribe({
loadNew(id = it.id)
}, {
Timber.e("")
})
This code is workin but looks not simple as via combine try.
This simple example based on your code is working
var i = 0
fun foo() {
Flowable.create<Int>({ emmit ->
emmit.onNext(i)
i++
}, BackpressureStrategy.LATEST)
.debounce(500, TimeUnit.MILLISECONDS)
.flatMapSingle {
Single.create<String> { emmit ->
emmit.onSuccess("onSuccess: $it")
}
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
Log.i("RX", "Subscribe: $it")
}, {
it.printStackTrace()
})
}
Check SingleEmitter.onSuccess() and SingleEmitter.onError() is called in all cases in when (pdfType)...
As #Stas Bondar said in answer below This simple example based on your code is working!!
Problem was in loadNewListener .
It does not init in time and has null value when need. Call create Flowable on init ViewModel but loadNewListener did not have time to create when i call him from fragment.
loadNewListener = object :Listener{...}
Becuse need some time mutch for init rxJava expression!
And combine flowable with single via flatMapSingle spent more time than just call single on flowable dubscrinbe!
So use temp field:
private var temp: Temp? = null
fun load(id: Int) {
loadNewListener.apply {
when {
this != null -> load(id = id)
else -> userEmitPdfTemp = Temp(id = id)
}
}
}
Flowable.create<Data>({ emmit ->
userEmitPdfTemp?.let {id->
emmit.onNext(Data(id))
userEmitPdfTemp =null
}
loadNewListener = object :Listener {
override fun load(id: Int) {
emmit.onNext(Data(id))
}
}
}
I would like to convert my rxJava Code to Kotlin CoRoutine.
Below is the code makes both the api and db call and returns the data to UI whatever comes first. Let us say if DB response happens to be quicker than the api. In that case still, the api response would continue until it receives the data to sync with db though it could have done the UI update earlier.
How Would I do it?
class MoviesRepository #Inject constructor(val apiInterface: ApiInterface,
val MoviesDao: MoviesDao) {
fun getMovies(): Observable<List<Movie>> {
val observableFromApi = getMoviesFromApi()
val observableFromDb = getMoviesFromDb()
return Observable.concatArrayEager(observableFromApi, observableFromDb)
}
fun getMoviesFromApi(): Observable<List<Movie>> {
return apiInterface.getMovies()
.doOnNext { it ->
it.data?.let { it1 -> MoviesDao.insertAllMovies(it1) }
println("Size of Movies from API %d", it.data?.size)
}
.map({ r -> r.data })
}
fun getMoviesFromDb(): Observable<List<Movie>> {
return MoviesDao.queryMovies()
.toObservable()
.doOnNext {
//Print log it.size :)
}
}
}
As the first step you should create suspend funs for your ApiInterface and MovieDao calls. If they have some callback-based API, you can follow these official instructions.
You should now have
suspend fun ApiInterface.suspendGetMovies(): List<Movie>
and
suspend fun MoviesDao.suspendQueryMovies(): List<Movie>
Now you can write this code:
launch(UI) {
val fromNetwork = async(UI) { apiInterface.suspendGetMovies() }
val fromDb = async(UI) { MoviesDao.suspendQueryMovies() }
select<List<Movie>> {
fromNetwork.onAwait { it }
fromDb.onAwait { it }
}.also { movies ->
// act on the movies
}
}
The highlight is the select call which will simultaneously await on both Deferreds and act upon the one that gets completed first.
If you want to ensure you act upon the result from the network, you'll need some more code, for example:
val action = { movies: List<Movie> ->
// act on the returned movie list
}
var gotNetworkResult = false
select<List<Movie>> {
fromNetwork.onAwait { gotNetworkResult = true; it }
fromDb.onAwait { it }
}.also(action)
if (!gotNetworkResult) {
action(fromNetwork.await())
}
This code will act upon the DB results only if they come in before the network results, which it will process in all cases.
Something along those lines should work:
data class Result(val fromApi: ???, val fromDB: ???)
fun getMovies(): Result {
val apiRes = getMoviesFromApiAsync()
val dbRes = getMoviesFromDbAsync()
return Result(apiRes.await(), dbRes.await())
}
fun getMoviesFromApiAsync() = async {
return apiInterface.getMovies()
.doOnNext { it ->
it.data?.let { it1 -> MoviesDao.insertAllMovies(it1) }
println("Size of Movies from API %d", it.data?.size)
}
.map({ r -> r.data })
}
fun getMoviesFromDbAsync() = async {
return MoviesDao.queryMovies()
}
I don't know what you're returning, so I just put ??? instead.
I have a class A like this:
A {
id: Long
eventId: Long
event: Event
}
B{
id:Long
name: String
}
I want to retrieve A by executing:
aService.getA(id)
then with the result (which has a null event) use eventId to retrieve the proper Event (eventService.getEvent()), assign it to A.event, and then return A.
How can I chain the request to achieve this? I tried flatmap to return the Event but then I lose the result from A.
This is my current implementation:
aRepository.getA().subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnSubscribe {
loadingState.onNext(true)
}
.doOnEvent { t1: Highlight, t2 ->
loadingState.onNext(false)
}
.subscribeWith(object : DisposableSingleObserver<A>() {
override fun onSuccess(a: A) {
aObservable.onNext(a)
}
override fun onError(e: Throwable) {
fetchErrors.onNext(e)
}
})
I tried this:
aRepository.getA()
.flatMap {
a: A ->
val event = eventsRepository.getEvent(a.eventId)
event
}
,subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnSubscribe {
loadingState.onNext(true)
}
.doOnEvent { t1: Highlight, t2 ->
loadingState.onNext(false)
}
.subscribeWith(object : DisposableSingleObserver<A>() {
override fun onSuccess(a: A) {
aObservable.onNext(a)
}
override fun onError(e: Throwable) {
fetchErrors.onNext(e)
}
})
Use flatMap and just map its inner flow back to the updated original value:
aRepository.getA()
.subscribeOn(Schedulers.io())
.flatMap(a -> {
if (a.event == null) {
return eventsRepository.getEvent(a.eventId)
.map(evt -> {
a.event = evt;
return a;
});
}
return Single.just(a);
})
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(/* ... */)
;