Run DB Update method after Observable result RxJava - android

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.

Related

Need help solving Firebase query in Android Studio

I'm trying to see if this "employee" has a service where the serviceComplete field is false (in other words trying to see if this employee has an active service that is not complete) if a Toast message pops up letting the employee know he cannot accept more services has he has an active one. If not the employee should be able to accept the service.
My problem is no matter what I do this firebase query seems to think there are documents in my DB that do not exist. Every time I go to accept the service it displays the toast. Meaning there is a collection "services" where a document has the field "serviceCompleted" which is equal to "false" but in my DB there is no collection "services" under employees
My database showing no collection "services" exist underneath "employees"
and here is my Kotlin code
fun setButton(serviceID: String, eID: String){
val btnAcceptService = view.findViewById<Button>(R.id.btnAcceptService)
btnAcceptService.setOnClickListener {
val queryEmpServices = database.collection("employees").document(eID).collection("services").whereEqualTo("serviceComplete", false)
queryEmpServices.get().addOnSuccessListener { documents ->
if (documents != null){
Toast.makeText(applicationContext,"You already have a service active!", Toast.LENGTH_SHORT).show()
}else {
database.collection("services").document(serviceID).update("saccept", true).addOnSuccessListener {
database.collection("services").document(serviceID).get().addOnSuccessListener { document ->
if (document != null) {
val Location = document.get("ulocation").toString()
val serviceType = document.get("serviceType").toString()
val uComment = document.get("ucomment").toString()
val uID = document.get("uid").toString()
if (document.getBoolean("saccept") == true) {
database.collection("users").document(document.get("uid").toString()).collection("services").document(serviceID).update("saccept", true).addOnSuccessListener {
database.collection("employees").document(mAuth.currentUser!!.uid).get().addOnSuccessListener { document ->
if (document != null) {
val calendar = Calendar.getInstance()
val simpleDateFormat = SimpleDateFormat("dd-MM-yyyy HH:mm:ss")
val acceptDate = simpleDateFormat.format(calendar.time)
val eFullName = document.get("ename").toString() + " " + document.get("esurname").toString()
val eCompany = document.get("ecompany").toString()
database.collection("users").document(uID).get().addOnSuccessListener { document ->
val uName = document.get("name").toString()
val uPhonenumber = document.get("phonenumber").toString()
val serviceAccept = EmployeeServiceAccept(acceptDate, serviceID, Location, serviceType, uComment, uName, uPhonenumber, false)
database.collection("employees").document(mAuth.currentUser!!.uid).collection("acceptedservices").document(serviceID).set(serviceAccept)
database.collection("services").document(serviceID).update("acceptedby", eFullName + ", " + eCompany)
database.collection("users").document(uID).collection("services").document(serviceID).update("acceptedby", eFullName + ", " + eCompany)
Toast.makeText(applicationContext, "Service Accepted", Toast.LENGTH_SHORT).show()
}
}
}
}
}
} else {
Toast.makeText(applicationContext, "Failed to accept service", Toast.LENGTH_SHORT).show()
}
When you are using the Task.addOnSuccessListener(OnSuccessListener) method on a Query object, the result that you get can be a success or a failure, never both and never null. It will always be one, or the other.
That being said, you should never check the documents object against nullity because it can never be null. What you should do instead, is to check the "documents" object, which is of type QuerySnapshot to see if isEmpty() or not:
if (!documents.isEmpty){
Toast.makeText(applicationContext,"You already have a service active!", Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(applicationContext,"You don't have a service active!", Toast.LENGTH_SHORT).show()
}
Where indeed the Toast message from the "else" part of the statement will be displayed, as there are no documents present in the QuerySnapshot object:
"You don't have a service active!"

RxJava and Realm request make app skip frame

I'm using RxJava and Realm database in an android project. But sometimes pressing a button is unresponsive and you have to do that many times for it to work sometime, and android log is saying xxx frame skipped. I know it has something to do with misusing UI thread. Here's some of my request, can someone tell me what's wrong with them? Realm wants me to perform IO request on the same thread I'm using the response(not too sure though).
public Flowable<List<ClothingItem>> getClothingItemsLocal() {
return Flowable.just(dbProvider.getClothingItems(mSortType));
}
public Flowable<List<ClothingItem>> getClothingItemsRemote() {
return clothingService.getAll("Bearer " + preferencesManager.getToken())
.map(response -> response.items)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnSuccess(clothingItems -> {
dbProvider.clearClothingItems();
dbProvider.saveOrUpdateClothingItems(clothingItems);
})
.toFlowable()
.map(remoteItems -> dbProvider.getClothingItems(mSortType));
}
public Flowable<ClothingItem> getClothingItem(#NonNull final String id) {
return getClothingItemRemote(id)
.startWith(dbProvider.getClothingItem(id))
.onErrorReturn(throwable -> dbProvider.getClothingItem(id));
}
getAll method with retrofit.
#GET(BuildConfig.BASE_API_PATH + "clothing_items")
Single<GetClothingItemsResponseModel> getAll(#Header("Authorization") String token);
Realm provider methods:
public void saveOrUpdateEvents(List<Event> data) {
realmInstance.executeTransaction(realm -> {
for (Event event : data) {
if (!TextUtils.isEmpty(event.date)) {
Date date = DateUtils.getFullDate(event.date);
Timber.d("date %s", date.toString());
event.timestamp = date;
}
Event cashedEvent = getEvent(event.id);
if (cashedEvent.id != null) {
event.eventClothingItems = cashedEvent.eventClothingItems;
event.tags = cashedEvent.tags;
event.location = cashedEvent.location;
}
}
realm.delete(Event.class);
realm.insertOrUpdate(data);
});
}
public void clearClothingItems() {
realmInstance.executeTransaction(realm -> {
realm.delete(ClothingItem.class);
});
}
Try this:
public Flowable<List<ClothingItem>> getClothingItemsRemote() {
return clothingService.getAll("Bearer " + preferencesManager.getToken())
.subscribeOn(Schedulers.io())
.map(response -> response.items)
.observeOn(AndroidSchedulers.mainThread())
.doOnSuccess(clothingItems -> {
dbProvider.clearClothingItems();
dbProvider.saveOrUpdateClothingItems(clothingItems);
})
.observeOn(Schedulers.computation())
.toFlowable()
.map(remoteItems -> dbProvider.getClothingItems(mSortType));
}

How to iterate over a list, and when finish launch a method with RXJava

I have a list of data models, so I have to apply a method that returns a view.
When everything is calculated, I have to launch a method, which makes another type of calculation.
The problem is that as I have it, at each iteration of the second method is launched.(for sure I'm missing something or doing bad, but my knowledge of RX is quite low)
Is it possible to make all the calculations for each method, and when finished, launch this method only once?
val markersViewList = hashMapOf<String, View>()
val subscription = Observable.fromIterable(retrivedUserInfoList)
.subscribeOn(Schedulers.computation())
.observeOn(AndroidSchedulers.mainThread())
.map { userInfo ->
val markerLayout = setupUpForMarkerLayout(userInfo)
if (markerLayout != null) {
if (userInfo.userId == owner.uid) { //is owner
markerViewList[OWNER] = markerLayout
} else {
if (!markerViewList.containsKey(userInfo.data1)) {
markerViewList[userInfo.data1] = markerLayout
}
}
}
}
.subscribe {
//THIS IS THE METHOD THAT ONLY HAS TO BE CALCULATED ONCE
createImages(retrivedUserInfoList,markerViewList)
}
addSubscription(subscription)
You can use ignoreElements() operator for it:
val markersViewList = hashMapOf<String, View>()
val subscription = Observable.fromIterable(retrivedUserInfoList)
.subscribeOn(Schedulers.computation())
.observeOn(AndroidSchedulers.mainThread())
.map { userInfo ->
val markerLayout = setupUpForMarkerLayout(userInfo)
if (markerLayout != null) {
if (userInfo.userId == owner.uid) { //is owner
markerViewList[OWNER] = markerLayout
} else {
if (!markerViewList.containsKey(userInfo.data1)) {
markerViewList[userInfo.data1] = markerLayout
}
}
}
}
.ignoreElements()
.subscribe {
//THIS IS THE METHOD THAT ONLY HAS TO BE CALCULATED ONCE
createImages(retrivedUserInfoList, markerViewList)
}
addSubscription(subscription)
It will turn your Observable to Completable so your subscribe block will be invoked only once on complete.

RXKotlin Break Inside doOnNext and Call Another Function

i am using rx kotlin newly and didn't understand all of it yet. I am trying to loop over a list of queries, and execute them one by one. in this list i have a special string that once reached, i want to break the loop and perform another function
how can i do this in the below example?
fun runQueries() {
Observable.fromIterable(queriesTemp)
.subscribeOn(Schedulers.computation())
.doOnNext { query ->
if (query.contains("COMPLETION OF SDF QUERIES")) {
if (loginStatus == StaticVariables.FT_CASE_NEW_LOGIN) {
tasksQueriesTemp = arrayOfNulls(queries.size - queries.indexOf(query))
System.arraycopy(queries, queries.indexOf(query), tasksQueriesTemp, 0, tasksQueriesTemp!!.size)
}
// break the loop here
runOtherQueries()
break
}
if (!TextUtils.isEmpty(query)) {
mDatabase.execSQL(query, false, "")
}
action(tasksQueriesTemp!!.indexOf(query))
}
.doOnComplete { executeOtherUpdates(tasksQueriesTemp) }
.observeOn(AndroidSchedulers.mainThread())
.subscribe()
}
fun runOtherQueries() {
}
Factor out the part you want to break on from the doOnNext use takeWhile:
val broken = AtomicBoolean();
Observable.fromIterable(queriesTemp)
.subscribeOn(Schedulers.computation())
.takeWhile { query ->
if (query.contains("COMPLETION OF SDF QUERIES")) {
if (loginStatus == StaticVariables.FT_CASE_NEW_LOGIN) {
tasksQueriesTemp = arrayOfNulls(queries.size -
queries.indexOf(query))
System.arraycopy(queries, queries.indexOf(query),
tasksQueriesTemp, 0, tasksQueriesTemp!!.size)
}
// break the loop here
runOtherQueries()
broken.set(true)
return#takeWhile false // whatever the Kotlin syntax is for local returns
}
return#takeWhile true
}
.doOnNext { query ->
if (!TextUtils.isEmpty(query)) {
mDatabase.execSQL(query, false, "")
}
action(tasksQueriesTemp!!.indexOf(query))
}
.doOnComplete {
// if you don't want to execute the other updates if the code
// in takeWhile has "broken out of the loop"
if (!broken.get())
executeOtherUpdates(tasksQueriesTemp)
}
.observeOn(AndroidSchedulers.mainThread())
.subscribe()

Problems with unsubscribe

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.

Categories

Resources