How to implement a Observable.concatEagerDelayError or an equivalent in RxJava2/RxKotlin2 ?
There is :
Observable.concatEager
Observable.concatDelayError
But not :
Observable.concatEagerDelayError
What i have :
fun getAll(): Observable<List<User>> = Observable.concatArrayDelayError(
// from db
userDAO
.selectAll()
.subscribeOn(ioScheduler),
// from api
userAPI
.getAll()
.doOnNext { lstUser -> Completable.concatArray(
userDAO.deleteAll().subscribeOn(ioScheduler),
userDAO.save(lstUser).subscribeOn(ioScheduler)
) }
.subscribeOn(ioScheduler)
)
I want same behaviour but eagerly for selectAll() and getAll() because there is no reason to wait from db to launch network call.
Use concatMapEagerDelayError:
Observable.fromIterable(sources)
.concatMapEagerDelayError(v -> v, true);
Observable.fromArray(source1, source2, source3)
.concatMapEagerDelayError(v -> v, true);
JavaDoc.
Edit:
fun getAll(): Observable<List<User>> = Observable.fromArray(
// from db
userDAO
.selectAll()
.subscribeOn(ioScheduler),
// from api
userAPI
.getAll()
// --- this makes no sense by the way -------------------
.doOnNext { lstUser -> Completable.concatArray(
userDAO.deleteAll().subscribeOn(ioScheduler),
userDAO.save(lstUser).subscribeOn(ioScheduler)
)}
// ------------------------------------------------------
.subscribeOn(ioScheduler)
)
.concatMapEagerDelayError({ v -> v }, true)
Related
I have following code
#Transactional(readOnly = true)
override fun getProjects(
pageNum: Int,
pageSize: Int,
sortBy: List<com.fmetric.project_mgmt_api.util.Sort>,
filter: ProjectFilterDescriptor,
full: Boolean
): PageDescriptor<ProjectDescriptor> {
val orders = mutableListOf<Sort.Order>()
sortBy.forEach { sort -> orders.add(Sort.Order(map(sort.sortOrder), sort.sortBy)) }
val sort = Sort.by(orders)
val page = PageRequest.of(pageNum, pageSize, sort)
val spec = buildSpecification(filter)
val projectsPage = projectRepository.findAll(spec, page)
return Page(
projectsPage.number,
projectsPage.size,
projectsPage.content.map { p -> map(p, full) },
projectsPage.totalElements,
map(projectsPage.sort)
)
}
...
interface ProjectRepository : JpaSpecRepository<Project>, PagingAndSortingRepository<Project, UUID?> {
...
#PreAuthorize("addPermissionFilter(#spec, 'Project', 'View')")
override fun findAll(spec: Specification<Project>?, pageable: Pageable): Page<Project>
...
package org.springframework.data.jpa.repository
public interface JpaSpecificationExecutor<T> {
...
Page<T> findAll(#Nullable Specification<T> var1, Pageable var2);
...
it generates following hibernate log with six HSQL queries, two of them are duplicates, why?
https://gist.github.com/iva-nova-e-katerina/b336f34b666cbb69c26f682b378ca311
UPD: this is the classical "Hibernate N+1 problem" and the best working solution is described here https://medium.com/geekculture/resolve-hibernate-n-1-problem-f0e049e689ab . Problem is solved.
I follow MVVM Login API, with Retrofit ,My problem is livedata is observed more than twice and always emitting previous response when observed from activity, But inside Repository its giving correct response
I tried a lot of solutions from stackoverflow and other websites but still no luck, Tried removing observers also but still getting previous data ,so plz suggest a working solution, I will post my code below,
LoginActivity.kt
private lateinit var loginViewModel: LoginViewModel
loginViewModel = ViewModelProvider(this, ViewModelProvider.NewInstanceFactory()).get(
LoginViewModel::class.java
)
loginViewModel.login(userEmail, pwd)
loginViewModel.getLoginRepository().observe(this, Observer {
val loginResult = it ?: return#Observer
val accessToken = loginResult.user?.jwtToken.toString()
})
val statusMsgObserver = Observer<String> { statusMsg ->
showToast(statusMsg)
})
val errorMsgObserver = Observer<String> { errorMsg ->
// Update the UI
showToast(errorMsg)
})
loginViewModel.getStatusMessage()?.observe(this, statusMsgObserver)
loginViewModel.getErrorStatusMessage()?.observe(this, errorMsgObserver)
LoginViewModel.kt:
class LoginViewModel: ViewModel() {
private var loginRepository: LoginRepository? = null
private var _mutableLiveData = MutableLiveData<LoginAPIResponse?>()
val liveData: LiveData<LoginAPIResponse?> get() = _mutableLiveData
private var responseMsgLiveData:MutableLiveData<String>?= null
private var errorResponseMsgLiveData:MutableLiveData<String>?= null
fun login(username: String, password: String) {
loginRepository = LoginRepository.getInstance()!!
/* Query data from Repository */
//val _mutableLiveData: MutableLiveData<Response<LoginAPIResponse?>?>? = loginRepository?.doLogin(username, password)
_mutableLiveData = loginRepository?.doLogin(username, password)!!
responseMsgLiveData = loginRepository?.respMessage!!
errorResponseMsgLiveData = loginRepository?.loginResponseErrorData!!
}
fun getLoginRepository(): LiveData<LoginAPIResponse?> {
return liveData
}
fun getStatusMessage(): LiveData<String>? {
return responseMsgLiveData
}
fun getErrorStatusMessage(): LiveData<String>? {
return errorResponseMsgLiveData
}
}
LoginRepository.kt:
class LoginRepository {
private val loginApi: ApiEndpoints = RetrofitService.createService(ApiEndpoints::class.java)
val responseData = MutableLiveData<LoginAPIResponse?>()
var respMessage = MutableLiveData<String>()
var loginResponseErrorData = MutableLiveData<String>()
fun doLogin(username: String, password: String)
: MutableLiveData<LoginAPIResponse?> {
respMessage.value = null
loginResponseErrorData.value = null
val params = JsonObject()
params.addProperty("email", username)
params.addProperty("password",password)
val jsonParams = JsonObject()
jsonParams.add("user",params)
loginApi.loginToServer(jsonParams).enqueue(object : Callback<LoginAPIResponse?> {
override fun onResponse( call: Call<LoginAPIResponse?>, response: Response<LoginAPIResponse?> ) {
responseData.value = response.body()
respMessage.value = RetrofitService.handleError(response.code())
val error = response.errorBody()
if (!response.isSuccessful) {
val errorMsg = error?.charStream()?.readText()
println("Error Message: $errorMsg")
loginResponseErrorData.value = errorMsg
} else {
println("API Success -> Login, $username, ${response.body()?.user?.email.toString()}")
}
}
override fun onFailure(call: Call<LoginAPIResponse?>, t: Throwable) {
println("onFailure:(message) "+t.message)
loginResponseErrorData.value = t.message
responseData.value = null
}
})
return responseData
}
companion object {
private var loginRepository: LoginRepository? = null
internal fun getInstance(): LoginRepository? {
if (loginRepository == null) {
loginRepository = LoginRepository()
}
return loginRepository
}
}
}
In onDestroy(),I have removed the observers,
override fun onDestroy() {
super.onDestroy()
loginViewModel.getLoginRepository()?.removeObservers(this)
this.viewModelStore.clear()
}
In LoginActivity, when I observe loginResult it gives previous emitted accessToken first and then again called and giving current accessToken, Similarly observer is called more than twice everytime.
But inside repository,its giving recent data, plz check my code and suggest where I have to correct to get correct recent livedata
Finally i found the solution, In LoginRepository, I declared responseData outside doLogin(), It should be declared inside doLogin()
Since it was outside the method, it always gave previous data first and then current data,
Once I declare inside method problem was solved and now it is working Perfect!!!
I'm trying to build an MVVM Kotlin app that uses Reactivex to make the Async calls to the API.
I've seen alot of threads with the same problem but on all of them the OP forgot to put the subscribeOn in the call, in my code i do this and still have the network error. I've looked in the debugger to find the problem but to no succes.
class NoteViewModel(apiKey : String) : ViewModel () { // , Observable {
private lateinit var noteAdapter : NoteAdapter
private val notesList = MutableLiveData <ArrayList<Note>>()
private var subscription : Disposable
private val noteCollector = NoteCollector()
private val isLoading = MutableLiveData<Boolean>()
init {
subscription = noteCollector.getAll(apiKey)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnSubscribe { onCollectNotesStart() }
.doOnTerminate { onCollectNotesFinished() }
.subscribe(
{ result -> onCollectNotesSuccess(result) },
{ error -> onCollectNotesError(error) }
)
}
...
}
If you need more info let me know, i'll provide it.
EDIT: added stacktrace
E/AndroidRuntime: FATAL EXCEPTION: main
Process: jari.student.hogent.cominghome, PID: 2532
java.lang.RuntimeException: Unable to start activity ComponentInfo{jari.student.hogent.cominghome/jari.student.hogent.cominghome.Activities.MainActivity}: android.os.NetworkOnMainThreadException
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2955)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3030)
at android.app.ActivityThread.-wrap11(Unknown Source:0)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1696)
at android.os.Handler.dispatchMessage(Handler.java:105)
at android.os.Looper.loop(Looper.java:164)
at android.app.ActivityThread.main(ActivityThread.java:6938)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:327)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1374)
Caused by: android.os.NetworkOnMainThreadException
at android.os.StrictMode$AndroidBlockGuardPolicy.onNetwork(StrictMode.java:1448)
at java.net.Inet6AddressImpl.lookupHostByName(Inet6AddressImpl.java:102)
at java.net.Inet6AddressImpl.lookupAllHostAddr(Inet6AddressImpl.java:90)
at java.net.InetAddress.getAllByName(InetAddress.java:787)
at com.android.okhttp.Dns$1.lookup(Dns.java:39)
at com.android.okhttp.internal.http.RouteSelector.resetNextInetSocketAddress(RouteSelector.java:200)
at com.android.okhttp.internal.http.RouteSelector.nextProxy(RouteSelector.java:148)
at com.android.okhttp.internal.http.RouteSelector.next(RouteSelector.java:90)
at com.android.okhttp.internal.http.StreamAllocation.findConnection(StreamAllocation.java:190)
at com.android.okhttp.internal.http.StreamAllocation.findHealthyConnection(StreamAllocation.java:142)
at com.android.okhttp.internal.http.StreamAllocation.newStream(StreamAllocation.java:104)
at com.android.okhttp.internal.http.HttpEngine.connect(HttpEngine.java:410)
at com.android.okhttp.internal.http.HttpEngine.sendRequest(HttpEngine.java:343)
at com.android.okhttp.internal.huc.HttpURLConnectionImpl.execute(HttpURLConnectionImpl.java:489)
at com.android.okhttp.internal.huc.HttpURLConnectionImpl.connect(HttpURLConnectionImpl.java:131)
at com.android.okhttp.internal.huc.HttpURLConnectionImpl.getOutputStream(HttpURLConnectionImpl.java:262)
at com.github.kittinunf.fuel.toolbox.HttpClient.setBodyIfDoOutput(HttpClient.kt:91)
at com.github.kittinunf.fuel.toolbox.HttpClient.executeRequest(HttpClient.kt:39)
at com.github.kittinunf.fuel.core.requests.TaskRequest.call(TaskRequest.kt:14)
at com.github.kittinunf.fuel.core.DeserializableKt$response$result$1.invoke(Deserializable.kt:81)
at com.github.kittinunf.fuel.core.DeserializableKt$response$result$1.invoke(Unknown Source:0)
at com.github.kittinunf.result.Result$Companion.of(Result.kt:116)
at com.github.kittinunf.fuel.core.DeserializableKt.response(Deserializable.kt:81)
at com.github.kittinunf.fuel.core.Request.responseObject(Request.kt:344)
at jari.student.hogent.cominghome.Models.NoteCollector.getAll(NoteCollector.kt:22)
at jari.student.hogent.cominghome.ViewModels.NoteViewModel.<init>(NoteViewModel.kt:22)
at jari.student.hogent.cominghome.ViewModels.CustomViewModelFactory.create(CustomViewModelFactory.kt:11)
at android.arch.lifecycle.ViewModelProvider.get(ViewModelProvider.java:134)
at android.arch.lifecycle.ViewModelProvider.get(ViewModelProvider.java:102)
at jari.student.hogent.cominghome.Fragments.NoteFragment.onCreateView(NoteFragment.kt:27)
at android.support.v4.app.Fragment.performCreateView(Fragment.java:2439)
at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1460)
at android.support.v4.app.FragmentManagerImpl.moveFragmentToExpectedState(FragmentManager.java:1784)
at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1852)
at android.support.v4.app.BackStackRecord.executeOps(BackStackRecord.java:802)
at android.support.v4.app.FragmentManagerImpl.executeOps(FragmentManager.java:2625)
E/AndroidRuntime: at android.support.v4.app.FragmentManagerImpl.executeOpsTogether(FragmentManager.java:2411)
at android.support.v4.app.FragmentManagerImpl.removeRedundantOperationsAndExecute(FragmentManager.java:2366)
at android.support.v4.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:2273)
at android.support.v4.app.FragmentManagerImpl.dispatchStateChange(FragmentManager.java:3273)
at android.support.v4.app.FragmentManagerImpl.dispatchActivityCreated(FragmentManager.java:3229)
at android.support.v4.app.FragmentController.dispatchActivityCreated(FragmentController.java:201)
at android.support.v4.app.FragmentActivity.onStart(FragmentActivity.java:620)
at android.support.v7.app.AppCompatActivity.onStart(AppCompatActivity.java:178)
at android.app.Instrumentation.callActivityOnStart(Instrumentation.java:1340)
at android.app.Activity.performStart(Activity.java:7200)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2918)
... 9 more
EDIT 2: added getAll methode
override fun getAll(ApiKey: String): Observable<ArrayList<Note>> {
val bodyJson: JSONObject = JSONObject()
.put("apiKey", ApiKey)
val notesList: ArrayList<Note> = ArrayList()
val (_, _, result) = path.httpPost()
.header(mapOf("Content-Type" to "application/json"))
.body(bodyJson.toString())
.responseObject(Note.Deserializer())
val (notes, err) = result
if (err != null){
throw err.exception
}
notes?.forEach { note ->
notesList.add(note)
}
return Observable.fromArray(notesList)
}
Please change your getAll implementation to this:
override fun getAll(ApiKey: String): Observable<ArrayList<Note>>
= Observable.create { emitter ->
val bodyJson: JSONObject = JSONObject()
.put("apiKey", ApiKey)
val notesList: ArrayList<Note> = ArrayList()
val (_, _, result) = path.httpPost()
.header(mapOf("Content-Type" to "application/json"))
.body(bodyJson.toString())
.responseObject(Note.Deserializer())
val (notes, err) = result
if (err != null) {
throw err.exception
}
notes?.forEach { note ->
notesList.add(note)
}
emitter.onNext(notesList)
}
I have this method deleteFeedTable() which returns a Completable and when it finishes I want to start another Disposable.
What I did is combine the two using operator concatWith, but this results in a nested subscription and I'd like to avoid that.
disposables.add(
localDataSource.deleteFeedTable()
.doOnComplete(() -> { preferencesManager.setFeedTableUpdateState(false);
})
.concatWith(new Completable() {
#Override
protected void subscribeActual(CompletableObserver s) {
s.onSubscribe(localDataSource.getLastStoredId()
.flatMap(lastStoredId -> remoteDataSource.getFeed(lastStoredId))
.doOnNext(feedItemList -> localDataSource.saveFeed(feedItemList))
.map(feedItemList -> {
Timber.i("MESA STO MAP");
List<Feed> feedList = new ArrayList<>();
for (FeedItem feedItem : feedItemList) {
feedList.add(mapper.from(feedItem));
}
downloadImageUseCase.downloadPhotos(feedList);
return feedList;
})
.subscribe());
}
})
.subscribeOn(schedulerProvider.io())
.observeOn(schedulerProvider.mainThread())
.subscribe(() -> {}, throwable -> Log.i("THROW", "loadData ", throwable)));
Is there a way I can avoid the nested subscription ? Or is there another way to add it to the disposables variable so I can clear the subscription later ?
Use andThen:
disposables.add(
localDataSource.deleteFeedTable()
.doOnComplete(() -> {
preferencesManager.setFeedTableUpdateState(false);
})
.andThen(
localDataSource.getLastStoredId()
.flatMap(lastStoredId -> remoteDataSource.getFeed(lastStoredId))
.doOnNext(feedItemList -> localDataSource.saveFeed(feedItemList))
.map(feedItemList -> {
Timber.i("MESA STO MAP");
List<Feed> feedList = new ArrayList<>();
for (FeedItem feedItem : feedItemList) {
feedList.add(mapper.from(feedItem));
}
downloadImageUseCase.downloadPhotos(feedList);
return feedList;
})
)
.subscribeOn(schedulerProvider.io())
.observeOn(schedulerProvider.mainThread())
.subscribe(() -> {}, throwable -> Log.i("THROW", "loadData ", throwable)));
I have an API endpoint that can different result count based on request parameters. Parameters are page, per_page, query and others.
fun getItems(params : Map<String, String>) : Single<ItemsResponse>
data class ItemsResponse(
val hasMore : Boolean,
val items : List<Items>
)
API is not trustworthy and could return less than per_page. I want to ensure, that I always get result count I need and cache remainder for next request cycle.
For example something
val page : Int = 1
fun fetchItems(requestedItems : Int = 20) : Single<List<Items>> {
...
.map { buildParams(page, perPage, query) }
.flatMap { api.getItems(it) }
.doOnSuccess { page++ }
.buffer(requestedItems)
}
fun buildParams(page: Int, perPage: Int, query : String) : Map<String, String> {
...
}
Example scenario:
Caller requests 20 items for the first time.
Call to api.getItems() with page: 1, per_page is always 20.
Call returns 16 items
Call to api.getItems() with page: 2
Call return 19 items
20 items were returned to caller and 15 remaining items were cached for next caller request.
Caller requests 20 items for 2nd time.
Call to api.getItems() with page: 3
Call returns 12 items
20 items were returned to caller (15 older ones and 5 from last response) and 7 remaining items were cached for next caller requests.
And so on and so forth.
This looks like Producer-Consumer pattern, but is doable in RxJava2?
Edit: based on the additional info
Requires: RxJava 2 Extensions library: compile "com.github.akarnokd:rxjava2-extensions:0.17.0"
import hu.akarnokd.rxjava2.expr.StatementObservable
import io.reactivex.Observable
import io.reactivex.functions.BooleanSupplier
import io.reactivex.subjects.PublishSubject
import java.util.concurrent.Callable
import java.util.concurrent.ConcurrentLinkedQueue
import java.util.concurrent.ThreadLocalRandom
var counter = 0;
fun service() : Observable<String> {
return Observable.defer(Callable {
val n = ThreadLocalRandom.current().nextInt(21)
val c = counter++;
Observable.range(1, n).map({ v -> "" + c + " | " + v })
})
}
fun getPage(pageSignal : Observable<Int>, pageSize: Int) : Observable<List<String>> {
return Observable.defer(Callable {
val queue = ConcurrentLinkedQueue<String>()
pageSignal.concatMap({ _ ->
StatementObservable.whileDo(
service()
.toList()
.doOnSuccess({ v -> v.forEach { queue.offer(it) }})
.toObservable()
, BooleanSupplier { queue.size < pageSize })
.ignoreElements()
.andThen(
Observable.range(1, pageSize)
.concatMap({ _ ->
val o = queue.poll();
if (o == null) {
Observable.empty()
} else {
Observable.just(o)
}
})
.toList()
.toObservable()
)
})
})
}
fun main(args: Array<String>) {
val pages = PublishSubject.create<Int>();
getPage(pages, 20)
.subscribe({ println(it) }, { it.printStackTrace() })
pages.onNext(1)
pages.onNext(2)
}