I'm trying to make two requests when user clicks on a button.
However, the request might take a little while.
When the user is on battery save mode and screens lock his device while the request is still being done, the request will not complete and will give a socket timeout exception.
I made a sample project to try this out and you can find it here.
I'm using retrofit and RxJava to make my requests like this:
networkFactory.request()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ result -> Log.d("TAG", "The value is this $result") },
{ error -> Log.e("TAG", "Ohoh an error ${Log.getStackTraceString(error)}")
})
My networkFactory request() is:
fun request(): Observable<Doc> {
return service.request(API_KEY)
}
with the following interface:
#GET("articlesearch.json")
fun request(#Query("api-key") apiKey : String) : Observable<Doc>
Am I doing something wrong here?
Related
I'm getting an unwanted behavior/flaw when passing a list of Observable Network calls to Observable.Zip(), similar to this accepted answer:
How to make multiple request and wait until data is come from all the requests in retrofit 2.0 - android
And the unwanted behavior is, the network calls are being fired twice...
It's fired once when the Observable is added to the List, and then it's fired again during the Observable.zip()
Here's a boiled down snippet from my project that's reproducing the behavior:
fun buildListOfObservableNetworkCalls(): Observable<Map<String, String?>> {
val clients = mutableListOf<Observable<NetworkResponse>>()
if (NetworkClient1.featureFlag) {
val postBody = someMethodToBuildPostBody()
clients.add(NetworkClient1().executeClient(postBody))
}
//There will be multiple NetworkClients in the near future
return executeAllNetworkClients(headerBidClients)
}
private fun executeAllNetworkClients(clients: List<Observable<NetworkResponse>>): Observable<Map<String, String?>> =
if (clients.isNotEmpty()) {
Observable.zip(clients) {
it
}
.map { clientResults ->
clientResults.forEach { response ->
if (response is NetworkResponse) {
map[MY_KEY] += response.stringResult
}
}
map
}.doOnSubscribe {
android.util.Log.d("LOGGER", "zip ON SUBSCRIBE")
}
} else {
Observable.just(mapOf())
}
//**** My NetworkClient1 class containing the RxJava function that executes the network call ****//
override fun executeClient(postBody: CustomPostBody): Observable<NetworkResponse> =
retrofitApiInterface.networkCall1Request(postBody)
.subscribeOn(Schedulers.io())
.doOnSuccess { response ->
Log.d("LOGGER", "Client1 ON SUCCESS")
}
.flatMapObservable { response ->
Observable.just(
NetworkResponse(
response
)
)
}.onErrorResumeNext { throwable: Throwable? ->
android.util.Log.d("LOGGER", "Client1 ON ERROR")
Observable.just(
NetworkResponse(
""
)
)
}.doOnSubscribe {
android.util.Log.d("LOGGER", "Client1 ON SUBSCRIBE")
}
//***** And my custom Interceptor which logs the double requests ****////
class MyInterceptor: Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
Log.d("LOGGER", "intercept request at ${System.currentTimeMillis()}")
val response = chain.proceed(request)
Log.d("LOGGER", "intercept response at ${System.currentTimeMillis()}")
return response
}
}
And the log output is:
D/LOGGER: zip ON SUBSCRIBE
D/LOGGER: Client1 ON SUBSCRIBE
D/LOGGER: intercept request at 1650059924358
D/LOGGER: intercept response at 1650059925747
D/LOGGER: Client1 ON SUCCESS
D/LOGGER: intercept request at 1650059925782
D/LOGGER: intercept response at 1650059925928
As you can see, the same network call is being executed twice.. and secondly, i'm also a bit puzzled as to why doOnSuccess isnt also called twice.
So, my main questions is, is there a way that I can build a list of Observables and pass it to Observable.zip() without executing the network call twice?
I see the issue is that I'm creating a List<Observable<NetworkResponse>> and in order to add network calls that return <Observable<NetworkResponse>>, I have to invoke the method as i'm adding them to the list. I know this may sound like a dumb question.. but is it at all possible to have a set-up where i'm able to add the Observable functions to the List without executing them? Probably over doing it, but would creating an extension function of .zip(iterable) which accepts a list of NetworkClients as the sources param and within the extension function, execute source.executeClient() be a feasible or stable solution?
I feel it would be inefficient if this was the unavoidable consequence of building a list of Observables to pass to zip(iterable), so i'm hoping that this is just a matter of my set-up rather than an overlooked consequence of the .zip(iterable) method.
I'm aware that I could avoid the above scenario by trying to pass each Observable Network call individually into the .zip() and use some sort of BiFunction to tie it all together. However, that doesn't seem very intuitive for my use case, being that I have to featureFlag check and build Post objects for each request that i'll be doing. Additionally, I'll be adding more NetWorkClients who's responses will all be returning the same base response type over the next few months, so I find the .zip(iterable) methodology as a clean and very scalable way of plugging in new NetworkClients.
Problem
I want to get the result of a get request by doing a synchronous call to an API by using Fuel as Httpclient.
I'm using Fuel in an Android (Anko) project.
The call is just a simple get request which always fails with this error:
Failure: com.github.kittinunf.fuel.core.BubbleFuelError: null
Caused by: com.github.kittinunf.fuel.core.BubbleFuelError: null
Background
I want to make a function for returning a result of a simple get request using Fuel. But I'm not able to retrieve the the result synchronous.
I cannot find any useful information about this subject on the internet.
I tried to await the result by using coroutines and use the awaitStringResponse function. --> Did not worked as expected.
Just responded to a Github issue covering this topic (marked as bug).
https://github.com/kittinunf/fuel/issues/606
Is there some workaround?
Code example
This code is working:
requestUrl.httpGet().responseString { _, _, result ->
when (result) {
is Result.Success -> {
// do something on success
}
is Result.Failure -> {
// do something on fail
}
}
}
But using this function, I am not able to return the result.
This code is NOT working
val (_,_,result)= Fuel.get(requestUrl).responseString()
I found a way to solve this with kotlin coroutines.
fun doRequest() = runBlocking {
val (_, _, result) = Fuel.get("https://jsonplaceholder.typicode.com/posts/1").awaitStringResponse()
result
}
Using runBlocking will block the current thread until it's completed.
Source: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/run-blocking.html
When you don't want to block the current thread you can start this function in a new thread like this:
Thread(
Runnable {
val result = doRequest()
Log.e("Result", result)
}
).start()
If someone knows a better way to handle this, please show your solution.
I am developing an android app using Kotlin, RxJava, and Retrofit.
I am sending a request to delete a resource.
HTTP - DELETE
And the response is 204 No Content.
My retrofit code is below:
#DELETE("res/{resId}")
fun deleteJob(#Path("resId") resId: String): Observable<Unit>
In this case I don't know how to define the return type.
So I defined "Observable".
Because there are no response body.
Response code is 204.
And below is my presenter code:
override fun deleteRes(resId: String) {
restService.deleteRes(resId)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
// here is not called
}, {
// Always here is called, because the response is 204.
if (it is NoContentException) { // I defined new exception.
view.removeRes(resId)
} else {
Log.e(TAG, "deleteRes - failed: ${it.message}")
}
})
}
I want to test this Presenter function.
Below is my test code:
#Test
fun deleteResTest() {
val deleteResId = "delete_res_id"
Mockito.`when`(mockRestService.deleteRes(deleteResId)).thenReturn(Observable.just(Unit))
mockRestService.deleteRes(deleteResId)
.toFlowable(BackpressureStrategy.BUFFER)
.subscribe(TestSubscriber.create<Unit>())
mJobsPresenter.deleteRes(deleteResId)
Mockito.verify(mockView).removeRes(deleteResId)
}
But when I run this test code, it's failed like this:
Wanted but not invoked:
view.removeRes("delete_res_id");
-> at com.RessPresenterTest.deleteResTest(ResPresenterTest.kt:95)
Actually, there were zero interactions with this mock.
Wanted but not invoked:
view.removeRes("delete_res_id");
-> at com.RessPresenterTest.deleteResTest(ResPresenterTest.kt:95)
Actually, there were zero interactions with this mock.
at com.RessPresenterTest.deleteResTest(ResPresenterTest.kt:95)
Somebody help me, please?
I suggest you to use Completable instead of Observable for "204 no content" responses, because these responses have not any content and we just need the onComplete and onError methods. so you can create a Completable and call onComplete method in test.
I currently have an EditText for the user to enter a search. I'm trying to use RxJava with debounce to only search every so often, instead of each character. However, I'm getting an InterruptedIOException while I'm testing, which kills the stream.
private val subject = BehaviorSubject.create<String>()
init {
configureAutoComplete()
}
private fun configureAutoComplete() {
subject.debounce(200, TimeUnit.MILLISECONDS)
.flatMap {
getSearchResults(query = it)
}
.subscribe({ result ->
handleResult(result)
}, { t: Throwable? ->
Logger.e(t, "Failed to search")
})
}
fun getSearchResults(query: String): Observable<List<MyObject>> {
val service = NetworkService.create() // get retrofit service
return service.search(query)
}
fun search(text: String) {
subject.onNext(text)
}
As you can see, I'm creating a BehaviorSubject, and within init I'm setting it up with debounce.
getSearchResult returns an Observable and does my network request.
But as I'm testing, if I type at a specific rate ( usually quick-ish, like typing another character while the request is ongoing ) it'll throw an Exception.
Failed to search : java.io.InterruptedIOException
at okhttp3.internal.http2.Http2Stream.waitForIo(Http2Stream.java:579)
at okhttp3.internal.http2.Http2Stream.takeResponseHeaders(Http2Stream.java:143)
at okhttp3.internal.http2.Http2Codec.readResponseHeaders(Http2Codec.java:125)
I was looking at this, https://stackoverflow.com/a/47276430/3106174, and it seems like I'm doing everything correctly.
After more testing, I realized that the network request was on the main thread.
You can test this by replacing your network call with Observerable.create{ ... } and throwing a Thread.sleep(1000) inside.
I was following this tutorial, https://proandroiddev.com/building-an-autocompleting-edittext-using-rxjava-f69c5c3f5a40, and one of the comments mention this issue.
"But I think one thing is misleading in your code snippet, and it’s
that subjects aren’t thread safe. And the thread that your code will
run on will be the thread that you emitting on (in this case the main
thread). "
To solve this issue, you need to force it to run on Schedulers.io(). Make sure it's after the debounce or it won't work.
private fun configureAutoComplete() {
subject.debounce(200, TimeUnit.MILLISECONDS)
.observeOn(Schedulers.io()) // add this here
.distinctUntilChanged()
.switchMap {
getSearchResults(query = it)
}
.subscribe({ result ->
handleResult(result)
}, { t: Throwable? ->
Logger.e(t, "Failed to search")
})
}
I'm trying to create a test using JUnit and Mockito for the following scenario: make a call to the server, in case the response is not successful, retry the request.
repository.uploadModel(model)
.subscribeOn(schedulers.io())
.observeOn(schedulers.ui())
.repeatWhen (repeatTimer)
.subscribe({
if (it.isSuccessful) {
mvpView?.showUploadComplete()
} else {
mvpView?.showGeneralUploadError()
}
}, {
it.printStackTrace()
})
So far I came up with this:
val model = Mockito.mock(Model::class.java)
val errorResponse = Response.error<Any>(500, ResponseBody.create(null, ""))
whenever(repository.uploadModel(model))
.thenReturn(Flowable.just(errorResponse))
presenter?.uploadModel()
testScheduler?.triggerActions()
verify(view, atLeast(5)).pendingUpload()
What actually happens: showGeneralUploadError() is called only once and then the test ends and fails.
What I want to happen: call showGeneralUploadError() multiple times
Extra info:
repeatTimer is defined as { it: Flowable<Any> -> it.delay(0, TimeUnit.SECONDS)} for unit testing
repeatTimer is defined as { it: Flowable<Any> -> it.delay(5, TimeUnit.SECONDS)} for production
This is only sample code to demonstrate the problem, not actual code
The takeUntil() operator would only respond to data emitted via onNext(). Since your test never emits a data value, there is nothing for it to do.
It's not clear from your sample code what you are trying to accomplish, so I can't suggest what you can do to fix this.
The problem was at repeatTimer. I tried to declare it for mocking purposes as:
repeatTimer is defined as { it: Flowable<Any> -> it.delay(0, TimeUnit.SECONDS)}
but it should have been just
{ it: Flowable<Any> -> it}