1

My question is based on a previous question about my Code - it's a newsfeed with different views which are scrollable vertically or horizontally or display various content (ads basically)

  • VERTICAL VIEW
  • HORIZONTAL VIEW
  • AD VIEW

Now I managed with the help of Ben P, on my previous post to organize my views with the following code:

this.items = new ArrayList<>();

if (listVertical.size() > 0) {
    items.add(listVertical.remove(0));
}

if (listHorizontal.size() > 0) {
    items.add(listHorizontal.remove(0));
}

while (listVertical.size() > 0 || listAd.size() > 0) {   

    if (listAd.size() > 0) {
        items.add(listAd.remove(0));
    }

    int count = 5;

    while (count > 0 && listVertical.size() > 0) {
        items.add(listVertical.remove(0));
    }
}

This is way to static for a feel-alive newsfeed. So I tried organizing my views with a position element in my JSON, like this:

{
"horizontal": [{...,position:"1",},{...,position:"3",...},{...,position:"4",...}] ,
"vertical": [{...,position:"2",},{...,position:"5",},{...,position:"6",}] 
"ad": [{...,position:"0",},{...,position:"7",},{...,position:"8",}] 
}

And with this code here it should be possible to get the Feed in position:

int length_tot= vertical.size() + horizontal.size() + ad.size();
this.items = new ArrayList<>();

for(int i = 0; i < lenth_tot; i++) {
    if(vertical.getallthepositions_somehow == i) {
        items.add(vertical.remove(0));
    }

    if(horizontal.getallthepositions_somehow == i) {
        items.add(horizontal.remove(0));
    }

    if(ad.getallthepositions_somehow == i) {
        items.add(ad.remove(0));
    }
}

As you can see, I'm stuck on looping through all Lists finding the next position to add in my items-List.

So my question is:

What's the fastest way to find the next position to add? My solution feels like it's a way too heavy, is there a more economical solution in your mind (from the point of view of the processor)?

Here is my onBindViewHolder:

@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {

    if (holder.getItemViewType() == VIEW_TYPE_VERTICAL)
        verticalView((VerticalViewHolder) holder, position);

    if (holder.getItemViewType() == VIEW_TYPE_HORIZONTAL)
        horizontalView((HorizontalViewHolder) holder, position);
}

And the getItemViewType from the adapter:

@Override
public int getItemViewType(int position) {
    if (items.get(position) instanceof Vertical)
        return VIEW_TYPE_VERTICAL;

    if (items.get(position) instanceof Horizontal)
        return VIEW_TYPE_HORIZONTAL;
}

EDIT:

So what I'm trying to get out of my JSON from the Server:

{
"horizontal": [{...,position:"1",},{...,position:"3",...},{...,position:"4",...}] ,
"vertical": [{...,position:"2",},{...,position:"5",},{...,position:"6",}] 
"ad": [{...,position:"0",},{...,position:"7",},{...,position:"8",}] 
}

is a Newsfeed with the following view-order:

- [AD] (position=0)
- [HORIZONTAL] (position =1)
- [VERTICAL] (position=2)
- [HORIZONTAL]
- [HORIZONTAL]

and so on...

So the server basically decides the order of my views in my adapter due the psoition item in each json-array

Ben P - Code for the right order in my List:

    Object[] itemsObject = new Object[listVertical.size() + listHorizontal.size() + adList.size()];

    for (Vertical item : listVertical) {
        itemsObject[Integer.parseInt(item.getPosition())] = item;
    }

    for (Horizontal item : listHorizontal) {
        itemsObject[Integer.parseInt(item.getPosition())] = item;
    }

    for (Adfeed item : adList) {
        it
    ArrayList<Object> itemsPos = new ArrayList<>();
    itemsPos.addAll(listVertical);
    itemsPos.addAll(listHorizontal);
    itemsPos.addAll(adList);

    Collections.sort(items, new Comparator<Object>() {
        @Override
        public int compare(Object o1, Object o2) {
            return Integer.compare(o1.getPosition(),o2.getPosition())
        }
    });

Vertical, Horizontal and Adfeed contain .getPosition(). So how do I access them in my Comparator?

KayD
  • 372
  • 5
  • 17
  • The problem is now a bit clearer to me after seeing your `onBindViewHolder` and `getItemViewType`. Let me think a little and will post if I find something. However, I think the implementation is correct and efficient. – Reaz Murshed Jul 15 '19 at 19:12
  • 1
    thank you @ReazMurshed, if you need some more code details, let me know. I didn't want to post the ultralong Adapter ad it is confusing having so much code for a question – KayD Jul 15 '19 at 19:13
  • Can you please check the updated answer? – Reaz Murshed Jul 15 '19 at 19:31
  • ok, this one is clear, thanks for the improvement :)! But there's a small piece of detail missing (and I'm pretty sure I wan't clear enough on this detail - sorry for that): you put a for-loop in there, but isn't it the case that I need to go with a if loop? Because I'm iterating through my Lists (which got parsed from a JSON and every List contains a position from my server) and I'm searching for position 0 in my Vertical,Horizontal or Ad-List to display it in my Newsfeed - then I go for position = 1 -> addToFeed ecc. – KayD Jul 15 '19 at 20:22
  • @ReazMurshed I did a quik edit on my Post to maybe get my question to a clear point! Thank you for yout time and patience, appreciate that <3 – KayD Jul 15 '19 at 20:26
  • After your JSONArray is being parsed, I guess you are storing all the items in separate arrays (i.e. vertical, horizontal & ad). Hence only the for loop for each of them is fine I think. In your code, `vertical.getallthepositions_somehow`, you are actually iterating over the `vertical` list again and again I suppose. – Reaz Murshed Jul 15 '19 at 21:16

2 Answers2

2

Actually, in any way, you need to construct a list of items for being passed in your adapter to be shown in the RecyclerView. Hence, looping through all the elements from each list (i.e. horizontal, vertical & ad) is not a very bad idea. You are actually doing some preprocessing of your items to be shown in the RecyclerView so that it can perform accordingly.

I can suggest one small improvement while preprocessing your data. In your code, you have to go through all the elements again an again for each list, in your every iteration. Which can be optimized actually. Considering this preprocessing is outside of the adapter, you might do something like this.

int total = vertical.size() + horizontal.size() + ad.size();
int[] types = new int[total];

// Considering the positions are not overlapping.
for(int i = 0; i < vertical.size(); i++) {
    types[vertical[i].position] = VIEW_TYPE_VERTICAL;
}

for(int i = 0; i < horizontal.size(); i++) {
    types[horizontal[i].position] = VIEW_TYPE_HORIZONTAL;
}

for(int i = 0; i < add.size(); i++) {
    types[add[i].position] = VIEW_TYPE_ADD;
}

// The type array has the view types for all elements now. 
// Now add the views accordingly in your items list and then pass it to your adapter.
ArrayList<Item> items = new ArrayList<Item>();
for(int i = 0; i < type.length; i++) {
    items.add(getItemBasedOnType(type[i]));
}

// Populate your adapter or pass new elements
adapter.addListItems(items);

The getItemBasedOnType can be something like this.

private Item getItemBasedOnType(int type) {
    if(type == VIEW_TYPE_VERTICAL) return vertical.remove(0);
    else if(type == VIEW_TYPE_HORIZONTAL) return horizontal.remove(0);
    else return add.remove(0);
}

Hope that helps a little.

Reaz Murshed
  • 23,691
  • 13
  • 78
  • 98
  • Maybe I don't get your code correctly (sorry if that's the case) but from what I understand you're basically changing the position of List 1, List 2 and Footer. I try to change the position of each item of your List 1 and List 2, and not only the full List by itself – KayD Jul 15 '19 at 18:53
  • 1
    The idea is actually the same. For each item, based on its type, you can pass different `ViewHolder` from your `onBindViewHolder` function. That's the idea. Let me know if that is still confusing. – Reaz Murshed Jul 15 '19 at 18:55
  • yeah it somehow still breaks my brain, because we're basically doing the same thing in our getItemView and onBindViewHolder, and I'm still trying to figure out how I can apply what I've seen from your code to my code (check my edit for the onBindViewHolder and getItemViewType) – KayD Jul 15 '19 at 19:02
  • 1
    I think the problem is now a bit clearer to me after seeing your `onBindViewHolder` and `getItemViewType`. Let me think a little and will post if I find something. However, I think the implementation is correct and efficient. – Reaz Murshed Jul 15 '19 at 19:11
  • Sorry for the delay of my Answer, the Code makes a lot of sense now. AndroidStudio somehow fills me in ClipData.Item in both ArrayList<> and in the private Item, in the private Item every return is underlined in red and tells me each time to make private Item return vertical, Hroizontal or AdList. This is new to me, is ArrayList depracted? – KayD Jul 16 '19 at 18:46
  • 1
    I don't think so. This may throw errors if you want to access the variable from a static environment without having those variables declared as static or final. Otherwise, they should be fine. – Reaz Murshed Jul 16 '19 at 19:05
  • 1
    The idea is to return elements based on their type. All element might extend a base class which is `ClipData.Item` I think in your case. I would suggest creating a single class having all the types and elements it and return that from the `getItemBasedOnType` function. So that there is no confusion and you can understand which type of data you are accessing based on their type. – Reaz Murshed Jul 16 '19 at 19:44
  • Look at my edit, I added the onBindViewHolder and the onCreateViewHolder Method. Can I skip the part you mentioned above, as I'm getting the viewType out of my OnCreateViewHolder or not? You're right, it's a bit confusing now at this point (sorry for that sir and thank you for your patience <3) – KayD Jul 16 '19 at 20:16
  • 1
    You can get your `ViewType` anywhere possible actually. I just provided a sample implementation. Your implementation might be different. – Reaz Murshed Jul 16 '19 at 21:36
  • implemented your Code an tried to use Object instead of Item, didn't work well. Why isn't Object allowed here? – KayD Jul 17 '19 at 20:52
  • Hi Reaz, I tried Ben P's way and it worked better on my problem. But I need to thank you for your time and engagement :)!! – KayD Jul 18 '19 at 19:17
  • Great to know that you have solved your problem. :) – Reaz Murshed Jul 18 '19 at 19:37
1

When implementing a RecyclerView.Adapter, you should strive to first create a single ordered list (or array) that represents your data set. If you can successfully build this list, everything else will become dramatically easier to do.

In order to build your list from the JSON sample you posted, I'd consider two different strategies. The first is to use an array so that you can assign specific indexes:

Object[] items = new Object[vertical.size() + horizontal.size() + ad.size()];

for (VerticalItem item : vertical) {
    items[item.position] = item;
}

for (HorizontalItem item : horizontal) {
    items[item.position] = item;
}

for (AdItem item : ad) {
    items[item.position] = item;
}

The second option is to use a List and then sort the list with a Comparator that compares positions. This will require that each of your three item types have a common superclass with the position field or implements an interface that exposes getPosition() etc.

List<PositionalItem> items = new ArrayList<>();
items.addAll(vertical);
items.addAll(horizontal);
items.addAll(ad);

Collections.sort(items, new Comparator<PositionalItem>() {
    @Override
    public int compare(PositionalItem lhs, PositionalItem rhs) {
        return Integer.compare(lhs.getPosition(), rhs.getPosition());
    }
});

Either of these will result in a single, sorted list that has your items in the same order you want to display them. We can now use this to implement the rest of the Adapter.

(This example is assuming you use an Object[] for your list of items. If you use List<PositionalItem> or something similar, obviously the syntax will be slightly different.)

private class MyAdapter extends RecyclerView.Adapter {

    private final Object[] items;

    public MyAdapter(Object[] items) {
        this.items = items;
    }

    @Override
    public int getItemCount() {
        return items.length;
    }

    @Override
    public int getItemViewType(int position) {
        Object item = items[position];

        if (item instanceof VerticalItem) {
            return R.layout.newsfeed_vertical;
        } else if (item instanceof HorizontalItem) {
            return R.layout.newsfeed_horizontal;
        } else if (item instanceof AdItem) {
            return R.layout.newsfeed_ad;
        } else {
            throw new IllegalStateException("unexpected item type: " + item);
        }
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        LayoutInflater inflater = LayoutInflater.from(parent.getContext());
        View itemView = inflater.inflate(viewType, parent, false);

        switch (viewType) {
            case R.layout.newsfeed_vertical: return new VerticalViewHolder(itemView);
            case R.layout.newsfeed_horizontal: return new HorizontalViewHolder(itemView);
            case R.layout.newsfeed_ad: return new AdViewHolder(itemView);
            default: throw new IllegalStateException("unexpected viewType: " + viewType);
        }
    }

    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        switch (holder.getItemViewType()) {
            case R.layout.newsfeed_vertical: verticalView((VerticalViewHolder) holder, position);
            case R.layout.newsfeed_horizontal: horizontalView((HorizontalViewHolder) holder, position);
            case R.layout.newsfeed_ad: adView((AdViewHolder) holder, position);
            default: throw new IllegalStateException("unexpected viewType: " + viewType);
        }
    }

    // ...
}

You'll notice that I've used a trick for view types: use the layout resource id instead of an int constant. There's no need to define your own constants; the resources framework has already created these constants for you!

Anyway, I'm happy to answer any other questions you have. But the major lesson here is to worry about building your list of items first, and then use the list in all of the Adapter methods.


Rather than use ArrayList<Object> it would be better to create an interface for all three of your item types to implement, and use that:

public interface NewsfeedItem {
    int getPosition();
}

Now you can add implements NewsfeedItem to the declaration of each of your classes:

public class Vertical implements NewsfeedItem {
    // ...
}
public class Horizontal implements NewsfeedItem {
    // ...
}
public class Adfeed implements NewsfeedItem {
    // ...
}

This will "just work" as long as each of your classes has an int getPosition() method in them. It looks like maybe getPosition() returns a String right now, so either you could change the interface definition to return String or you could change the classes to return int. Either way is fine, as long as all four match each other.

Then, once you have this interface set up, you can change your sorting code to use it:

ArrayList<NewsfeedItem> itemsPos = new ArrayList<>();
itemsPos.addAll(listVertical);
itemsPos.addAll(listHorizontal);
itemsPos.addAll(adList);

Collections.sort(items, new Comparator<NewsfeedItem>() {
    @Override
    public int compare(NewsfeedItem o1, NewsfeedItem o2) {
        return Integer.compare(o1.getPosition(),o2.getPosition());
    }
});
Ben P.
  • 52,661
  • 6
  • 95
  • 123
  • Hello Ben, nice to hear from you again after my first question to my feedAdapter :). I love the way you solve this, it was the way I'm searching for :). The onCreateViewHolder and onBindViewHolder are the same as mine, so there shouldn't be any problem. I'm just stuck on the PositionalItem - List, I'm using a List of Objects to solve this, but lhs and rhs won't access my .getPosition(). As you said, every class of Vertical Horizontal and Ads have a getter and setter for the Position in them, so I don't understand why this won't work (have a look at my Edit) – KayD Jul 18 '19 at 13:58
  • 1
    Do `Vertical`, `Horizontal`, and `Adfeed` have a supertype (`extends Foo`)? Or do they implement an interface (`implements Foo`)? If not, you can create an interface for them to all implement and then you can use that interface type instead of `Object`. I'll edit my answer with some more detail about this. – Ben P. Jul 18 '19 at 14:53
  • This was the first time working with interfaces like this, learned something new. Thank you sir!! Just one technical question: I'm having a JSON Array called Horizontal, which basically is a horizontal scrollable view, this one needs to get added only one time. So if I understand the code right, for this I need to change the for-loop and also the Horizontal.size() which should be 1 if I need to add only one row to the list. Right? – KayD Jul 18 '19 at 18:55
  • 1
    Yeah, if every horizontal element is supposed to just be a single view in the larger recyclerview, it should be represented as one item (with many sub-items) in the main recyclerview's list. – Ben P. Jul 18 '19 at 19:03
  • Ok, if I'm marking this as correct answer, may I comment in 2 weeks (because I'm on holiday now and ain't got a laptop with me :D) if I have further questions? – KayD Jul 18 '19 at 19:14
  • And I need to say thank you in every way because that was literally one good lession for me! Keep up, the world needs more men like you! – KayD Jul 18 '19 at 19:18
  • 1
    Yeah, feel free. Your best bet is probably to open a new question if a new problem comes up, but you can comment here so that I get a notification :) – Ben P. Jul 18 '19 at 19:27