RX-Android + ViewModel + Retrofit doesn't call OnComplete() - mvvm

I cannot get the OnComplete() method to be called after all items are processed. I need to do so in order to (al the very least) hide the loading view. I'm a little new to JavaRX so I don't know where exactly is the problem. Can you help me to get the OnComplete() called when all items are processed?
The code does the following:
Show the loading view and get the list of items (just references).
Check if they are local or remote items.
If they are local, get them and add them to the list.
If they are remote, download them and add them to the list.
With the list built, draw the data on the UI.
Final processing and hiding of the loading view.
The code is the following:
private void loadDataRX(final long fromTime, final long toTime) {
mLoadingPb.setVisibility(View.VISIBLE);
iCompositeDisposable.clear();
iCompositeDisposable.add(mViewModel.getItems(fromTime, toTime)
.subscribeOn(Schedulers.io())
.flatMap(items -> {
Activity context = ItemFragment.this.getActivity();
if (context == null) {
Log.e(TAG, "Cannot present results: context is null");
return Flowable.empty();
} else {
context.runOnUiThread(() -> {
mItems.clear();
mCustomView.reset();
});
if (items != null && items.size() > 0) {
return Flowable.just(items);
} else {
Log.i(TAG, "No items.");
return Flowable.just(Collections.singletonList(new Item(-1))); // This is my current way of solving a similar problem so as to know if I don't have any items
}
}
})
.concatMapIterable(items -> items)
.concatMap(item -> {
if (item.getUid() == -1) {
return Flowable.just(item);
}
String file = item.getFileName();
boolean uploaded = item.isUploaded();
if (uploaded) { // Remote file
if (item.getUid() > 0) {
return iRetrofit.create(RestApi.class).getItem(item.getUid());
} else {
return Flowable.empty();
}
} else { // Local file
return Flowable.just(item);
}
})
.observeOn(AndroidSchedulers.mainThread())
.subscribe(item -> {
Log.i(TAG, "Loaded items RX");
if (item instanceof Item) {
//Do stuff with the item and the files
} else if (item instanceof ResponseBody) {
//This is dirty but I didn't find another way. So here I basically extract the items and the files from the server's response. At least, it works.
} else {
Log.i(TAG, "No results for the given dates");
}
}, throwable -> {
mLoadingPb.setVisibility(View.GONE);
Log.e(TAG, "Error: " + throwable.getMessage());
}, () -> {
mLoadingPb.setVisibility(View.GONE);
Log.i(TAG, "Loading results completed"); // Can't get this to be called
})
);
}
Thanks in advance.

I guess that mViewModel.getItems returns Flowable. For flowable to complete we need to explicitly dispose it.
To resolve that you can make mViewModel.getItems to return Single<List<ItemType>>, then transform stream using .flatMapObservable { Observable.fromIterable(it) } to process each item.

Related

Returning Mono response from subscribe of Mono.fromCallable

What I am trying to accomplish is to return a simple Mono Response.
I am calling different backends API's in the method detailsHandler.fetchDetailsValue
Since this is a Synchronous blocking call, I am wrapping it in Mono.fromCallable as suggested in the documentation.
But I am facing this error upon compiling -
error: local variables referenced from a lambda expression must be final or effectively final
Actually, inside .subscribe lambda I am trying to assign to Response object which is declared outside the lambda. Since I need to assign the object returned from the fetchDetailsValue method upon subscription, how can I return this response object ?
Please correct me if wrong below and suggest how to fix this. Appreciate any inputs. Thanks!
Below is the sample code -
#Override
public Mono<Response> getDetails(Mono<RequestDO> requestDO) {
return requestDO.flatMap(
request -> {
Response response = new Response();
Mono<List<Object>> optionalMono = Mono.fromCallable(() -> {
return detailsHandler.fetchDetailsValue(request);
});
optionalMono. subscribeOn(Schedulers.boundedElastic())
.subscribe(result -> {
Cat1 cat1Object = null;
Cat2 cat2Object = null;
for(Object obj : result) {
if (obj instanceof Cat1) {
cat1Object = (Cat1) obj;
response.addResponseObj(cat1Object); // error: local variables referenced from a lambda expression must be final or effectively final
}
if (obj instanceof Cat2) {
cat2Object = (Cat2) obj;
response.addResponseObj(cat2Object); // error: local variables referenced from a lambda expression must be final or effectively final
}
}
});
return Mono.just(response);
});
}
When I tried to declare that Response object inside subscribe method and tried to return as and when value is received. But getting the error - Void methods cannot return a value
Below is the code -
#Override
public Mono<Response> getDetails(Mono<RequestDO> requestDO) {
return requestDO.flatMap(
request -> {
Mono<List<Object>> optionalMono = Mono.fromCallable(() -> {
return detailsHandler.fetchDetailsValue(request);
});
optionalMono. subscribeOn(Schedulers.boundedElastic())
.subscribe(result -> {
Response response = new Response(); // Added this inside subscribe lambda. But now getting - Void methods cannot return a value
Cat1 cat1Object = null;
Cat2 cat2Object = null;
for(Object obj : result) {
if (obj instanceof Cat1) {
cat1Object = (Cat1) obj;
response.addResponseObj(cat1Object);
}
if (obj instanceof Cat2) {
cat2Object = (Cat2) obj;
response.addResponseObj(cat2Object);
}
}
return Mono.just(response); // Added this inside subscribe lambda. But now getting - Void methods cannot return a value
});
});
}
UPDATE:
When I tried like below, I am getting errors. Please correct if anything I am doing wrong.
public Mono<Response> getDetails(Mono<RequestDO> requestDO) {
return requestDO
.flatMap(request -> Mono.fromCallable(() -> detailsHandler.fetchDetailsValue(request)))
.map(result -> {
Response response = new Response();
for (Object obj : result) {
if (obj instanceof Cat1) {
response.addResponseObj((Cat1) obj);
}
if (obj instanceof Cat2) {
response.addResponseObj((Cat2) obj);
}
}
return response;
})
.map(result1 -> {
Response response = resultnew;
requestDO.flatMap(request -> Mono.fromCallable(() -> detailsHandler.fetchAdditionalValue(request, response)))
.map(result2 -> {
return result2;
});
}
You should not call subscribe inside your Reactor pipeline. Subscribe should be considered a terminal operation that starts the pipeline asynchronously in an unknown time in the future, and should only serve to connect to some other part of your system.
What you want is to transform your List<Object> into a new Response using a simple synchronous function, the map operator is made for this:
public Mono<Response> getDetails(Mono<RequestDO> requestDO) {
return requestDO
.flatMap(request -> Mono.fromCallable(() -> detailsHandler.fetchDetailsValue(request)))
.map(result -> {
Response response = new Response();
for (Object obj : result) {
if (obj instanceof Cat1) {
response.addResponseObj((Cat1) obj);
}
if (obj instanceof Cat2) {
response.addResponseObj((Cat2) obj);
}
}
return response;
});
}
Update
For your updated question you want to use both request and response to call another Mono. You can do this by first pulling the map inside the flatMap, then add another flatMap to it:
public Mono<Response> getDetails(Mono<RequestDO> requestDO) {
return requestDO
.flatMap(request -> Mono.fromCallable(() -> detailsHandler.fetchDetailsValue(request))
.map(result -> {
Response response = new Response();
for (Object obj : result) {
if (obj instanceof Cat1) {
response.addResponseObj((Cat1) obj);
}
if (obj instanceof Cat2) {
response.addResponseObj((Cat2) obj);
}
}
return response;
})
.flatMap(response -> Mono.fromCallable(() -> detailsHandler.fetchAdditionalValue(request, response))));
}

Is this approach is an Anti-Pattern? - Flutter

strong textI'm implementing a contacts list search bar, where the user can search & select some contacts, but I need the selected contacts to be shown as selected while the user is still searching.
I achieved this by modifying the original list and the duplicate list which used in the searching process at the same time.
is this an Anti-Pattern and is there a better way to do it?
Here's what I'm doing with the search query:
void searchContacts([String? name]) {
if (name == null || name.isEmpty) {
searchedList.clear();
addAllContactsToSearchList();
return;
} else {
searchedList.clear();
originalContactsList!.forEach((contact) {
if (contact.name.toLowerCase().contains(name.toLowerCase())) {
searchedList.clear();
searchedList.add(contact);
return;
} else {
return;
}
});
return;
}
}
and here's the code for selecting a contact:
void _onChange(bool value, int index) {
final selectedContact = searchedList[index].copyWith(isSelected: value);
searchedList.removeAt(index);
setState(() {
searchedList.insert(index, selectedContact);
notifier.originalContactsList = notifier.originalContactsList!.map((e) {
if (e.number == selectedContact.number) {
return selectedContact;
} else {
return e;
}
}).toList();
});}
This is expected behavior: gif
Some assumptions I'm making is that (1) each contact has a unique identifier that isn't just a name, and (2) you don't need the selected contacts to be shown in the same list as a search with a different query (if you search H, select Hasan, then search Ha, you expect it to still show up as selected on the next page, but if you search He, Hasan shouldn't shouldn't be on that next list.
The best way to do this is to have one constant list, and one list with results:
Set<String> selectedContactIds = {};
List<Contact> searchedList = [];
void _onChange(bool value, int index) {
final clickedContact = searchedList[index];
bool itemAlreadySelected = selectedContactIds.contains(clickedContact.userID);
setState({
if(itemAlreadySelected) {
selectedContactIds.remove(clickedContact.userID);
} else {
selectedContactIds.add(clickedContact.userID);
}
});
}
Now once you set state, your ListView should be updating the appearance of selected objects by checking if it's selected the same way that the _onChange function is checking if the item was already selected, which was:
bool itemAlreadySelected = selectedContactIds.contains(clickedContact.userID);
And this way, you don't have a whole class for contacts with a dedicated isSelected member. You wouldn't want that anyways because you're only selecting contacts for very specific case by case uses. Hopefully this helps!

How to Implement a Print Queue on Flutter?

I´m using esc_pos_bluetooth 0.2.8 library and connect the printer by bluetooth
Is ok when I send one order at a time, but when there are simultaneous orders, it print just the first and ignore the others. I try implement a print queue but I was not successful.
List printQueue = [];
insertPrintQueue(Schema item) {
printQueue.add(item);
printTicket(printQueue[0]);
}
verifyPrintQueue() {
if (printQueue.length > 0) {
printTicket(printQueue[0]);
}
}
printTicket(Schema item) async {
if (printerDevices != null) {
printerManager.selectPrinter(printerDevices[0]);
await printerManager
.printTicket(await ticket.templateTicket(item))
.then((value) => {
printQueue.removeAt(0),
if (printQueue.length > 0)
{
printTicket(printQueue[0])
}
});
} else {
print("None Printer Found");
}
I think this is happening because I'm trying to print while the printer is busy, have a way to check the status of the printer? and just order the print when the printer is not printing
insertPrintQueue(Schema item) {
printQueue.add(item);
printTicket(printQueue[0]); // Here
}
You are trying to print the first item always. So it works as expected. You can do it like this:
insertPrintQueue(Schema item) {
printQueue.add(item);
//printTicket(printQueue[0]);
for (var ticket in printQueue) {
printTicket(ticket);
}
}

How to map each item from observable to another one that comes from async function?

I want to
1.map item from observable to another one if it has already saved in database.
2.otherwise, use it as it is.
and keep their order in result.
Saved item has some property like tag, and item from observable is 'raw', it doesn't have any property.
I wrote code like this and run testMethod.
class Item {
final String key;
String tag;
Item(this.key);
#override
String toString() {
return ('key:$key,tag:$tag');
}
}
class Sample {
///this will generate observable with 'raw' items.
static Observable<Item> getItems() {
return Observable.range(1, 5).map((index) => Item(index.toString()));
}
///this will find saved item from repository if it exists.
static Future<Item> findItemByKey(String key) async {
//simulate database search
await Future.delayed(Duration(seconds: 1));
if (key == '1' || key == '4') {
final item = Item(key)..tag = 'saved';
return item;
} else
return null;
}
static void testMethod() {
getItems().map((item) async {
final savedItem = await findItemByKey(item.key);
if (savedItem == null) {
print('not saved:$item');
return item;
} else {
print('saved:$savedItem');
return savedItem;
}
}).listen((item) {});
}
The result is not expected one.
expected:
saved:key:1,tag:saved
not saved:key:2,tag:null
not saved:key:3,tag:null
saved:key:4,tag:saved
not saved:key:5,tag:null
actual:
not saved:key:2,tag:null
not saved:key:3,tag:null
not saved:key:5,tag:null
saved:key:1,tag:saved
saved:key:4,tag:saved
How to keep their order in result?
I answer myself to close this question.
According to pskink's comment, use asyncMap or concatMap solve my problem. Thanks!!
below is new implementation of testMethod.
asyncMap version:
getItems().asyncMap((item) {
final savedItem = findItemByKey(item.key);
if (savedItem != null)
return savedItem;
else
return Future.value(item);
}).listen(print);
concatMap version:
getItems().concatMap((item) {
final savedItem = findItemByKey(item.key);
if (savedItem != null)
return Observable.fromFuture(savedItem);
else
return Observable.just(item);
}).listen(print);

How do I check if a firebase database value exists?

I'm using the Realtime Database with Google's Firebase, and I'm trying to check if a child exists.
My database is structured as the following
- / (root)
- /users/
–- /james/
-- /jake/
- /rooms/
-- /room1/
--- (room 1 properties)
-- /room2/
--- (room 2 properties)
I would like to check if room1 exists.
I have tried the following:
let roomName:String = "room1"
roomsDB.child(roomName).observeSingleEventOfType(.Value) {
(snap:FIRDataSnapshot) in
let roomExists:Bool = snap.value != nil ? "TAKEN" : "NOT TAKEN"
}
In accessing snap.value it returns a JSON of the properties of that room, but how would I check if the room (/rooms/room1/) is there to begin with?
Comment if any clarification is needed
self.ref = FIRDatabase.database().reference()
ref.child("rooms").observeSingleEvent(of: .value, with: { (snapshot) in
if snapshot.hasChild("room1"){
print("true rooms exist")
}else{
print("false room doesn't exist")
}
})
While the answer of #ismael33 works, it downloads all the rooms to check if room1 exists.
The following code accomplishes the same, but then only downloads rooms/room1 to do so:
ref = FIRDatabase.database().reference()
ref.child("rooms/room1").observeSingleEvent(of: .value, with: { (snapshot) in
if snapshot.exists(){
print("true rooms exist")
}else{
print("false room doesn't exist")
}
})
I have some suggestions by using firebase.You check it from firebase.
We can test for the existence of certain keys within a DataSnapshot using its exists() method:
A DataSnapshot contains data from a Firebase database location. Any
time you read data from a Firebase database, you receive the data as a
DataSnapshot.
A DataSnapshot is passed to the event callbacks you attach with on()
or once(). You can extract the contents of the snapshot as a
JavaScript object by calling its val() method. Alternatively, you can
traverse into the snapshot by calling child() to return child
snapshots (which you could then call val() on).
A DataSnapshot is an efficiently-generated, immutable copy of the data
at a database location. They cannot be modified and will never change.
To modify data, you always use a Firebase reference directly.
exists() - Returns true if this DataSnapshot contains any data. It is slightly more efficient than using snapshot.val() !== null.
Example from firebase documentation(javascript example)
var ref = new Firebase("https://docs-examples.firebaseio.com/samplechat/users/fred");
ref.once("value", function(snapshot) {
var a = snapshot.exists();
// a === true
var b = snapshot.child("rooms").exists();
// b === true
var c = snapshot.child("rooms/room1").exists();
// c === true
var d = snapshot.child("rooms/room0").exists();
// d === false (because there is no "rooms/room0" child in the data snapshot)
});
Also please refer this page(already mentioned in my comment)
Here there is an example using java.
Firebase userRef= new Firebase(USERS_LOCATION);
userRef.child(userId).addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot snapshot) {
if (snapshot.getValue() !== null) {
//user exists, do something
} else {
//user does not exist, do something else
}
}
#Override
public void onCancelled(FirebaseError arg0) {
}
});
I hope you got an idea now.
You can check snapshot.exists value.
NSString *roomId = #"room1";
FIRDatabaseReference *refUniqRoom = [[[[FIRDatabase database] reference]
child:#"rooms"]
child:roomId];
[refUniqRoom observeSingleEventOfType:FIRDataEventTypeValue
withBlock:^(FIRDataSnapshot * _Nonnull snapshot) {
bool isExists = snapshot.exists;
NSLog(#"%d", isExists);
}];
Use any of them So simple and easy ...
Which way you like
ValueEventListener responseListener = new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
if (dataSnapshot.exists()) {
// Do stuff
} else {
// Do stuff
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
};
FirebaseUtil.getResponsesRef().child(postKey).addValueEventListener(responseListener);
function go() {
var userId = prompt('Username?', 'Guest');
checkIfUserExists(userId);
}
var USERS_LOCATION = 'https://SampleChat.firebaseIO-demo.com/users';
function userExistsCallback(userId, exists) {
if (exists) {
alert('user ' + userId + ' exists!');
} else {
alert('user ' + userId + ' does not exist!');
}
}
// Tests to see if /users/<userId> has any data.
function checkIfUserExists(userId) {
var usersRef = new Firebase(USERS_LOCATION);
usersRef.child(userId).once('value', function(snapshot) {
var exists = (snapshot.val() !== null);
userExistsCallback(userId, exists);
});
}
Firebase userRef= new Firebase(USERS_LOCATION);
userRef.child(userId).addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot snapshot) {
if (snapshot.getValue() !== null) {
//user exists, do something
} else {
//user does not exist, do something else
}
}
#Override
public void onCancelled(FirebaseError arg0) {
}
});
users = new HashMap<>();
users.put("UserID", milisec);
users.put("UserName", username);
users.put("UserEmailID", email);
users.put("UserPhoneNumber", phoneno);
users.put("UserPassword", hiddenEditPassword);
users.put("UserDateTime", new Timestamp(new Date()));
users.put("UserProfileImage", " ");
FirebaseFirestore.getInstance().collection("Users").document(phoneno).get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
#Override
public void onComplete(#NonNull Task<DocumentSnapshot> task) {
if (task.getResult().exists()) {
Toast.makeText(SignupActivity.this, "Already User", Toast.LENGTH_SHORT).show();
} else {
FirebaseFirestore.getInstance().collection("Users")
.document(phoneno).set(users).addOnCompleteListener(new OnCompleteListener<Void>() {
#Override
public void onComplete(#NonNull Task<Void> task) {
Toast.makeText(SignupActivity.this, "Registers", Toast.LENGTH_SHORT).show();
}
});
}
hideProgressDialog();
}
});`
enter code here