0

I'm trying to sort each entry in my NewsAdapter's array by the publishedAt variable found on the Articles_Map object. I need it to be sorted by publishedAt after each time I add a new entry using the addAll method. Here is my code:

ListNewsActivity:

public class ListNewsActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        /* ... */

        // parameters for Sources endpoint
        String category = "sport";
        String language = "en";
        String country = "us";

        // Sources endpoint
        Sources_Interface client_sources = NewsAPI_Adapter.createService(Sources_Interface.class);
        Call<Sources_Map> call_sources = client_sources.getData(category, language, country);

        call_sources.enqueue(new Callback<Sources_Map>() {
            @Override
            public void onResponse(Call<Sources_Map> call_sources, Response<Sources_Map> response) {
                if (response.body() != null) {
                    final NewsAdapter nAdapter = new NewsAdapter(ListNewsActivity.this,
                            R.layout.article_layout);

                    for (final Sources_Content source : response.body().sources) {
                        if (source.sortBysAvailable.contains("latest")) {
                            // Articles endpoint
                            NewsAPI_Interface client = NewsAPI_Adapter.createService(NewsAPI_Interface.class);
                            Call<NewsAPI_Map> call = client.getData(source.id, "17f8ddef543c4c81a9df2beb60c2a478");

                            call.enqueue(new Callback<NewsAPI_Map>() {
                                @Override
                                public void onResponse(Call<NewsAPI_Map> call, Response<NewsAPI_Map> response) {
                                    if (response.body() != null) {
                                        ExpandableHeightGridView gv_content = (ExpandableHeightGridView) findViewById(R.id.gv_content);
                                        nAdapter.addAll(response.body().articles);
                                        gv_content.setAdapter(nAdapter);
                                        gv_content.setExpanded(true);
                                    }
                                }

                                @Override
                                public void onFailure(Call<NewsAPI_Map> call, Throwable t) {
                                    System.out.println("An error ocurred!\n" +
                                            "URL: " + call.request().url() + "\n" +
                                            "Cause: " + t.getCause().toString());
                                }
                            });
                        }
                    }
                }
            }

            @Override
            public void onFailure(Call<Sources_Map> call_sources, Throwable t) {
                System.out.println("An error ocurred!");
            }
        });
    }
}

NewsAdapter:

public class NewsAdapter extends ArrayAdapter<Articles_Map> {
    Context mContext;

    public NewsAdapter(Context c, int resource) {
        super(c, resource);
        this.mContext = c;
    }

    public NewsAdapter(Context c, int resource, List<Articles_Map> articles) {
        super(c, resource, articles);
        this.mContext = c;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        // get the property we are displaying
        Articles_Map article = getItem(position);

        // get the inflater and inflate the XML layout for each item
        LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
        View view = inflater.inflate(R.layout.article_layout, null);

        ImageView thumbnail = (ImageView) view.findViewById(R.id.thumbnail);
        TextView title = (TextView) view.findViewById(R.id.title);
        TextView description = (TextView) view.findViewById(R.id.description);

        Picasso.with(mContext).load(article.urlToImage).into(thumbnail);
        title.setText(article.title);
        description.setText(article.description);

        return view;
    }
}

Articles_Map:

public class Articles_Map {
    String title;
    String description;
    String url;
    String urlToImage;
    Date publishedAt;

    public Articles_Map(String title, String description, String url, String urlToImage, Date publishedAt) {
        this.title = title;
        this.description = description;
        this.url = url;
        this.urlToImage = urlToImage;
        this.publishedAt = publishedAt;
    }
}

EDIT - implemented byPublishedAtComparator's code:

private static final Comparator<Articles_Map> byPublishedAtComparator =
            new Comparator<Articles_Map>() {
                @Override
                public int compare(Articles_Map o1, Articles_Map o2) {
                    if (o1.publishedAt == null || o2.publishedAt == null) {
                        return 0;
                    }

                    return o1.publishedAt.compareTo(o2.publishedAt);
                }
            };
KaiZ
  • 391
  • 1
  • 5
  • 13
  • You'd probably wanna pass in sorted array when instantiating the adapter. You can use [Collections.sort()](http://stackoverflow.com/questions/16425127/how-to-use-collections-sort-in-java-specific-situation) method to get that. – Vucko Oct 19 '16 at 23:47

1 Answers1

4

Sort it before consuming using Collections.sort(List, Comparator)

Collections.sort(
  myArticles_MapList, // your specific list to be sorted
  new Comparator<Articles_Map>() {
    public int compare(Articles_Map o1, Articles_Map o2) {
      // Improve this to handle null publishedAt (make it early Paleozoic?)
      return o1.getPublishedAt().compareTo(o2.getPublishedAt());
    }
  }
)

[Edited]

Ha! But the ArrayAdapter already has a sort(Comparator) method!!!

Either invoke it:

  • when (if?) you know you have all the elements that need to be sorted (e.g end of processing a response); or
  • after each addition - by overriding the add and addAll methods (may be more CPU expensive).

The second approach can go like this:

public class NewsAdapter extends ArrayAdapter<Articles_Map> {
  private static final Comparator<Articles_Map> 
    byPublishedAtComparetor = new Comparator<Articles_Map>() {
        public int compare(Articles_Map o1, Articles_Map o2) {
          // Improve this to handle null publishedAt
          return o1.getPublishedAt().compareTo(o2.getPublishedAt());
        }
      }
    ;

  Context mContext;

  // snip...


  public NewsAdapter(Context c, int resource, List<Articles_Map> articles) {
    super(c, resource, articles);
    this.sort(byPublishedAtComparetor);
    this.mContext = c;
  }


  protected void doAdd(Articles_Map another) {
    suoer.add(another);
  }

  // Override to maintain the order
  void add(Articles_Map another) {
    this.doAdd(another);
    this.sort(byPublishedAtComparator);
  }
  // Overrides to maintain order and to avoid calling into
  // individual add(...) method, which will cause unnecessary
  // sorting after each element
  @Override
  public void addAll(Articles_Map... others) {
    for(Articles_Map a : others) {
      this.doAdd(a);
    }
    this.sort(byPublishedAtComparator);
  }

  @Override
  public void addAll(Collection<Articles_Map> others) {
    for(Articles_Map a : others) {
      this.doAdd(a);
    }
    this.sort(byPublishedAtComparator);
  }

  @Override
  public void insert(int i, Articles_Map article) {
    // decline to insert it at any indicated position
    // as it may break the order. Instead, treat it as any addition
    // which will automatically result in a reordering
    this.add(article);
  }

  // No override for remove - removing elements don't break
  // the order 
Adrian Colomitchi
  • 3,974
  • 1
  • 14
  • 23
  • Thanks, but that isn't quite enough to get what I need. Since I'm using callbacks, I can't seem to store the data retrieved by the `response` on a separate List, to later fill the NewsAdapter with that List's content. Do you have any idea how to get around that? – KaiZ Oct 20 '16 at 01:18
  • @KaiZ Hang on, where do you see the "separate `List`"? If you look into the static method, it sorts the received list in place the it returns the same list with the same content, only sorted. – Adrian Colomitchi Oct 20 '16 at 01:38
  • It isn't in the code I've posted here, as I've implemented it after posting this question. The thing is, I can't use that 3 argument constructor, I need to use my 2 argument constructor. I say that because if I used the 3 argument one, I had to declare it inside the loop (since that's where the data is received), and that declaring the same constructor on the same variable would be problematic. So to get around that, I made the 2 argument constructor, and add the data separately inside the loop. – KaiZ Oct 20 '16 at 01:43
  • @KaiZ Ok. See an updated solution, using the `ArrayAdapter` API. Tell me if it helps – Adrian Colomitchi Oct 20 '16 at 02:08
  • Very, very close this time! I've implemented what I needed from the code snippet you gave me, and I got it working overall. It sorted the adapter like I needed it to. However, I did some further tests with bigger sets of data, and in that case the app crashes with a `java.lang.IllegalArgumentException: Comparison method violates its general contract!` error. Do you know why? I think I'm already doing null handling correctly, but I'll leave my `byPublishedAtComparator`'s code in the original post so you can see it by yourself. – KaiZ Oct 21 '16 at 01:53
  • Nevermind, I've fixed the problem by following the advice on a [new question I posted](http://stackoverflow.com/questions/40180280/why-is-my-compareto-crashing-with-a-general-contract-violation-error). Still, thanks anyway, and I'll accept your answer as the correct one as it gave me most of what I needed. – KaiZ Oct 21 '16 at 22:21