Search code examples
javaandroidgenericsparcelable

Stuck in generics


I've a base class BaseNewsItem, and 2 derived classes NewsItem and MovieNewsItem. Then, there's a MovieListingFeed class which holds a list of MovieNewsItem and is parcelable. It can parcel this list as and when required by any activity.

protected MovieNewsListingFeed(Parcel in) {
    this.pg = in.readParcelable(PageDetail.class.getClassLoader());
    this.items=new ArrayList<>();
    in.readTypedList(items, MovieNewsItem.CREATOR);
}

I receive an error at the line :

in.readTypedList(items, MovieNewsItem.CREATOR);

Error:

Error:(60, 11) error: method readTypedList in class Parcel cannot be applied to given types;   required: List<T>,Creator<T>   found: ArrayList<CAP#1>,Creator<CAP#2>   reason: inferred type does not conform to equality constraint(s)   inferred: CAP#2   equality constraints(s): CAP#2,CAP#1   where T is a type-variable:   T extends Object declared in method <T>readTypedList(List<T>,Creator<T>) where CAP#1,CAP#2 are fresh type-variables:   CAP#1 extends BaseNewsItem from capture of ? extends BaseNewsItem   CAP#2 extends BaseNewsItem from capture of ? extends BaseNewsItem

I'm a novice at Generics, so when I searched for the above error, I didn't completely get the solutions posted. I only added as I needed to convert ArrayList<MovieNewsItem> to ArrayList<BaseNewsItem>.

Relevant code from MovieNewsItem class:

public static final Creator<? extends BaseNewsItem> CREATOR = new Creator<MovieNewsItem>() {
    public MovieNewsItem createFromParcel(Parcel source) {
        return new MovieNewsItem(source);
    }

    public MovieNewsItem[] newArray(int size) {
        return new MovieNewsItem[size];
    }
};

Let me know if any further code snippets are needed.

UPDATE 1: (readTypedList from Android framework.)

public final <T> void readTypedList(List<T> list, Parcelable.Creator<T> c) {
    int M = list.size();
    int N = readInt();
    int i = 0;
    for (; i < M && i < N; i++) {
        if (readInt() != 0) {
            list.set(i, c.createFromParcel(this));
        } else {
            list.set(i, null);
        }
    }
    for (; i<N; i++) {
        if (readInt() != 0) {
            list.add(c.createFromParcel(this));
        } else {
            list.add(null);
        }
    }
    for (; i<M; i++) {
        list.remove(N);
    }
}

MovieNewsListingFeed.java (Problem file):

public class MovieNewsListingFeed implements Parcelable {

PageDetail pg;

public void setItems(ArrayList<? extends BaseNewsItem> items) {
    this.items = items;
}

ArrayList<? extends BaseNewsItem> items;

public ArrayList<? extends BaseNewsItem> getItemsTemp() {
    return itemsTemp;
}

public void setItemsTemp(ArrayList<? extends BaseNewsItem> itemsTemp) {
    this.itemsTemp = itemsTemp;
}

ArrayList<? extends BaseNewsItem> itemsTemp;

public ArrayList<? extends BaseNewsItem> getItems() {
    return items;
}

public PageDetail getPg() {
    return pg;
}


@Override
public int describeContents() {
    return 0;
}

@Override
public void writeToParcel(Parcel dest, int flags) {
    dest.writeParcelable(this.pg, flags);
    dest.writeTypedList(items);
}

public MovieNewsListingFeed() {
}

protected MovieNewsListingFeed(Parcel in) {
    this.pg = in.readParcelable(PageDetail.class.getClassLoader());
    this.items = new ArrayList<>();
    in.readTypedList(items, MovieNewsItem.CREATOR);
}

public static final Parcelable.Creator<MovieNewsListingFeed> CREATOR = new Parcelable.Creator<MovieNewsListingFeed>() {
    public MovieNewsListingFeed createFromParcel(Parcel source) {
        return new MovieNewsListingFeed(source);
    }

    public MovieNewsListingFeed[] newArray(int size) {
        return new MovieNewsListingFeed[size];
    }
};

}

MovieNewsItem.java:

public class MovieNewsItem extends BaseNewsItem implements Parcelable {
String hl;
String imageid;
String syn;
String id;
String dm;
String tn;
String dl = "";
String sectionHeader;
String upd;
String ud;

private int validListPosition = -1;

public int getValidListPosition() {
    return validListPosition;
}

@Override
public void setTitle(String title) {

}

@Override
public void setId(String id) {

}

@Override
public void setDate(String date) {

}

@Override
public void setNewsType(int newsType) {

}

public String getFormatedDate() {
    return formatedDate;
}

String formatedDate;

int position;
int newSectionType = NewsType.CONTENT_TYPE_NEWS;
String dayHeading;

public int getPosition() {
    return position;
}

public void setPosition(int position) {
    this.position = position;
}


public void setNewSectionType(int newSectionType) {
    this.newSectionType = newSectionType;
}

public String getDayHeading() {
    return dayHeading;
}

public void setDayHeading(String dayHeading) {
    this.dayHeading = dayHeading;
}

@Override
public void setValidListPosition(int listPosition) {

}

public String getWu() {
    return wu;
}

String wu;


public String getHl() {
    return hl;
}

public String getImageid() {
    return imageid;
}

public String getSyn() {
    return syn;
}

public String getId() {
    return id;
}

public String getDm() {
    return dm;
}

public String getTn() {
    return tn;
}

public String getDl() {
    return dl;
}

public String getSectionHeader() {
    return sectionHeader;
}


public MovieNewsItem() {
}

@Override
public String getTitle() {
    return (!TextUtils.isEmpty(getSyn())) ? getSyn() : getHl();
}

@Override
public String getDate() {
    return getFormatedDate();
}

@Override
public int getNewsType() {
    return newSectionType;
}

@Override
public int describeContents() {
    return 0;
}

@Override
public void writeToParcel(Parcel dest, int flags) {
    dest.writeString(this.hl);
    dest.writeString(this.imageid);
    dest.writeString(this.syn);
    dest.writeString(this.id);
    dest.writeString(this.dm);
    dest.writeString(this.tn);
    dest.writeString(this.dl);
    dest.writeString(this.sectionHeader);
    dest.writeString(this.upd);
    dest.writeString(this.ud);
    dest.writeInt(this.position);
    dest.writeInt(this.newSectionType);
    dest.writeString(this.dayHeading);
    dest.writeString(this.wu);
}

protected MovieNewsItem(Parcel in) {
    this.hl = in.readString();
    this.imageid = in.readString();
    this.syn = in.readString();
    this.id = in.readString();
    this.dm = in.readString();
    this.tn = in.readString();
    this.dl = in.readString();
    this.sectionHeader = in.readString();
    this.upd = in.readString();
    this.ud = in.readString();
    this.position = in.readInt();
    this.newSectionType = in.readInt();
    this.dayHeading = in.readString();
    this.wu = in.readString();
}

public static final Creator<? extends BaseNewsItem> CREATOR = new Creator<MovieNewsItem>() {
    public MovieNewsItem createFromParcel(Parcel source) {
        return new MovieNewsItem(source);
    }

    public MovieNewsItem[] newArray(int size) {
        return new MovieNewsItem[size];
    }
};

@Override
public boolean equals(Object obj) {
    MovieNewsItem newsItem = this;
    if (obj instanceof MovieNewsItem) {
        MovieNewsItem oldNewsItem = (MovieNewsItem) obj;
        if (oldNewsItem != null &&
                newsItem.id.equals(oldNewsItem.id)) {
            return true;
        }
    }
    return false;
}

}

BaseNewsItem.java:

 public abstract class BaseNewsItem implements NewsItemType {

}

NewsItemType.java:

public interface NewsItemType extends Parcelable {
String getTitle();
String getId();
String getDate();
int getNewsType();
String getDayHeading();
int getValidListPosition();
int getPosition();

void setTitle(String title);
void setId(String id);
void setDate(String date);
void setNewsType(int newsType);
void setDayHeading(String dayHeading);
void setValidListPosition(int listPosition);
void setPosition(int position);

}


Solution

  • Your issue can be reproduced with this minimal example (replace ? extends Object by ? extends BaseNewsItem and the second list by a Parcelable.Creator and you'll see that the logic is exactly the same):

    public void test() {
      List<? extends Object> a1 = new ArrayList<> ();
      List<? extends Object> a2 = new ArrayList<> ();
      m(a1, a2); //your compilation error here
    }
    public <T> void m(List<T> a1, List<T> a2) { }
    

    The problem is that the generic type of the two lists are unrelated: they both extend the same base class but that's all we know about them - for example a1 may be a List<String> and a2 a List<Integer>.

    However the m method expects the two generic types to be the same - which is not what you are passing.

    So you need to pass lists which have the same generic type, for example:

    public void test() {
      List<SomeBaseClass> a1 = new ArrayList<> ();
      List<SomeBaseClass> a2 = new ArrayList<> ();
      m(a1, a2); //compiles fine
    }
    public <T> void m(List<T> a1, List<T> a2) { }