0

I have a class LocationComparator, which looks like this:

public class LocationComparator implements Comparator<MyLocation> {

    double mLatitude, mLongitude;

    public LocationComparator(double baseLat, double baseLon){
        mLatitude = baseLat;
        mLongitude = baseLon;
    }

    @Override
    public int compare(MyLocation o, MyLocation o2) {
        float[] result1 = new float[3];
        android.location.Location.distanceBetween(mLatitude, mLongitude, o.latitude, o.longitude, result1);
        Float distance1 = result1[0];           

        float[] result2 = new float[3];
        android.location.Location.distanceBetween(mLatitude, mLongitude, o2.latitude, o2.longitude, result2);
        Float distance2 = result2[0];           

        return distance1.compareTo(distance2);
    }    
}

Next, I have List<MyLocation> locations and I want to sort it. I use this code:

Collections.sort(locations, new LocationComparator(baseLat, baseLon));

where baseLat and baseLon is latitude and longitude of current position. Sometimes, the code throws an exception java.lang.IllegalArgumentException: Comparison method violates its general contract!.

I know, that this exception is thrown in case that compare method does not satisfy the condition of transitivity. I found some information here: Comparison method violates its general contract!. I understand why the exception is thrown in referenced issue, but still I don't know why exception is thrown in my code.

Could you help me, please? Thanks.

UPDATE

List of MyLocation is used for adapter for ListView. In case, that there is a new GPS location, data in ListView are sorted. The code looks like this:

@Override
public void onLocationChanged(Location location) {
    super.onLocationChanged(location);
    sortAdapter(new LatLng(location.getLatitude(), location.getLongitude()));
    mIsSortedByLocation = true;   
    }
}

And sortAdapter(LatLng latLng) method is here:

private void sortAdapter(LatLng latLng) {
    if (mAdapter == null || latLng == null) {
        return;
    }
    List<MyLocation> list = Arrays.asList(mAdapter.getItems());
    Collections.sort(locations, new LocationComparator(latLng.latitude, latLng.longitude));
    mAdapter.setItems(mMyLocations = (MyLocation[]) list.toArray());
}

UPDATE 2

The data comes from server. First, data are stored into the database, and then, there is a my ContentProvider and Loader. In onLoadFinished(Loader<Cursor> loader, Cursor cursor), the data from cursor are stored into the array and this array is used for creating an adapter, which extends from BaseAdapter.

Community
  • 1
  • 1
PetrS
  • 1,110
  • 14
  • 38
  • 2
    Could it be that your `MyLocation` is mutable and modified from another thread while sorting is running? – Dmitry Zaytsev Jun 26 '15 at 13:48
  • If you use this solution, what happens? `return Float.compare(result1[0], result2[0]);` – Buhake Sindi Jun 26 '15 at 13:53
  • I think that there is not big difference, because `Float.compare(float float1, float float2)` is called from `compareTo(Float object)`. Am I right or there is some difference? – PetrS Jun 26 '15 at 13:55
  • You are right. I was suspecting that maybe your `compareTo` call is causing the issue "somehow" so a static reference might resolve this? (Crazy, right?) :-) – Buhake Sindi Jun 26 '15 at 14:27
  • I think I understand the cause of your problem. You have a collection of `MyLocation` which some of `MyLocation` results in having the same comparator results. This can be attributed to the fact that you're always using the first element of the `result` array to determine the equality of `MyLocation`. A better solution to `compareTo` needs to be implemented here. – Buhake Sindi Jun 26 '15 at 14:33
  • @BuhakeSindi first element of `result` contains the distance. That's (very ugly) Android API. – Dmitry Zaytsev Jun 26 '15 at 14:50
  • I still do not know where is the problem. The first element of `result` contains the distance. I do not see the problem in this... – PetrS Jun 26 '15 at 14:56
  • @PSglass please post more code: your `MyLocation` class, how do you acquire it and what happens after sorting – Dmitry Zaytsev Jun 26 '15 at 15:06
  • @DmitryZaitsev see my update, please. – PetrS Jun 29 '15 at 07:29

1 Answers1

1

Without knowing details of your code, I would assume that your problem comes from concurrency.

List<MyLocation> list = Arrays.asList(mAdapter.getItems());
Collections.sort(locations, new LocationComparator(latLng.latitude, latLng.longitude));

It's either typo in your example, or locations reference is indeed populated outside of the method scope. So, I assume you're loading it from the server. Or it might very well be that you're modifying contents of Adapter from the background thread.

Avoid changing data from background thread which is used by the main thread. So, you must either synchronize on your collection before using it or just pass the result from background thead to main thread Handler:

List<MyLocation> locations;

void myBackgroundTask() {
    List<MyLocation> result = getLocationsFromServer();
    runOnUiThread(() -> { locations = result }); // lambda or Runnable
}
Dmitry Zaytsev
  • 23,650
  • 14
  • 92
  • 146
  • Thanks a lot for answer. You're right, the problem might be a concurrency, but... Data comes from server, but adapter is created in `onLoadFinished`. This method is called from UI thread. You can check my next update. – PetrS Jun 29 '15 at 08:31
  • @PSglass could you please paste the whole Activity/Fragment class? – Dmitry Zaytsev Jun 29 '15 at 08:54