Problems with unsubscribe - android

I'm doing an app that works as a remote control to a ventilator using RxAndroidBle. I have a problem with the unsubscribe because when I use
.flatMap(rxBleConnection -> rxBleConnection.writeCharacteristic(Uuids.UUID_RX, flaktCommandConcat.getBytes()))
and after that I use subscription.unsubscribe(); the writeCharacteristics doesn´t work because the unsubscribe runs always first and the connection disconect before the data was sent.
What I need is:
When I click the button I want to connect to the ventilator
Then send all values
And then disconnect.
If I repeat the procedure, it will need to do the same thing over and over again.
Can some one help me with some idea? I tried to use .delay(1000, Time.MILISECONDS) and it worked but it took a long time to send the information to the ventilator.
This is my code:
public void writeRxCharacteristics(String flaktCommandConcat){
rxBleDevice = rxBleClient.getBleDevice(Uuids.DEVICE_ADDRESS);
subscription = rxBleDevice.establishConnection(true) //false
.observeOn(AndroidSchedulers.mainThread())
.flatMap(rxBleConnection -> rxBleConnection.createNewLongWriteBuilder()
.setCharacteristicUuid(Uuids.UUID_RX)
.setBytes(flaktCommandConcat.getBytes())
.build())
.subscribe(
byteArray -> {
Log.d("CharacteristicValue","WRITE: " + Arrays.toString(byteArray));
},
throwable -> {
Log.d("CharacteristicValue","Throwable: " + throwable.toString());
rxBleActivity.onScanFailure(throwable, getContext());
}
);
rxBleDevice.observeConnectionStateChanges()
.observeOn(AndroidSchedulers.mainThread())
.delay(1000, TimeUnit.MILLISECONDS)
.subscribe(
rxBleConnectionState -> {
Log.d("RxBleConnectionState", " CON_STATUS: " + rxBleConnectionState);
disconnect();
},
throwable -> {
Log.d("ConnectionStateChanges","Throwable: " + throwable.toString());
}
);
}
public void disconnect() {
if (subscription != null && !subscription.isUnsubscribed()) {
subscription.unsubscribe();
subscription = null;
}
Log.d("CONNECTION2", " CON_STATUS: " + rxBleDevice.getConnectionState().toString());
}

it looks that you don't need a long write here. Is your data longer than 20 bytes?
Anyway, the library releases the connection when the Observable<RxBleConnection> is unsubscribed. What I'd do if I were you is to:
public void writeRxCharacteristics(String flaktCommandConcat){
rxBleDevice = rxBleClient.getBleDevice(Uuids.DEVICE_ADDRESS);
rxBleDevice.establishConnection(true) //false
.flatMap(rxBleConnection -> rxBleConnection.createNewLongWriteBuilder()
.setCharacteristicUuid(Uuids.UUID_RX)
.setBytes(flaktCommandConcat.getBytes())
.build()
)
.take(1)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
byteArray -> {
Log.d("CharacteristicValue","WRITE: " + Arrays.toString(byteArray));
},
throwable -> {
Log.d("CharacteristicValue","Throwable: " + throwable.toString());
rxBleActivity.onScanFailure(throwable, getContext());
}
);
Please make sure you're not overusing the long write. It has a known bug (unrelated) in 1.2.0 which was recently fixed in 1.3.0-SNAPSHOT.

Related

RxJava - .andThen() never called after .flatMapCompletable()

I have a Observable with a chained Completeable with a chained Single that is called at an interval of 5 seconds.
public void getCoinPrices() {
disposable = Observable
.interval(5, TimeUnit.SECONDS)
.flatMapCompletable(n -> {
Timber.d("Called flatmap completeable: " + n);
boolean isFirstTime = sharedPrefManager.isFirstTimeOpeningApp();
if (isFirstTime) {
Timber.d("Is first time.");
return insertFavoritesUseCase.insertStartingCoins()
.andThen(Completable.fromAction(() -> {
sharedPrefManager.setFirstTimeOpeningApp(false);
}));
} else {
Timber.d("Not first time.");
return Completable.complete();
}
})
.andThen(getFavoritesUseCase.getFavoriteCoins())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.flatMap(listFavorites -> {
Timber.d("List favorites called...");
return getCoinsUseCase.getCoinPrices(listFavorites);
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(listCoinsWithPrice -> {
Timber.d("Called.......");
}, err -> {
Timber.e("Failed to get price of coins: " + err);
});
}
I can't figure out why the .andThen(getFavoritesUseCase.getFavoriteCoins()) is never fired.
public Single<List<CoinStatus>> getFavoriteCoins() {
Timber.d("Get Favorite Coins Fired");
return localRepository.getFavoriteCoins();
}
I have tested the flag isFirstTime with true and false, but the .andThen() is still never fired.
What am I doing incorrectly here?
I got it the interval to work by using akarnokd's advice removing the Completeable. I used a Single instead and continued the chain flow by passing a value with Single.just(). Please let me know if there's a better way or cleaner way to do it!
disposable = Observable.interval(0, 15, TimeUnit.SECONDS)
.flatMapSingle(n -> {
boolean isFirstTime = sharedPrefManager.isFirstTimeOpeningApp();
if (isFirstTime) {
return insertFavoritesUseCase.insertStartingCoins().doOnSuccess(insertResult -> {
sharedPrefManager.setFirstTimeOpeningApp(false);
});
} else {
return Single.just("Did not insert coins, continuing stream");
}
})
.flatMapSingle(insertResult -> getFavoritesUseCase.getFavoriteCoins())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.flatMapSingle(listFavorites -> {
//update ui
return getCoinsUseCase.getCoinPrices(listFavorites);
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(listCoinsWithPrice -> {
//update ui
}, err -> {
Timber.e("Failed to get price of coins: " + err);
});

How can I propagate throwable to the Completable's next chain?

I am developing an Android app using RxJava.
I have some API call chains.
verify
consume
val verify = Completable.error(Exception("TEST"))
.doOnSubscribe { Log.d(TAG, "1. verify") }
.doOnComplete{ Log.d(TAG, "1. verify - success") }
.doOnError { Log.e(TAG, "1. verify - failed: ${it.message}") }
.retryWhen { attempts ->
attempts.zipWith(
Flowable.range(1, 3), BiFunction<Throwable, Int, Long> { t, i ->
if (i <= 3) {
1L
} else {
throw t
}
}
).flatMap {
Flowable.timer(it, TimeUnit.SECONDS)
}
}
// 2. consume
val consume = Single.just("SUCCESS")
.doOnSubscribe { Log.d(TAG, "2. consume") }
.doOnSuccess { Log.d(TAG, "2. consume - success") }
.doOnError { Log.e(TAG, "2. consume - failed: ${it.message}", it) }
disposable.add(
verify.andThen (consume)
.subscribeOn(ioScheduler)
.observeOn(uiScheduler)
.subscribe({
Log.d(TAG, "done")
}, { t ->
Log.e(TAG, "failed: ${t.message}", t)
})
);
What I excepted is...
"verify" should be called 3 times every 1 seconds.
After 3 retries failed, it should be done with Error.
But in my case, "consume" was run too.
Why?
I want to skip "consume" if "verify" is failed!
How can I do it?
It's because your code is not failing.
With Flowable.range(1, 3) you create a range from 1 to 3 so the else part of your code is never reached.
Try with Flowable.range(1, 4) and you will see the correct behaviour.

How can I retrieve an integer from a SingleFlatMap in RxJava?

I'm trying to retrieve a value from a Bluetooth Device.
if (rxBleDevice.connectionState != RxBleConnection.RxBleConnectionState.CONNECTED) {
rxBleDevice!!.establishConnection(false) ? .subscribe({
rxBleConnection ->
Log.d("Device: ", "Connection Established")
val stringDeviceUUID = rxBleDevice.bluetoothDevice.uuids[0].toString()
val charUUID = UUID.nameUUIDFromBytes(stringDeviceUUID.toByteArray())
val count = rxBleConnection.readCharacteristic(charUUID)
println("OUTPUT: ${count}")
}, {
throwable -> Log.d("Device: ", "$throwable")
})
}
I'm using the following dependencies, mainly RxJava and a reactive Bluetooth library called RxAndroidBLE:
implementation 'io.reactivex.rxjava2:rxkotlin:2.1.0'
implementation "com.polidea.rxandroidble2:rxandroidble:1.8.1"
implementation "io.reactivex.rxjava2:rxjava:2.2.7"
My output:
I/System.out: OUTPUT: io.reactivex.internal.operators.single.SingleFlatMap#bf9162d
I have no how to process this object. I believe I should be receiving a simple ByteArray from the Bluetooth device.
An example for the value I should see is datc00099, indicating a count of 99.
You are supposed to subscribe to Single. Following the examples provided by RxAndroidBle, something like this might work in your case:
if (rxBleDevice.connectionState != RxBleConnection.RxBleConnectionState.CONNECTED) {
// Have your charUUID ready. Might need extra null checks for rxBleDevice
val charUUID = rxBleDevice.bluetoothDevice.uuids[0].uuid
rxBleDevice!!.establishConnection(false) ?
.doOnNext {
_ -> Log.d("Device: ", "Connection Established")
} ?
.flatMapSingle {
rxBleConnection -> rxBleConnection.readCharacteristic(charUUID)
} ? .subscribe({
count ->
// count should be in bytes
println("OUTPUT: $count")
}, {
throwable ->
Log.d("ERROR: ", "$throwable")
})
}

How to combine following Observables into one in Kotlin?

I have these two Observables in Kotlin where is just act as a timer and another one is HTTP network call response Observer.
timerDisposable = Observable.timer(daleyABCControlResetSeconds, TimeUnit.SECONDS, AndroidSchedulers.mainThread())
.doOnNext {
if (getABCUpdate() != null) {
Log.d("ABC", "Media status reset after 3 seconds: ")
updateABCResponse(getABCUpdate())
}
}.subscribe()
disposable = audioApi.setABCUpdate(abcUpdate)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
timerDisposable.dispose()
updateABCResponse(it)
Log.d("ABC", "Media Status updated:")
}, {
Log.d("ABC", "Error updating Media Status: " + it.message)
isABCControlChangeRequested = false
})
I am not satisfied with this approach, can anyone please direct me right direction to use the rx's full potential. Thanks in advance.
EDIT
Observable.combineLatest(Observable.timer(daleyABCControlResetSeconds, TimeUnit.SECONDS, AndroidSchedulers.mainThread())
.doOnNext {
if (getABCUpdate() != null) {
Log.d("ABC", "Media status reset after 3 seconds: ")
updateABCResponse(getABCUpdate())
}
},
audioApi.setABCUpdate(abcUpdate)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()),
BiFunction<Long, ABCStatusUpdate, ABCStatusUpdate> { _, abcStatusUpdate ->
abcStatusUpdate
})
.subscribe({
timerDisposable.dispose()
updateABCResponse(abcStatusUpdate)
Log.d("ABC", "Media Status updated:")
}, {
Log.d("ABC", "Error updating Media Status: " + abcStatusUpdate.vol)
isABCControlChangeRequested = false
})
You can use combinelatest, zip or merge for combinig. I think in your case combinelatest is suitable
Observable.combineLatest(
Observable.timer(daleyABCControlResetSeconds, TimeUnit.SECONDS, AndroidSchedulers.mainThread())
.doOnNext {
if (getABCUpdate() != null) {
Log.d("ABC", "Media status reset after 3 seconds: ")
updateABCResponse(getABCUpdate())
}
},
audioApi.setABCUpdate(abcUpdate)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()),
BiFunction<Long, YourApiResponseType, YourApiResponseType> { _, response ->
response})
.subscribe({
timerDisposable.dispose()
updateABCResponse(it)
Log.d("ABC", "Media Status updated:")
}, {
Log.d("ABC", "Error updating Media Status: " + it.message)
isABCControlChangeRequested = false
})
UPD:
You can change your code like this:
Observable.timer(5, TimeUnit.SECONDS, AndroidSchedulers.mainThread()).startWith(-1L)
.doOnNext {
if (it == -1L) return#doOnNext
//your condition
}

Run DB Update method after Observable result RxJava

Hello
The App: I have a very basic app which have some Machines (name, id, total_income) and some Incomes (id, money, note, machines_id). On the first screen I allow the users to add a machine from a FAB which then I display it in a RecyclerView. When the user clicks any machine I navigate them to the second screen, where the user can see the name of the machine, total income and a RecyclerView with its corresponding Income; There's a FAB which enables them to add the income for that machine and refreshes the RecyclerView.
The problem: I been failing to translate from the conventional world to RxJava.
I have managed to make it work using Room .allowMainThreadQueries() and conventional methods.
MachineViewModel
public long updateByID(long id, double total_income){
return machinesDB.getMachineDAO().updateMachineByID(id, total_income);
}
MachineDAO
#Query("update machines set total_income = :total_income where id = :id")
int updateMachineByID(long id, double total_income);
MachineInfo Activity
disposable.add(incomeViewModel.getIncomeOfMachine(id)
.defaultIfEmpty(0.0)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(total_amount -> {
if (total_amount != null) {
// This works correctly using .allowMainThreadQueries() and conventional methods
machineViewModel.updateByID(id, total_amount);
DecimalFormat formatter = new DecimalFormat("$#,##0.000");
String formatted = formatter.format(total_amount);
mMoney.setText(formatted);
Toast.makeText(this, "MachineInfo: " + String.valueOf(machineViewModel.updateByID(id, total_amount)), Toast.LENGTH_SHORT).show();
showMenu = true;
} else {
mMoney.setText("$0.0");
}
}, throwable -> Log.e(TAG, "MachineInfo: ERROR", throwable )));
I know Room needs to be instantiated in a background thread and thats why I am using RxJava. But when I try to translate the above methods into RxJava World like the below methods I'm failing to update but not crashing, "Return Value of the method is never used".
MachineViewModel
public Completable updateByID(long id, double total_income){
return Completable.fromAction(() -> machinesDB.getMachineDAO().updateMachineByID(id, total_income));
}
MachineDAO
#Query("update machines set total_income = :total_income where id = :id")
int updateMachineByID(long id, double total_income);
Try # 1: Failure
private PublishSubject<Double> publishSubject = PublishSubject.create();
private Observable<Double> clickEvent = publishSubject;
/*
/ other stuff in here
*/
disposable.add(incomeViewModel.getIncomeOfMachine(id)
.defaultIfEmpty(0.0)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(total_amount -> {
if (total_amount != null) {
publishSubject.onNext(total_amount);
DecimalFormat formatter = new DecimalFormat("$#,##0.000");
String formatted = formatter.format(total_amount);
mMoney.setText(formatted);
showMenu = true;
} else {
mMoney.setText("$0.0");
}
}, throwable -> Log.d(TAG, "MachineInfo: ERROR")));
disposable.add(clickEvent.subscribe(
total_amount -> machineViewModel.updateByID(id, total_amount)));
Try # 2: Failure
MachineViewModel
public Completable updateByID(long id, double total_income){
return Completable.fromCallable(() -> machinesDB.getMachineDAO().updateMachineByID(id, total_income));
}
MachineDAO
#Query("update machines set total_income = :total_income where id = :id")
int updateMachineByID(long id, double total_income);
MachineInfo Activity
disposable.add(incomeViewModel.getIncomeOfMachine(id)
.defaultIfEmpty(0.0)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(total_amount -> {
if (total_amount != null) {
// Completable.fromCallable()
machineViewModel.updateByID(id, total_amount);
DecimalFormat formatter = new DecimalFormat("$#,##0.000");
String formatted = formatter.format(total_amount);
mMoney.setText(formatted);
Toast.makeText(this, "MachineInfo: " + String.valueOf(machineViewModel.updateByID(id, total_amount)), Toast.LENGTH_SHORT).show();
showMenu = true;
} else {
mMoney.setText("$0.0");
}
}, throwable -> Log.e(TAG, "MachineInfo: ERROR", throwable )));
What do you mean by failing? Is the database not getting updated or are you getting some exceptions?
Anyway, the main problem I see is that you are not subscribing to your Completable objects - without this, they won't be executed.
So change:
machineViewModel.updateByID(id, total_amount);
to for example:
machineViewModel.updateByID(id, total_amount).subscribeOn(Schedulers.io()).observeOn(Schedulers.io()).subscribe();
and it will work (of course, you might wanna add specific Subscriber to subscribe method).
Okay, so thanks to Michael I managed to find the hole in the problem.
So apparently you can Add Disposables inside Disposables.
You need to initialize the Observable and add its .observeOn(etc).subscribeOn(etc).subscribe(etc)"
Add the disposable inside the disposable like this:
disposable.add(incomeViewModel.getIncomeOfMachine(id)
.defaultIfEmpty(0.0)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(total_amount -> {
if (total_amount != null) {
disposable.add(machineViewModel.updateByID(id, total_amount)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
() -> Log.d(TAG, "MachineInfo: COMPLETED"),
throwable -> Log.e(TAG, "MachineInfo: ERROR", throwable )));
DecimalFormat formatter = new DecimalFormat("$#,##0.000");
String formatted = formatter.format(total_amount);
mMoney.setText(formatted);
showMenu = true;
} else {
mMoney.setText("$0.0");
}
}, throwable -> Log.d(TAG, "MachineInfo 2: ERROR")));
Im note sure if is a clean answer but it works.

Categories

Resources