Android sorting array of custom objects - android

I am trying to sort a static array of Ticket objects into two different arrays based on their dates and Class. These two arrays will be used as data to populate two different list views. I only want to add one Ticket per Class object to each array. I get an error
ArrayList<Ticket> myUpcomingTickets = new ArrayList<>();
ArrayList<Ticket> myPastTickets = new ArrayList<>();
for (Ticket t : Ticket.getTickets()) {
if (t.getClass().getDate().after(date)) {
if(myUpcomingTickets.isEmpty()){
myUpcomingTickets.add(t);
}else {
for(Ticket t1: myUpcomingTickets) {
if (!t.getClass().getId().equals(t1.getClass().getId())) {
myUpcomingTickets.add(t);
}
}
}
} else {
if(myPastTickets.isEmpty()){
myPastTickets.add(t);
} else {
for(Ticket t2: myPastTickets) {
if (!t.getClass().getId().equals(t2.getClass().getId())) { //Add if not already there
myPastTickets.add(t);
}
}
}
}
}
Error Show in Logcat java.util.ConcurrentModificationException

There are a few things wrong with this. The error is caused by this
for(Ticket t2: myPastTickets) {
if (!t.getClass().getId().equals(t2.getClass().getId())) { //Add if not already there
myPastTickets.add(t);
}
}
you are not allowed to add or remove from a list you are currently looping through.
In addition this method would be very slow because of the nested loops you have.
My suggesting would be
1) extend your objects hash and equals to make your object easier to work with
public class Ticket {
#Override
public int hashCode() {
return getId().hashCode();
}
#Override
public boolean equals(final Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
final Ticket other = (Ticket ) obj;
return other.getId() == this.getId();
}
}
and then you can do this:
ArrayList<Ticket> myUpcomingTickets = new ArrayList<>();
ArrayList<Ticket> myPastTickets = new ArrayList<>();
for (Ticket t : Ticket.getTickets()) {
if (t.getClass().getDate().after(date)) {
if(!myUpcomingTickets.contains(t)){
myUpcomingTickets.add(t);
}
} else {
if(!myPastTickets.contains(t)){
myPastTickets.add(t);
}
}
}
and if you dont need the lists to be in an order i would suggest using a hashmap instead of arraylist to speed this up

You are modifying the lists (myUpcomingTickets, myPastTickets) while iterating them!
What you could do:
1. If not implemented yet, implement the equals and hashcode for Ticket object
2. Use list.contains(Obj) instead of using the for loop
E.g:
if(!myUpcomingTickets.contains(t)){
myUpcomingTickets.add(t);
}

Related

Realm not fully saved

is realm have something like listener for commiting data?
my code is
...
RealmDatabase.submitNewUser(mainActivity.getMyRealm(), userModel);
mainActivity.closeKeyboard();
mainActivity.onBackPressed();
...
public static void submitNewUser(Realm myRealm, UserModel user) {
myRealm.beginTransaction();
UserDb userDb = myRealm.createObject(UserDb.class);
userDb.setUserid(getNextUserId(myRealm));
userDb.setName(user.name);
....
myRealm.commitTransaction();
}
private static int getNextUserId(Realm myRealm) {
try {
return myRealm.where(UserDb.class).max("userid").intValue() + 1;
} catch (ArrayIndexOutOfBoundsException e) {
return 1;
}
}
after save data, i closed fragment and back to previous fragment.
on start function, checking if have data
#Override
public void onStart() {
super.onStart();
if (loading.isShowing()) loading.dismiss();
if (reloadList && checkContent()) {
....
}
reloadList = true;
}
private boolean checkContent() {
users = RealmDatabase.loadUserList(mainActivity.getMyRealm());
if (users.size() > 0 && users.get(0).userid > 0) {
// note:
// user id is auto increament while saved
// if no data, return dataset with userid = 0 for other purpose
return true;
} else {
....
return false;
}
}
public static List<UserModel> loadUserList(Realm myRealm) {
List<UserModel> list = new ArrayList<>();
RealmResults<UserDb> results = myRealm.where(UserDb.class).findAll();
results = results.sort("userid", Sort.DESCENDING);
for (UserDb result : results) {
UserModel userModel = new UserModel();
userModel.userid = result.getUserid();
....
userModel.note = result.getNote();
list.add(userModel);
}
if (list.size() == 0) {
UserModel userModel = new UserModel();
userModel.userid = 0;
userModel.note = "You still have no user at this time";
list.add(userModel);
}
return list;
}
checkContent(), user.size detected as 1 (new data is added) but userid still 0.
am i miss something in this logic? because everything is working well if i reopen app after add new user.
update
after using listener, i got my dataset but still not showing my content. after some trial i found that my list view is not showing the data even after i re-input data and do notifydataset on adapter.
users = RealmDatabase.loadUserList(mainActivity.getMyRealm());
homeAdapter.reloadList(users);
....
public void reloadList(List<UserModel> users) {
this.users = users;
notifyDataSetChanged();
}
update 2
everything going well for the 2nd, 3rd, and later item except the first one
is realm have something like listener for commiting data?
Yes
realmResults.addChangeListener(realmChangeListener);
One must keep a field reference to the RealmResults.
everything is working well if i reopen app after add new user.
Probably the ArrayList you build from the RealmResults is not updated.
as suggested by #epicPandaForce answer, i use listener to my code.
and to solved my problem as i mentioned in the last comment in #epicPandaForce answer, i change my code like this
getMyRealm().addChangeListener(new RealmChangeListener<Realm>() {
#Override
public void onChange(Realm element) {
// previous code in my activity
// getFragmentManager().popBackStackImmediate();
// new code in my activity
fragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.container, fragment);
}
});
those code not really inside the listener instead calling function on my activity. those placement just for where its called.

How to query in CouchDB by docType?

I'm trying to use the below query to get my documents by passing the value of docType [template or audit]; however, it is not showing the correct results.
Data.getData(database, "audit").run();
public static Query getData(Database database, final String type) {
View view = database.getView("data");
if (view.getMap() == null) {
view.setMap(new Mapper() {
#Override
public void map(Map<String, Object> document, Emitter emitter) {
if(document.get("docType").equals(type)){
emitter.emit(document.get("_id"), null);
}
}
}, "4");
}
return view.createQuery();
}
As far as I know, you might want to creat an index like this:
function (doc) {
if (doc.type) {
emit(doc.type, doc._id);
}
}
The above function creates an index for you sorted according to doc.type. Then you can do your queries faster.

Realm Custom Sort throws UnsupportedOperationException

Using Realm for Android and I have a custom sort which throws a java.lang.UnsupportedOperationException: Replacing and element is not supported
So, I have a piece of data that I need to sort after I have retreived it from a Realm database, so it is sitting in a List collection like this:
Collections.sort(stores, new DistanceComparator(currentLocation));
The DistanceComparator looks like this:
public class DistanceComparator implements Comparator<Restaurant> {
private Location currentLocation;
public DistanceComparator(Location location) {
currentLocation = location;
}
public int compare(RealmObject c1, RealmObject c2) {
if ((LocationUtil.calculateSomeValue(c1.getLocation()))
== (LocationUtil.calculateSomeValue(c2.getLocation()))) {
return 0;
}
else if ((LocationUtil.calculateSomeValue(c1.getLocation()))
< (LocationUtil.calculateSomeValue(c2.getLocation()))) {
return -1;
}
else {
return 1;
}
}
}
However, when I execute the sort, I am getting a:
java.lang.UnsupportedOperationException: Replacing and element is not supported.
at io.realm.RealmResults$RealmResultsListIterator.set(RealmResults.java:868)
at io.realm.RealmResults$RealmResultsListIterator.set(RealmResults.java:799)
at java.util.Collections.sort(Collections.java:247)
I cannot perform the sort within Realm due to the transient nature of the data.
Can you not perform a basic sort against a collection that happen to have Realm data or do I need to add some sort of annotation in the class that is a RealmObject in order to perform this type of sort?
I did take a look at this question and that is my fallback if this will not work: How do you sort a RealmList or RealmResult by distance?
You can't perform custom sorting on RealmResult. You'll have to copy your results to an unmanaged List and work with it instead of the realm results.
List<ModelMyFlirtsItem> storesList = realm.copyFromRealm(stores);
Collections.sort(storesList, new DistanceComparator(currentLocation));
You can consider moving the value evaluated by LocationUtil to be part of the RealmObject itself, and do the sort by Realm.
public int compare(RealmObject c1, RealmObject c2) {
if ((LocationUtil.calculateSomeValue(c1.getLocation()))
== (LocationUtil.calculateSomeValue(c2.getLocation()))) {
return 0;
}
else if ((LocationUtil.calculateSomeValue(c1.getLocation()))
< (LocationUtil.calculateSomeValue(c2.getLocation()))) {
return -1;
}
else {
return 1;
}
}
could be
public class RealmObject extends RealmObject {
private Integer distance;
private Location location;
public void setLocation(Location location) {
if(location == null) {
this.distance = null;
} else {
this.distance = LocationUtil.calculateSomeValue(location);
}
this.location = location;
}
}
Then
RealmResults<RealmObject> results = realm.where(RealmObject.class).findAllSorted("distance", Sort.ASCENDING);
I have also faced this problem for using sorting in RealmResults. I resolved it just using the Realm's native .sort() function. For example -
RealmResult<Location> stores......;
.......
.......
stores.sort(new DistanceComparator(currentLocation));

Is it possible to reuse the same RealmObject instance with different IDs to save/update multiple objects in a write transaction?

Currently, we are creating a new instance for every RealmObject that we want to save in our Mapper class.
#Override
public Person toRealmObject(Realm realm, PersonXML businessObject) {
Person person = new Person();
person.setId(businessObject.getId());
person.setName(businessObject.getName());
return person;
}
When we create a new one, we collect it into a list.
#Override
public void populateRealmListWithMappedModel(Realm realm, RealmList<Person> realmList, PersonsXML personXML) {
for(PersonXML personXML : personXML.getPersons()) {
realmList.add(personMapper.toRealmObject(realm, personXML));
}
}
/*next the following happens:*/
//realm.beginTransaction();
//personRepository.saveOrUpdate(realm, list);
//realm.commitTransaction();
Then we save the list.
#Override
public RealmList<T> saveOrUpdate(Realm realm, RealmList<T> list) {
RealmList<T> realmList = new RealmList<T>();
for(T t : realm.copyToRealmOrUpdate(list)) {
realmList.add(t);
}
return realmList;
}
The question is, is the following possible, can I re-use the same Person object instead and change its values to specify to Realm the objects that I want to have saved, but not have a whole object associated with it?
As in, something like this:
#Override
public Person toRealmObject(Realm realm, PersonXML businessObject, Person person) {
person.setId(businessObject.getId());
person.setName(businessObject.getName());
return person;
}
Then
#Override
public void writeObjectsToRealm(Realm realm, PersonsXML personXML) {
realm.beginTransaction();
Person person = new Person();
for(PersonXML personXML : personXML.getPersons()) {
person = personMapper.toRealmObject(realm, personXML, person));
personRepository.saveOrUpdate(person);
}
realm.closeTransaction();
}
Where this method is
#Override
public T saveOrUpdate(Realm realm, T t) {
return realm.copyToRealmOrUpdate(t);
}
I'm asking this because rewriting the architecture to use the following would require rewriting every populateRealmListWithMappedModel() methods I have, and that would be a bit concerning if it doesn't work. So I'm curious if it theoretically works.
Basically the short question is, if I call copyToRealmOrUpdate(t) on a realmObject, and alter its id and data, and save the same object again and again, will the write transaction succeed?
Yes you can re-use the object used as input to copyToRealm. We are creating a copy of your input object but not altering the original in any way, so reusing the object like you are doing should work and will also reduce the amount work the GC has to do.
Person javaPerson = new Person();
Person realmPerson = realm.copyToRealm(javaPerson);
// The following is true
assertTrue(javaPerson != realmPerson)
assertFalse(javaPerson.equals(realmPerson))
assertFalse(realmPerson.equals(javaPerson))
Yep, it worked.
#Override
public void persist(Realm realm, SchedulesXML schedulesXML) {
Schedule defaultSchedule = new Schedule();
if(schedulesXML.getSchedules() != null) {
realm.beginTransaction();
for(SchedulesByChannelXML schedulesByChannelXML : schedulesXML.getSchedules()) {
Channel channel = channelRepository.findOne(realm, schedulesByChannelXML.getChannelId());
if(channel == null) {
Log.w(TAG,
"The channel [" + schedulesByChannelXML.getChannelId() + "] could not be found in Realm!");
}
for(ScheduleForChannelXML scheduleForChannelXML : schedulesByChannelXML.getSchedules()) {
defaultSchedule = scheduleMapper.toRealmObject(realm, scheduleForChannelXML, defaultSchedule);
defaultSchedule.setChannel(channel);
defaultSchedule.setChannelId(schedulesByChannelXML.getChannelId());
boolean isInRealm = scheduleRepository.findOne(realm,
scheduleForChannelXML.getScheduleId()) != null;
Schedule savedSchedule = scheduleRepository.saveOrUpdate(realm, defaultSchedule);
if(!isInRealm) {
if(channel != null) {
channel.getSchedules().add(savedSchedule);
}
}
}
}
realm.commitTransaction();
}
}
Instead of creating 150 Schedule each time I save the batch of schedules, now I only create one per batch, and it works flawlessly.

How can I get the results returned by a map function with RxJava?

I am getting a list of multimedia information as part of my API call using RxJava's map function to create a list of image URLs from the response JSON. How can I handle the returned list from the map function? This is my code:
BrowseAPI service = CKApplication.getInstance().getRestAdapter().create(BrowseAPI.class);
BrowseRequest request = new BrowseRequest("0","50","true","A");
service.getMultmedia(MyApplication.getInstance().getJSessionID(), request).map(new Func1<Multimedia, Object>() {
#Override
public Object call(Multimedia multimedia) {
ArrayList<String> imageURLs = new ArrayList<String>();
for(MultiMediaImage image : multimedia.getResultsWrapper().getMultiMediaImage()) {
String url = "https://xxxxxxx/service/content/imageByEntitlement/";
url += image.getEid();
url +="?thumbnail=true";
imageURLs.add(url);
}
return imageURLs;
}
}).subscribe(multimedia -> {
Log.d("dfa", "adga");
});
I am able to create the string list of URLs from the response, I would like to handle this list of URLs in a separate activity or fragment.
Using flatMap, I am sure this can be improved:
BrowseAPI service = CKApplication.getInstance().getRestAdapter().create(BrowseAPI.class);
BrowseRequest request = new BrowseRequest("0","50","true","A");
service.getMultmedia(CKApplication.getInstance().getJSessionID(), request).flatMap(new Func1<Multimedia, Observable<MultiMediaImage>>() {
#Override
public Observable<MultiMediaImage> call(Multimedia multimedia) {
Observable<MultiMediaImage> observable = Observable.from(multimedia.getResultsWrapper().getMultiMediaImage());
return observable;
}
}).subscribe(new Action1<MultiMediaImage>() {
#Override
public void call(MultiMediaImage multiMediaImage) {
service.getImageObject(CKApplication.getInstance().getJSessionID(), multiMediaImage.getEid()).observeOn(AndroidSchedulers.mainThread()).subscribe(imageObject -> {
mImages.add(imageObject.getAuthenticatedStoreUrl());
adapter.notifyDataSetChanged();
});
}
});
How about this?
Observable<Multimedia> multimediaObservable = service.getMultmedia(MyApplication.getInstance().getJSessionID(), request);
Subscription subscription = bindFragment(this, multimediaObservable
.observeOn(AndroidSchedulers.handlerThread(new Handler()))
.map(new Func1<Multimedia, ArrayList<String>>() {
#Override
public ArrayList<String> call(Multimedia multimedia) {
ArrayList<String> imageURLs = new ArrayList<String>();
for(MultiMediaImage image : multimedia.getResultsWrapper().getMultiMediaImage()) {
imageURLs.add(makeImageUrl(image);
}
return imageURLs;
}
})).subscribe(multimediaArrayList -> {
//do something with the list
});
...
private String makeImageUrl(MultiMediaImage image){
String url = "https://xxxxxxx/service/content/"
url += image.getEid();
url +="?thumbnail=true";
return url;
}
Or with Java8 syntax that you use in the subscribe() method:
.map(multimedia -> {
ArrayList<String> imageURLs = new ArrayList<String>();
for(MultiMediaImage image : multimedia.getResultsWrapper().getMultiMediaImage()) {
imageURLs.add(makeImageUrl(image);
}
return imageURLs;
})
Some notes:
I assume that the multimediaObservable is a network call which should not be done in the main thread. If its an observable emitted from Retrofit you don't need to worry to have a separate async thread.
The bindFragment() is part of the RxAndroid library which automatically
"observe(s)On" the mainThread(). For activities use bindActivity(). Alternatively use observeOn(mainThread()).
There is no need as you do in the second example to use flatmap because it converts without need the object to observable and back to normal object (which the map directly can receive).
Don't forget the onError in the subsribe().
Update:
You can use from() to flatten the array and handle per MultimediaImage item in the subscribe.
Subscription subscription = bindFragment(this, multimediaObservable
.observeOn(AndroidSchedulers.handlerThread(new Handler()))
.flatMap(multimedia->
Observable.from(multimedia.getResultsWrapper().getMultiMediaImage())
).subscribe(multimediaImage -> {
//multimediaImage is a MultimediaImage class object
Log.i("ImageId", multimediaImage.getEid());
});

Categories

Resources