PetrS
PetrS

Reputation: 1110

Sorting locations: Comparison method violates its general contract

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.

Upvotes: 0

Views: 314

Answers (1)

Dmitry Zaytsev
Dmitry Zaytsev

Reputation: 23972

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
}

Upvotes: 1

Related Questions