I'm using Retrofit2 and RxJava2 for my network requests.
I've a simple runnable that includes:
override fun run() {
handler.postDelayed(this, interval)
DummyService.syncData(context, null)
.observeOn(AndroidSchedulers.mainThread())
.subscribe( response -> {
//nothing
}, e -> {
//nothing
});
}
And my syncData function looks like this:
fun syncData(): Single<Unit> {
Log.d("...", "sync before")
return service.test(null)
.onErrorResumeNext(ErrorHandler(BaseErrorParser()))
.map { jsonApiObject ->
Log.d("...", "sync WHOLE DATA")
....
}
}
In log I can see, that runnable works good - syncData() gets called and I see in logs "sync before" message, but I do not see "sync WHOLE DATA". I did try to place a breakpoint - it shows check (as it was there, but debugger never stops [hits] that point really). I can see that network request is executed, but it does not succeed. Why is that? If I call the very same function normally (not from runnable), it works okay.
your syncData function return Single object. you should execute it by
DummyService.syncData(context, null).subscribe(response -> {
})
Related
Completable.fromAction(() -> startRecording())
.subscribeOn(Schedulers.io())
.subscribe(() -> {
boolean startSuccess = mMediaRecorder.getState() == MediaRecorder.RECORDING_STATE;
if (startSuccess) {
updateView();
startRepeatingTask();
}
},throwable -> {
Logger.info("Record failed with exception" + throwable);
}).dispose();
I am trying to execute code in background using Completable.fromAction but it is not executing the code if I use subscribeOn(Schedulers.io()).
if I remove subscribeOn(Schedulers.io()), it is executing the code in main thread. I want to executing the code in background thread.
People have already highlighted the problem with your code in the comments - you call dispose() on the Disposable that your Completable returns immediately. This means you cancel your Completable before it has even started. Alter your code to store your Disposable in an instance variable, and call dispose() only when you are no longer interested in receiving it completing. This usually happens in a lifecycle callback like onPause or onStop. For example:
public class SomeActivity extends Activity {
private final CompositeDisposable disposables = new CompositeDisposable();
//...
disposables.add(Completable.fromAction(() -> startRecording())
.subscribeOn(Schedulers.io()) //Note: `updateView` implies UI work. Should you also have `observeOn(AndroidSchedulers.mainThread)?
.subscribe(() -> {
boolean startSuccess = mMediaRecorder.getState() == MediaRecorder.RECORDING_STATE;
if (startSuccess) {
updateView();
startRepeatingTask();
}
}, throwable -> {
Logger.info("Record failed with exception" + throwable);
}));
//Later, in some other lifeycle callback when you no longer care about updates...
disposables.clear();
change the .fromAction to .fromCallable
Callables are designed to perform a single emitter, and then complete. Actual doc explanation here.
The .fromAction is a bit different. Docs here.
I am trying to chain two network calls in my Android app. I am using Retrofit. Basically I want to do :
Make API Call to login
Wait for the response of login, save the token to SharedPrefs
Make another API call right after I've saved the token
Wait for the response, save the data
I think I have chained the stream in the right way, the only thing is I want to update the UI in between. For example once the call starts I want to display a progressDialog ( I do that in doOnSubscribe ), or dismiss the Dialog once the call has completed ( I do that in doOnComplete ). However I get the exception Only the original thread that created a view hierarchy can touch its views. I subscribe on the io thread and observe on the mainThread so that I can make the changes to the UI, however I must be missing something.
I tried adding .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
lower in the stream, but I still get the same error message.
getView().onLoginAction().subscribe(aVoid -> Observable.combineLatest(
getView().userNameObservable().map(CharSequence::toString),
getView().passwordObservable().map(CharSequence::toString),
Pair::new)
.first()
.subscribe(usernamePasswordPair -> {
User user = User.create(usernamePasswordPair.first, usernamePasswordPair.second, "");
RetrofitClientInstance.createService(AuthenticationNetworkApi.class).login(new Login(user.username(), user.password()))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnNext(loginResponse -> {
AuthorizationResponse responseBody = loginResponse.body();
if (responseBody != null && responseBody.getAccessToken() != null && !responseBody.getAccessToken().isEmpty()) {
if (localStorage.getAccessToken().isEmpty()) {
localStorage.saveAccessToken(responseBody.getAccessToken());
}
}
}
).
doOnSubscribe( action -> getView().showProgressDialog())
.doOnError(error -> {
getView().dismissProgressDialog();
getView().showErrorMessage("Login Unsuccessful");
}).doOnComplete(() -> getView().dismissProgressDialog()
)
.flatMap(response -> RetrofitClientInstance.createService(ActivitiesApi.class).getUserActivities())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnNext(activities -> {
for (UserActivityApiModel useractivity : activities
) {
activityService.addActivity(Activity.create(Integer.parseInt(useractivity.getId()), useractivity.getActivityName(), useractivity.getDate(),
Integer.parseInt(useractivity.getValue()), Integer.parseInt(useractivity.getSubCategory().getId())));
}
}).doOnError(error -> getView().showErrorMessage(error.getMessage()))
.doOnComplete(() -> getView().redirectToHomeScreen())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe();
}));
The error occurs here :
.doOnError(error -> {
getView().dismissProgressDialog();
getView().showErrorMessage("Login Unsuccessful");
})
It seems you are using a different thread to execute your backend. In that case, you can't touch the main UI thread from the second one. You need to execute first runOnUiThread { //your code }
In //your code, call the two lines of code that you put on doOnError.
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")
})
}
Retrofit first request with Single blocks UI thread. Below is relevant code, and more text:
RetrofitProvider
object RetrofitProvider {
private val TAG: String = RetrofitProvider::class.java.simpleName
val retrofit: Retrofit by lazy {
val httpClient = OkHttpClient.Builder()
.addInterceptor {
val request = it.request()
if (BuildConfig.DEBUG) {
Log.d(TAG, "${request.method()}: ${request.url()}")
}
it.proceed(request)
}
.build()
Retrofit.Builder()
.client(httpClient)
.baseUrl("http://192.168.0.10:3000")
.addCallAdapterFactory(RxJava2CallAdapterFactory.createWithScheduler(Schedulers.io()))
.addConverterFactory(JacksonConverterFactory.create(jacksonObjectMapper()))
.build()
}
}
ProductApi
interface ProductApi {
#GET("/products")
fun getProducts(): Single<List<Product>>
}
MainViewModel
fun fetchProducts() {
productData.value = Resource.Loading()
productApi.getProducts() // <- This call is a problem (even when I comment out all code below)
.subscribeOn(Schedulers.io())
.subscribe(
{
productData.postValue(Resource.Success(it))
},
{
productData.postValue(Resource.Fail(it.message))
})
.addTo(disposableContainer)
}
MainFragment
...
button.setOnClickListener {
Toast.makeText(requireContext(), "click", Toast.LENGTH_SHORT).show()
mainViewModel.fetchProducts()
}
...
App flow is simple, clicking a button on MainFragment calls MainViewModel's fetchProducts() which uses retrofit to fetch some stuff.
productApi.getProducts() happens on UI thread and blocks it significantly(~half a second), even Toast is delayed, even though it should be shown immediately on button click, before getProducts() call.
productApi.getProducts() by itself, without subscribe doesn't send network request (I checked on server side), it just prepares Single.
Important note, delay DOES NOT happen on subsequent clicks to button. Just the first time, I guess creating Single<> is expensive operation.
So my question is, why is UI thread blocked on first request, and how do I fix it the way it isn't ugly/hacking.
Also Observable acts the same, but Completable works much faster, but I need the data, so can't use Completable.
I think your problem lies with the lazy initialisation of your Retrofit object.
It will be deferred to the last possible moment, so I guess the first time you click on the button, you create the expensive retrofit button (this is done on the main thread).
My suggestion is to remove the lazy initialisation and try running the app once again.
Returning Completable also blocks the UI thread but for less time than returning Single or Observable so it seems like it doesn't have any impact but it does.
Invoking the API call on a background thread will not block your UI as the converter creation will not happen on the UI thread.
Something like this does the trick.
Completable.complete()
.observeOn(Schedulers.io())
.subscribe {
productApi.getProducts()
.subscribe(
{
productData.postValue(Resource.Success(it))
},
{
productData.postValue(Resource.Fail(it.message))
}
)
.addTo(disposableContainer)
}
.addTo(disposableContainer)
Another thing you can do instead of using the converter is to make a wrapper class around the Retrofit API which will call it in a fitting observable on a background thread.
fun getProducts() = Single.create<List<Product>> { emitter ->
try {
val response = productApi.getProducts().execute()
if (!response.isSuccessful) {
throw HttpException(response)
}
emitter.onSuccess(response.body()!!)
} catch (e: Exception) {
emitter.onError(e)
}
}.observeOn(Schedulers.io())
When you invoke a RxJava action, for example, a retrofit request you can to tell it where to perform the action and where to get the result the default location is where you subscribe to it
in order to change it you need to add two lines
observeOn(Where you will receive the result)
subscribeOn(Where the action will be executed)
In your case, it should be something like this
productApi.getProducts() // <- This call is a problem (even when I comment out all code below)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io()) //or .subscribeOn(Schedulers.newThread())
.subscribe({Success},{Failure})
I have made a library that has a lot of utilities/extensions for Android development in kotlin.
One of the packages is there to make it simple to avoid this issue.
All you need to do is type:
yourObservable //or any other reactive type
.runSafeOnMain() //it will perform you action in another thread and it will return the result in main
.subscribe({}, {])
This is my code which does some background work
fun getAllArtists(): LiveData<List<Artist>> {
val artistListLiveData = MutableLiveData<List<Artist>>()
doAsync {
val artistList = MusicGenerator.getAllArtists()
onComplete {
getArtistInfo(artistList)
artistListLiveData.value = artistList
}
}
return artistListLiveData
}
On completion I make a network call to get Artist Info
private fun getArtistInfo(artistList: List<Artist>) {
artistList.forEach {
val url = "http://ws.audioscrobbler.com/2.0/?method=artist.getinfo&api_key=API_KEY&format=json"
.plus("&artist=")
.plus(it.artistName)
val artistInfoList: MutableList<ArtistInfo> = ArrayList()
apiService.getArtistImage(url)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe({ result ->
info { result.toString() }
}, { error ->
error.printStackTrace()
})
verbose { artistInfoList.size }
}
}
However, I'm making sure that the network call is in the background thread and results are on the main thread.
But there is jank in the UI, and the logcat says too much work being done on the main thread. I don't get it, what am I doing wrong here?
I suspect you are creating too many threads. io() is unbounded, and computation is based on the processor cores. Since you are doing io, you should be using io(), but also need to take care to not blast a ton of requests at the same time. You can use a Flowable.flatMap to iterate through your list instead of foreach. The key here is to specify a value for the max concurrency to flatMap. Below, I have set it to 4, but you can play around with the number to see what gives you a good result for max requests inflight without creating jank. Also, since we are using flatMap, I moved your subscribe outside the loop to process the stream of results coming from getArtistImage. It is not clear what you are doing with artistInfoList from your code snippet, so I have left it off, but you can use the following as a guide --
private fun getArtistInfo(artistList: List<Artist>) {
Flowable.fromIterable(artistList).flatMap({
val url = "http://ws.audioscrobbler.com/2.0/?method=artist.getinfo&api_key=API_KEY&format=json"
.plus("&artist=")
.plus(it.artistName)
getArtistImage(url)
.subscribeOn(Schedulers.io())
}, 4)
.subscribe({ result ->
info { result.toString() }
}, { error ->
error.printStackTrace()
})
}