I have a series of sequential Parse network calls that are all dependent on each other to save the final object all wrapped by an Observable so I can deal with everything as one call in my presenter.
If the user decides to change fragments, or leave the app or whatever, this network call is important enough that I'd like it to attempt to complete.
If I call disposables.dispose() the observable will throw an error (which I can catch) on the next .save method in the Observable and the network call does not finish.
If I don't dispose of it, the network call finishes, but it will call my onComplete and throw an error since the view is gone. I can stop that error from happening, but then I'm worried that I've created a memory leak.
I don't care if the oncomplete/onerror get called if the user gets into this situation but I would like to ensure it completes one way or another.
Is there any way to let the call complete, but not cause a memory leak by not disposing it?
fun doParseNetworkCall(){
return Observable.create<Boolean> { emitter ->
createParseObject1
createParseObject1.save()
createParseObject2
createParseObject2.add(key, createParseObject1)
createParseObject2.save()
createParseObject3
createParseObject3.add(key, createParseObject2)
createParseObject3.save()
emitter.onNext(true)
}
fun doNetworkCall(){
repo.doParseNetworkCall()
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io()).subscribeBy(
onError = { error ->
//do something
},
onComplete = {
//do something
}
).addTo(disposable)
}
I was able to resolve my issue doing the following. I think it's memory safe since the 2nd set of observables don't subscribe until the first observable completes, and if the Composite Disposable in the presenter has been disposed of already, the 2nd set will not subscribe.
repo.saveSomething()
.map {
//Do some non view stuff
}
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io()).subscribeBy(
onError = { error ->
onErrorMethod()
},
onComplete = {
onSuccessMethod()
}
)
fun onSuccessMethod() {
Observable.just(true)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io()).subscribeBy(
onComplete = {
//Do view stuff
}).addTo(disposable)
}
fun onErrorMethod() {
Observable.just(true)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io()).subscribeBy(
onComplete = {
//Do view stuff
}).addTo(disposable)
}
Related
I try to implement deleting user in FirebaseAuth using Kotlin flow (SharedFlow).
In onDeleteAccountClicked() there is delete() method called from FirebaseAuth which may throw AuthReauthenticationRequiredException. When the exception is thrown, app redirects to another fragment to reauthenticate, then call onDeleteAccountClicked() once again, but flow emits nothing.
ViewModel
private val _deleteAccount = MutableSharedFlow<() -> Unit>()
fun onDeleteAccountClicked() {
logd("outside the viewModelScope")
viewModelScope.launch {
logd("inside the viewModelScope")
_deleteAccount.emit {
logd("emitting log")
firebaseAuth.deleteUser()
//throw AuthReauthenticationRequiredException()
}
}
}
init {
viewModelScope.launch {
_deleteAccount
.onEach {
it()
}
.catch {
if (it is AuthReauthenticationRequiredException) {
_redirectToSignInMethodsScreen.emit(Unit)
}
}
.collect()
}
}
Logs "outside the viewModelScope" and "inside the viewModelScope" shows every time when the method is called, but "emitting log" only for the first time.
Am I even trying to do it the right way?
I just tested the code, and it works for me. I called onDeleteAccountClicked() three times with delay between calling, and all three logs "emitting log" inside emit lambda were printed. Try to remove calling firebaseAuth.deleteUser() inside emit lambda and test. Calling FirebaseUser.delete function when user is already deleted throws FirebaseAuthInvalidUserException exception. Maybe that's why you didn't see logs - because FirebaseUser.delete function throws an exception.
I think the structure you use for calling just one function is a bit complicated, I can suggest to get rid of _deleteAccount flow and just wrap firebaseAuth.deleteUser() inside try-catch (you even don't need to launch a coroutine for that):
fun onDeleteAccountClicked() {
try {
firebaseAuth.deleteUser()
} catch(e: AuthReauthenticationRequiredException) {
_redirectToSignInMethodsScreen.emit(Unit)
}
}
I have the following code:
Single.create { emitter ->
// I/O thread here
ThirdPartySDK.doSomeAction {
// Main thread here
emitter.onSuccess(someValue)
}
}
.flatMap {
someOtherSingle(it) // Executes on main thread
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({},{})
The ThirdPartySDK.doSomeAction callback posts on main thread, so the emitter will emit on the main thread too, not on the subscribe thread (and if I have some network interactions further in the flatMap, chain will fail).
If I add observeOn(Schedulers.io()) after the first Single, it switches to the correct thread, but is there any way to emit on right thread? I can't modify ThirdPartySDK behaviour.
subscribeOn
The subscribeActual lambda will be invoked on given scheduler
observeOn
Switch thread to given scheduler. Every upstream-onNext call will be called from an ObserveOn-Scheduler-Thread
As you already said, subscribeOn will only invoke the subscribeActual method call on subscribe on given Scheduler-Thread. This does not mean, that the downstream emit will be on the same thread. In your case the onSuccess emit will be called from a different thread (e.g. Database/ Http-ThreadPool etc.).
onSuccess will be called from a unknown thread (in your case main thread). The downstream call will be called from the main-thread. Therefore flatMap is called from the main-thread. Network-calls on the main-thread in the flatMap will probably fail, because it is not allowed to "block" the main-thread.
How to solve this issue?
Just place a observeOn after the Single#create. The main-thread calls onSucess. The observeOn-subscriber will get called from the main-thread. The observeOn-subscriber re-directs onSuccess downstream-call (e.g. flatMap) to given ObserveOn-Scheduler-Thread. Therefore it is given, that flatMap is called from a non main-loop thread.
Example:
#Test
fun wurst() {
val thirdPartySDKImpl = ThirdPartySDKImpl()
Single.create<String> { emitter ->
thirdPartySDKImpl.doSomeAction {
emitter.onSuccess(it)
}
}
// .subscribeOn(Schedulers.computation())
// move emit from unknown thread to computation thread
.observeOn(Schedulers.computation())
// Single.just will be subscribe from a computation thread
.flatMap { Single.just(123) }
// move onSucess/ onError emit from computation thread to main-thread
.observeOn(AndroidSchedulers.mainThread())
// subscribe onNext / onError will be called from the main-android-thread
.subscribe({}, {})
}
interface ThirdPartySDK {
fun doSomeAction(callback: (v: String) -> Unit)
}
class ThirdPartySDKImpl : ThirdPartySDK {
override fun doSomeAction(callback: (v: String) -> Unit) {
// <- impl-detail ->
callback("whatever")
}
}
NOTE: You do not need a subscribeOn, if the create-lambda does not block or does some cpu heavy stuff. If it only subscribes to a callback, which will be called from a different thread, you do not need subscribeOn.
but is there any way to emit on right thread?
You should not use any concurrency in operators. You would think, you could just do something like:
Single.create<String> { emitter ->
thirdPartySDKImpl.doSomeAction {
Schedulers.io().scheduleDirect {
emitter.onSuccess(it)
}
}
}
But this is not recommended, because you could break the serialized onNext contract^1. This example would make sure, that the onSucess downstream call would happen on expected thread, but cancellation/ unsubscription is not handled and there might be other pitfalls.
If you have a non reactive API and you want to enforce some threading-model I would suggest to wrap the sync. API with an async one and provide proper observeOn/ subscribeOn operators. Later on only use the async API.
interface ThirdPartySDKAsync {
fun doSomeAction(): Single<String>
}
class ThirdPartySDKAsyncImpl(private val sdk: ThirdPartySDK, private val scheduler: Scheduler) :
ThirdPartySDKAsync {
override fun doSomeAction(): Single<String> {
return Single.create<String> { emitter ->
sdk.doSomeAction {
emitter.onSuccess(it)
}
}.observeOn(scheduler)
}
}
Further reading: https://tomstechnicalblog.blogspot.com/2016/02/rxjava-understanding-observeon-and.html
^1 Only one thread a time is allowed to call onNext/onSuccess/onError/onComplete
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.
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({}, {])
I've upgraded to Android Studio 3.1 today, which seems to have added a few more lint checks. One of these lint checks is for one-shot RxJava2 subscribe() calls that are not stored in a variable. For example, getting a list of all players from my Room database:
Single.just(db)
.subscribeOn(Schedulers.io())
.subscribe(db -> db.playerDao().getAll());
Results in a big yellow block and this tooltip:
The result of subscribe is not used
What is the best practice for one-shot Rx calls like this? Should I keep hold of the Disposable and dispose() on complete? Or should I just #SuppressLint and move on?
This only seems to affect RxJava2 (io.reactivex), RxJava (rx) does not have this lint.
The IDE does not know what potential effects your subscription can have when it's not disposed, so it treats it as potentially unsafe. For example, your Single may contain a network call, which could cause a memory leak if your Activity is abandoned during its execution.
A convenient way to manage a large amount of Disposables is to use a CompositeDisposable; just create a new CompositeDisposable instance variable in your enclosing class, then add all your Disposables to the CompositeDisposable (with RxKotlin you can just append addTo(compositeDisposable) to all of your Disposables). Finally, when you're done with your instance, call compositeDisposable.dispose().
This will get rid of the lint warnings, and ensure your Disposables are managed properly.
In this case, the code would look like:
CompositeDisposable compositeDisposable = new CompositeDisposable();
Disposable disposable = Single.just(db)
.subscribeOn(Schedulers.io())
.subscribe(db -> db.get(1)));
compositeDisposable.add(disposable); //IDE is satisfied that the Disposable is being managed.
disposable.addTo(compositeDisposable); //Alternatively, use this RxKotlin extension function.
compositeDisposable.dispose(); //Placed wherever we'd like to dispose our Disposables (i.e. in onDestroy()).
The moment the Activity will be destroyed, the list of Disposables gets cleared and we’re good.
io.reactivex.disposables.CompositeDisposable mDisposable;
mDisposable = new CompositeDisposable();
mDisposable.add(
Single.just(db)
.subscribeOn(Schedulers.io())
.subscribe(db -> db.get(1)));
mDisposable.dispose(); // dispose wherever is required
You can subscribe with DisposableSingleObserver:
Single.just(db)
.subscribeOn(Schedulers.io())
.subscribe(new DisposableSingleObserver<Object>() {
#Override
public void onSuccess(Object obj) {
// work with the resulting todos...
dispose();
}
#Override
public void onError(Throwable e) {
// handle the error case...
dispose();
}});
In case you need to directly dispose Single object (e.g. before it emits) you can implement method onSubscribe(Disposable d) to get and use the Disposable reference.
You can also realize SingleObserver interface by your own or use other child classes.
As was suggested you may use some global CompositeDisposable to add the result of the subscribe operation there.
The RxJava2Extensions library contains useful methods to automatically remove created disposable from the CompositeDisposable when it completes. See subscribeAutoDispose section.
In your case it may look like this
SingleConsumers.subscribeAutoDispose(
Single.just(db)
.subscribeOn(Schedulers.io()),
composite,
db -> db.playerDao().getAll())
You can use Uber AutoDispose and rxjava .as
Single.just(db)
.subscribeOn(Schedulers.io())
.as(AutoDispose.autoDisposable(AndroidLifecycleScopeProvider.from(this)))
.subscribe(db -> db.playerDao().getAll());
Make sure that you understand when you unsubscribe based on the ScopeProvider.
Again and again I find myself coming back to the question of how to correctly dispose of subscriptions, and to this posting in particular. Several blogs and talks claim that failing to call dispose necessarily leads to a memory leak, which I think is a too general statement. In my understanding, the lint warning about not storing the result of subscribe is a non-issue in some cases, because:
Not all observables run in the context of an Android activity
The observable can be synchronous
Dispose is called implicitly, provided the observable completes
Since I don't want to suppress lint warnings I recently started to use the following pattern for cases with a synchronous observable:
var disposable: Disposable? = null
disposable = Observable
.just(/* Whatever */)
.anyOperator()
.anyOtherOperator()
.subscribe(
{ /* onSuccess */ },
{ /* onError */ },
{
// onComplete
// Make lint happy. It's already disposed because the stream completed.
disposable?.dispose()
}
)
I'd be interested in any comments on this, regardless of whether it's a confirmation of correctness or the discovery of a loophole.
There's another way available, which is avoiding to use Disposables manually (add and remove subscriptions).
You can define an Observable and that observable is going to receive the content from a SubjectBehaviour (in case you use RxJava). And by passing that observable to your LiveData, that should work. Check out the next example based on the initial question:
private val playerSubject: Subject<Player> = BehaviorSubject.create()
private fun getPlayer(idPlayer: String) {
playerSubject.onNext(idPlayer)
}
private val playerSuccessful: Observable<DataResult<Player>> = playerSubject
.flatMap { playerId ->
playerRepository.getPlayer(playerId).toObservable()
}
.share()
val playerFound: LiveData<Player>
get() = playerSuccessful
.filterAndMapDataSuccess()
.toLiveData()
val playerNotFound: LiveData<Unit>
get() = playerSuccessful.filterAndMapDataFailure()
.map { Unit }
.toLiveData()
// These are a couple of helpful extensions
fun <T> Observable<DataResult<T>>.filterAndMapDataSuccess(): Observable<T> =
filter { it is DataResult.Success }.map { (it as DataResult.Success).data }
fun <T> Observable<DataResult<T>>.filterAndMapDataFailure(): Observable<DataResult.Failure<T>> =
filter { it is DataResult.Failure }.map { it as DataResult.Failure<T> }
If you are sure that disposable handled correctly, for example using doOnSubscribe() operator, you may add this to Gradle:
android {
lintOptions {
disable 'CheckResult'
}}