RxJava custom ArrayList changes reference list also - android

Structure
Class Dogs extends RealmObject{
private RealmList<Cats> cats;
}
dogList contains non empty list of cats.
I am trying to remove cats/set as empty list but result also affects on parent list.
protected void removeCatsFromDogs(ArrayList<Dogs> dogList) {
ArrayList<Dogs> newList = new ArrayList<>(dogList);
Observable.fromIterable(newList)
.map(dogs ->
{
dogs.setCats(null);
// dogs.setCats(new RealmList<>()); also not working
return dogs;
}
).toList()
.doOnSuccess(dogs -> baseRealm.executeTransaction(realm -> baseRealm.copyToRealmOrUpdate(dogs)))
.subscribe();
}
doOnSuccess returns dogs with null list but it also reflects in original list.
I have also tried to copy objects one by one using for loop but still same issue.

As stated above, you need to manually create a copy of your objects with the properties you want copied. Something like this should work:
public class Dogs extends RealmObject {
public String name;
public RealmList<Cats> cats;
public static Dogs shallowCopy(Dogs dogs) {
Dogs copy = new Dogs();
copy.name = dogs.name;
return copy;
}
}
protected void removeCatsFromDogs(ArrayList<Dogs> dogList) {
ArrayList<Dogs> newList = new ArrayList<>(dogList);
Observable.fromIterable(newList)
.map(dogs -> { return Dogs.shallowCopy(dogs); })
.toList()
.doOnSuccess(dogs -> baseRealm.executeTransaction(realm -> baseRealm.copyToRealmOrUpdate(dogs)))
.subscribe();
}

You need to create a new Dog in your map() operation. The new Dog will have cats as empty, presumably.
Because you are passing in an object as a parameter, changes the value that is passed in. This is how Java parameter passing works.

Related

RXJava2 | Android: Filter arrayList and keep track of filter

Scenario: RXJava 2. Android.
Imagine you have an Observable from iterable like so: Observable.fromIterable(arrayList<Something>) and you need to do two things with it:
Filter the items.
Know if an item was filtered out (a.k.a.: the filter function returned false at least once).
This is a similar observable (extremely simplified to be relevant):
final ArrayList<Something> someArrayList = getTheArrayList();
Observable
.fromIterable(someArrayList)
.subscribeOn(Schedulers.computation())
.filter(new AppendOnlyLinkedArrayList.NonThrowingPredicate<Something>() {
#Override
public boolean test(final Something something) {
return something.isValid();
}
})
.toList()
.observeOn(AndroidSchedulers.mainThread())
.doOnSuccess(new Consumer<List<Something>>() {
#Override
public void accept(#NonNull final List<Something> somethings)
throws Exception {
// How can I tell if the filter above returned false
// at least once?
}
})
.subscribe();
To answer the above question, one option is to compare the original someArrayList to somethings. If they are different, well, something happened. But this means the list have to have the same items in the same order, which can be a problem if Something is a complicated object, that must implement an equals.
The plan B, which I am using and I don't like is to keep a "boolean array" outside the observable, like so:
final boolean[] hasInvalidData = new boolean[1];
and then in the .filter I can do:
final isValid = something.isValid();
if (!isValid) {
hasInvalidData[0] = true;
}
return isValid;
and in success, I could simply do:
if (hasInvalidData[0]) {
// Something has been filtered
}
The question is: is there a better approach?
UPDATE:
What I have done so far is simply compare originalList.size() with finalEmitedList.size(). If they are different, that means my filter "filtered" something.
The way that I read it, this smells like an XY problem. If you don't need to keep track of the elements and you just need to compute until you find one element, everything becomes much easier:
Observable
.fromIterable(someArrayList)
.subscribeOn(Schedulers.computation())
.map(something -> something.isValid())
.filter(bool -> bool)
.first(false);
If you actually need the list of elements:
Observable<Something> source = Observable
.fromIterable(someArrayList)
.subscribeOn(Schedulers.computation())
.publish()
.autoConnect(2);
source
.map(something -> something.isValid())
.reduce(false, (a,b) -> a | b)
.zipWith(source.toList(), (flag, list) -> {
// do your stuff
})
.subscribe();
not sure it works but Observable.sequenceEqual could work:
ArrayList<Object> list = ...
Predicate myFilter = ...
Observable<Object> observable = Observable.fromIterable(list);
Observable.sequenceEqual(observable, observable.filter(myFilter))
.subscribe(new Consumer<Boolean>() {
#Override
public void accept(#NonNull Boolean changed) throws Exception {
// result here
}
});

Combine multiple Retrofit Observable dynamically

I have a list of Observables like so:
List<Observable<MyObj>> listObservables = new ArrayList<Observable<MyObj>>();
I'd like to combine all Observable in a single one, I can handle it if I know the number of Observable using zip(), for example we have 3 Observable:
Observable<MyObj1> obs1= MyRestClient.getSomeData1();
Observable<MyObj2> obs2= MyRestClient.getSomeData2();
Observable<MyObj3> obs3= MyRestClient.getSomeData3();
I have a wrapper obj:
class MyWrapperObj {
private MyObj1 onj1;
private MyObj2 onj2;
private MyObj3 onj3;
public MyWrapperObj(MyObj1 onj1, MyObj2 onj2, MyObj3 onj3) {
this.onj1 = onj1;
this.onj2 = onj2;
this.onj3 = onj3;
}
}
So I can combine them like so:
Observable<MyWrapperObj> combinedObservable = Observable.zip(obs1, obs2, obs3, new Func3<MyObj1, MyObj2, MyObj3, MyWrapperObj>() {
#Override
public MyWrapperObj call(MyObj1 obj1, MyObj2 obj2, MyObj3 obj3) {
return new MyWrapperObj(obj1, obj2, obj3);
}
});
combinedObservable.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe(new Subscriber<MyWrapperObj>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable throwable) {
}
#Override
public void onNext(MyWrapperObj wrapperObj) {
}
});
Everything is working fine, so my problem is how to organize this combination to be for n Observable?
RESPONSE
as #maciekjanusz mentioned in it's answer I did:
Observable<MyWrapperObj> combinedObservable = Observable.zip(listObservables, new FuncN<MyWrapperObj>() {
#Override
public MyWrapperObjcall(Object... args) {
return null;
}
});
If you want to zip n Observables, put them in a list and apply the public static <R> Observable<R> zip(#NotNull java.lang.Iterable<? extends Observable<?>> ws, rx.functions.FuncN<? extends R> zipFunction) factory method.
List<Observable<String>> observables = Arrays.asList(Observable.just("String 1"), Observable.just("String 2"));
Observable.zip(observables, args -> {
// put your zipping code here
});
For example, if you want to create a list of strings for each emission from all observables:
Observable.zip(observables, Arrays::asList);
Or, if using RxJava on android without retrolambda:
Observable.zip(observables, args -> Arrays.asList(args));
Suppose you have the list:
List<Observable<MyObj>> listObservables
You might consider using Observable.concatDelayError
The advantage if it is finishing all Obbservable's even if any of them finishes with error (resulting in an error in such case).
Remember, that every Observable in this sequence must return the result to onNext method of Subscriber. The result also must have the same type.
Example:
Observable.concatDelayError(listObservables);
You can wait until all observables is complete by using
.zip(observable1, ..., observableN, funcN).first() operators. There is an overload, accepting Observable> argument (as in FlatMap).
First overload takes Iterable> - you can pass list of observables of arbitrary size and second argument - FuncN - receives list of values.

Query realm data contained on other object

This question is a follow-up question from: Organize Android Realm data in lists
Due to the data returned by the API we use, it's slightly impossible to do an actual query on the realm database. Instead I'm wrapping my ordered data in a RealmList and adding a #PrimaryKey public String id; to it.
So our realm data looks like:
public class ListPhoto extends RealmObject {
#PrimaryKey public String id;
public RealmList<Photo> list; // Photo contains String/int/boolean
}
which makes easy to write to and read from the Realm DB by simply using the API endpoint as the id.
So a typical query on it looks like:
realm.where(ListPhoto.class).equalTo("id", id).findFirstAsync();
This creates a slightly overhead of listening/subscribing to data because now I need to check listUser.isLoaded() use ListUser to addChangeListener/removeChangeListener and ListUser.list as an actual data on my adapter.
So my question is:
Is there a way I can query this realm to receive a RealmResults<Photo>. That way I could easily use this data in RealmRecyclerViewAdapter and use listeners directly on it.
Edit: to further clarify, I would like something like the following (I know this doesn't compile, it's just a pseudo-code on what I would like to achieve).
realm
.where(ListPhoto.class)
.equalTo("id", id)
.findFirstAsync() // get a results of that photo list
.where(Photo.class)
.getField("list")
.findAllAsync(); // get the field "list" into a `RealmResults<Photo>`
edit final code: considering it's not possible ATM to do it directly on queries, my final solution was to simply have an adapter that checks data and subscribe if needed. Code below:
public abstract class RealmAdapter
<T extends RealmModel,
VH extends RecyclerView.ViewHolder>
extends RealmRecyclerViewAdapter<T, VH>
implements RealmChangeListener<RealmModel> {
public RealmAdapter(Context context, OrderedRealmCollection data, RealmObject realmObject) {
super(context, data, true);
if (data == null) {
realmObject.addChangeListener(this);
}
}
#Override public void onChange(RealmModel element) {
RealmList list = null;
try {
// accessing the `getter` from the generated class
// because it can be list of Photo, User, Album, Comment, etc
// but the field name will always be `list` so the generated will always be realmGet$list
list = (RealmList) element.getClass().getMethod("realmGet$list").invoke(element);
} catch (Exception e) {
e.printStackTrace();
}
if (list != null) {
((RealmObject) element).removeChangeListener(this);
updateData(list);
}
}
}
First you query the ListPhoto, because it's async you have to register a listener for the results. Then in that listener you can query the result to get a RealmResult.
Something like this
final ListPhoto listPhoto = realm.where(ListPhoto.class).equalTo("id", id).findFirstAsync();
listPhoto.addChangeListener(new RealmChangeListener<RealmModel>() {
#Override
public void onChange(RealmModel element) {
RealmResults<Photo> photos = listPhoto.getList().where().findAll();
// do stuff with your photo results here.
// unregister the listener.
listPhoto.removeChangeListeners();
}
});
Note that you can actually query a RealmList. That's why we can call listPhoto.getList().where(). The where() just means "return all".
I cannot test it because I don't have your code. You may need to cast the element with ((ListPhoto) element).
I know you said you're not considering the option of using the synchronous API, but I still think it's worth noting that your problem would be solved like so:
RealmResults<Photo> results = realm.where(ListPhoto.class).equalTo("id", id).findFirst()
.getList().where().findAll();
EDIT: To be completely informative though, I cite the docs:
findFirstAsync
public E findFirstAsync()
Similar to findFirst() but runs asynchronously on a worker thread This method is only available from a Looper thread.
Returns: immediately an empty RealmObject.
Trying to access any field on the returned object before it is loaded
will throw an IllegalStateException.
Use RealmObject.isLoaded() to check if the object is fully loaded
or register a listener RealmObject.addChangeListener(io.realm.RealmChangeListener<E>) to be
notified when the query completes.
If no RealmObject was found after
the query completed, the returned RealmObject will have
RealmObject.isLoaded() set to true and RealmObject.isValid() set to
false.
So technically yes, you need to do the following:
private OrderedRealmCollection<Photo> photos = null;
//...
final ListPhoto listPhoto = realm.where(ListPhoto.class).equalTo("id", id).findFirstAsync();
listPhoto.addChangeListener(new RealmChangeListener<ListPhoto>() {
#Override
public void onChange(ListPhoto element) {
if(element.isValid()) {
realmRecyclerViewAdapter.updateData(element.list);
}
listPhoto.removeChangeListeners();
}
}

How can i change a Field in a List of Objects with RXJava/Android

I have a List myPersonList an i wanna fill the Name and the City of every item. I have the id's already filled. I could create a foreach and call the FillPersonData but how can i do this with Observables? I know there is something with Map() and FlatMap() ... but i don't get it
class Person{
int id;
String name;
String city;
}
List<Person> myPersonList;
what i have to do to get an observable List where the Person Data are filled by this Function:
class PersonUtil{
public Person FillPersonData(Person person){
... do something to fill the Person Data...
}
}
Update:
Sorry my Fault: i didn't say that i also need the Observable back to subscribe on it AND not the Person Class has a Function named fillPersonData()... let me say i have something like PersonUtil...
So i need this to return it:
Observable<List<Person>> myObservable = ...the Magic Code :-)
Update 2:
I tried the Answer from frhack and it works. Just for completeness because i work in Android Studio without Lambdas here the Code without Lambdas:
Observable<Person> observable = Observable.from(personList)
.filter(new Func1<Person, Boolean>() {
#Override
public Boolean call(Person person) {
PersonUtil.FillMailData(person);
return true;
}
});
I hope is the right Way... if NOT... tell me that and i change my Update2!!
List<Person> personList = new ArrayList<Person>();
// populate personList
Observable.from(personList).forEach((person)-> {
person.setName(name);
person.setCity(city);
});
alternate version:
List<Person> personList = new ArrayList<Person>();
// populate personList
Observable.from(personList).forEach((person)-> p.fillPersonData() );
Alternate version:
List<Person> personList = new ArrayList<Person>();
// populate personList
Observable<Person> observable = Observable.from(personList)
.filter((person) -> {
person.setName(name);
//PersonUtil.fillPersonData(person)
return true;
});
observable.forEach((pp)->System.out.println(pp));

Using a ForeignCollection

My entity contains the following private ForeignCollection attribute:
#ForeignCollectionField
private ForeignCollection<Order> orderCollection;
private List<Order> orderList;
What is the best way or usual way to avoid a having a caller use a ForeignCollection? Is there any neat way to return the Collections data to a caller?
How does the following method look? It allows a caller to access the data via a List. Would you recommend doing it this way?
public List<Order> getOrders() {
if (orderList == null) {
orderList = new ArrayList<Order>();
for (Order order : orderCollection) {
orderList.add(order);
}
}
return orderList;
}
If it's ok to change the signature to Collection rather than List, you could try using Collections.unmodifiableCollection().
public Collection<Order> getOrders()
{
return Collections.unmodifiableCollection(orderCollection);
}
Otherwise, your approach of using a lazy member variable is fine (provided you don't need synchronization). Also, note that you can just use the constructor of ArrayList to copy the values from the source collection:
orderList = new ArrayList<Order>(orderCollection);

Categories

Resources