I need a way around to tackle the following IndexOutOfBoundsException [closed] - java

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 5 years ago.
Improve this question
I have a list. I set cardview card for each item of the list.I have implemented remove on swipe up , but when I remove last item(last card) an IndexOutOfBoundsException is thrown.
My code:
Activity:
public class FirstPage extends Activity
{
RallyRestApi restApi;
private RecyclerView recyclerView;
private CustomAdapter adapter;
private List<MyData> data_list;
private Context mcontext;
String username;
String password;
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_first_page);
mcontext=this;
recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
data_list = new ArrayList<>();
load_data();
recyclerView.setLayoutManager(new LinearLayoutManager(mcontext,LinearLayoutManager.HORIZONTAL,false));
adapter = new CustomAdapter(FirstPage.this,data_list);
recyclerView.setAdapter(adapter);
username=getIntent().getStringExtra("username");
password=getIntent().getStringExtra("password");
ItemTouchHelper.SimpleCallback simpleItemTouchCallback = new ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.UP )
{
#Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target)
{
Toast.makeText(getApplicationContext(), "on Move", Toast.LENGTH_SHORT).show();
return false;
}
#Override
public void onSwiped(final RecyclerView.ViewHolder viewHolder, int swipeDir)
{
Toast.makeText(getApplicationContext(), "Task Status changed to COMPLETE", Toast.LENGTH_LONG).show();
String username = getIntent().getStringExtra("username");
String password = getIntent().getStringExtra("password");
try
{
restApi=new RallyRestApi(new URI("https://rally1.rallydev.com"),username,password);
JsonObject updatedValues = new JsonObject();
updatedValues.addProperty("State", "Completed");
UpdateRequest taskUpdate = new UpdateRequest(data_list.get(viewHolder.getAdapterPosition()).getRef(), updatedValues);
restApi.update(taskUpdate);
data_list.remove(viewHolder.getAdapterPosition());
adapter.notifyDataSetChanged();
}
catch (URISyntaxException | IOException e)
{
e.printStackTrace();
}
}
};
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(simpleItemTouchCallback);
itemTouchHelper.attachToRecyclerView(recyclerView);
}
my Adapter class:
public class CustomAdapter extends RecyclerView.Adapter<CustomAdapter.ViewHolder> {
private Context context;
private List<MyData> my_data;
FirstPage activity;
RallyRestApi restApi;
public CustomAdapter(FirstPage activity, List<MyData> my_data)
{
this.my_data = my_data;
this.activity=activity;
}
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType)
{
View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.card,parent,false);
itemView.setMinimumWidth(parent.getMeasuredWidth());
itemView.setMinimumHeight(parent.getMeasuredHeightAndState());
return new ViewHolder(itemView);
}
#Override
public void onBindViewHolder(final ViewHolder holder, final int position)
{
holder.userstory.setText("User Story: "+my_data.get(position).getUserstory());
holder.tasks.setText("Task: "+my_data.get(position).getTask());
holder.seekBar.setMax(my_data.get(position).getEstimate());
holder.seekBar.setProgress(my_data.get(position).getActual());
holder.actual_estimate.setText(my_data.get(position).getActual()+"/"+my_data.get(position).getEstimate());
holder.seekBar.setOnSeekBarChangeListener(new CircularSeekBar.OnCircularSeekBarChangeListener() {
#Override
public void onProgressChanged(CircularSeekBar circularSeekBar, int progress, boolean fromUser)
{
//IOB exception
holder.seekBar.setProgress(holder.seekBar.getProgress());
holder.actual_estimate.setText(holder.seekBar.getProgress()+"/"+my_data.get(position).getEstimate());
}
#Override
public void onStopTrackingTouch(CircularSeekBar seekBar)
{
try
{
restApi=new RallyRestApi(new URI("https://rally1.rallydev.com"),activity.username,activity.password);
JsonObject updatedValues = new JsonObject();
updatedValues.addProperty("Actuals", holder.seekBar.getProgress());
UpdateRequest taskUpdate = new UpdateRequest(my_data.get(position).getRef(), updatedValues);
restApi.update(taskUpdate);
holder.actual_estimate.setText(holder.seekBar.getProgress()+"/"+my_data.get(position).getEstimate());
holder.seekBar.setProgress(holder.seekBar.getProgress());
}
catch (URISyntaxException | IOException e)
{
e.printStackTrace();
}
}
#Override
public void onStartTrackingTouch(CircularSeekBar seekBar)
{
}
});
holder.seekBar.setProgress(holder.seekBar.getProgress());
holder.actual_estimate.setText(holder.seekBar.getProgress()+"/"+my_data.get(position).getEstimate());
}
#Override
public int getItemCount()
{
return my_data.size();
}
public class ViewHolder extends RecyclerView.ViewHolder
{
public TextView userstory,tasks,actual_estimate;
public CircularSeekBar seekBar;
//
public ViewHolder(View itemView)
{
super(itemView);
userstory=(TextView) itemView.findViewById(R.id.tvUserStory);
tasks=(TextView) itemView.findViewById(R.id.tvTask);
seekBar=(CircularSeekBar) itemView.findViewById(R.id.circularSeekBar1);
actual_estimate=(TextView) itemView.findViewById(R.id.tvactuals_estimate);
}
}
}
ErrorLog:
java.lang.IndexOutOfBoundsException: Invalid index 2, size is 2
at
java.util.ArrayList.throwIndexOutOfBoundsException(ArrayList.java:255)
at java.util.ArrayList.get(ArrayList.java:308)
at
com.bmc.apetkar.akshay_rallyrest.CustomAdapter$1.onProgressChanged(CustomAdapter.java:68)
at
com.circularseekbar.CircularSeekBar.setProgress(CircularSeekBar.java:530)
at
com.bmc.apetkar.akshay_rallyrest.CustomAdapter$1.onProgressChanged(CustomAdapter.java:67)
at
com.circularseekbar.CircularSeekBar.setProgress(CircularSeekBar.java:530)
at
com.bmc.apetkar.akshay_rallyrest.CustomAdapter.onBindViewHolder(CustomAdapter.java:61)
at
com.bmc.apetkar.akshay_rallyrest.CustomAdapter.onBindViewHolder(CustomAdapter.java:32)
at
android.support.v7.widget.RecyclerView$Adapter.onBindViewHolder(RecyclerView.java:5277)
at
android.support.v7.widget.RecyclerView$Adapter.bindViewHolder(RecyclerView.java:5310)
at
android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:4568)
at
android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:4461)
at
android.support.v7.widget.LinearLayoutManager$LayoutState.next(LinearLayoutManager.java:1962)
at
android.support.v7.widget.LinearLayoutManager.layoutChunk(LinearLayoutManager.java:1371)
at
android.support.v7.widget.LinearLayoutManager.fill(LinearLayoutManager.java:1334)
at
android.support.v7.widget.LinearLayoutManager.onLayoutChildren(LinearLayoutManager.java:563)
at
android.support.v7.widget.RecyclerView.dispatchLayout(RecyclerView.java:2847)
at
android.support.v7.widget.RecyclerView.onLayout(RecyclerView.java:3145)
at android.view.View.layout(View.java:16630)
at android.view.ViewGroup.layout(ViewGroup.java:5437)
at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1743)
at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1586)
at android.widget.LinearLayout.onLayout(LinearLayout.java:1495)
at android.view.View.layout(View.java:16630)
at android.view.ViewGroup.layout(ViewGroup.java:5437)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:336)
at android.widget.FrameLayout.onLayout(FrameLayout.java:273)
at android.view.View.layout(View.java:16630)
at android.view.ViewGroup.layout(ViewGroup.java:5437)
at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1743)
at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1586)
at android.widget.LinearLayout.onLayout(LinearLayout.java:1495)
at android.view.View.layout(View.java:16630)
at android.view.ViewGroup.layout(ViewGroup.java:5437)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:336)
at android.widget.FrameLayout.onLayout(FrameLayout.java:273)
at
com.android.internal.policy.PhoneWindow$DecorView.onLayout(PhoneWindow.java:2678)
at android.view.View.layout(View.java:16630)
at android.view.ViewGroup.layout(ViewGroup.java:5437)
at android.view.ViewRootImpl.performLayout(ViewRootImpl.java:2171)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1931)
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1107)
at
android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:6013)
at
android.view.Choreographer$CallbackRecord.run(Choreographer.java:858)
at android.view.Choreographer.doCallbacks(Choreographer.java:670)
at android.view.Choreographer.doFrame(Choreographer.java:606)
at
android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:844)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:148)
at android.app.ActivityThread.main(ActivityThread.java:5417)
at java.lang.reflect.Method.invoke(Native Method)
at
com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
I know it might be a duplicate of IndexOutOfBoundsException but I referred it and did not understand where exactly I need to change to position-1 as list starts from 0;

The exception originates from my_data.get(position) in your onProgressChanged() listener.
This listener is called asynchronously, when progress changes, but it refers to the original position provided, when you perform the onBindViewHolder().
So when at time X you do the onBindViewHolder(), position with value 2 is valid (if there are at least 3 entries in the list). The listener will keep this value 2 and hold on to it.
Now, if you delete items and only have 2 items left, position = 2 is no longer valid, but the listener still keeps that value and when it is called, it tries to access my_data at position = 2, which has now become invalid.
To fix this, you will have to make the listener hold on to the actual data, not the position. You can do this like so:
public void onBindViewHolder(final ViewHolder holder, final int position)
{
final SomeClass data = my_data.get(position);
holder.seekBar.setOnSeekBarChangeListener(new CircularSeekBar.OnCircularSeekBarChangeListener() {
#Override
public void onProgressChanged(CircularSeekBar circularSeekBar, int progress, boolean fromUser)
{
holder.seekBar.setProgress(holder.seekBar.getProgress());
holder.actual_estimate.setText(holder.seekBar.getProgress()+"/" + data.getEstimate());
}

Before removing an item , you can check if the list is already empty.
if(!data_list.isEmpty()){
data_list.remove(viewHolder.getAdapterPosition());
adapter.notifyDataSetChanged();
}

Related

Recyclerview in Android Studio Error - "null object reference"

I'm new in android and I have just screwed something.
I've used recycleView template and it worked fine but I wanted to change type of my object from string to "TagsManagerObject" which contains
private String tagName;
private String gender;
private String mAgeMin;
private String mAgeMax;
private String mDistance;
since then I'm stuck with this error:
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.tinderapp, PID: 10921
java.lang.NullPointerException: Attempt to invoke interface method 'void com.example.tinderapp.Tags.TagsManagerAdapter$ItemClickListener.onDeleteClick(int)' on a null object reference
at com.example.tinderapp.Tags.TagsManagerAdapter$ViewHolder$1.onClick(TagsManagerAdapter.java:78)
at android.view.View.performClick(View.java:6614)
at android.view.View.performClickInternal(View.java:6587)
at android.view.View.access$3100(View.java:787)
at android.view.View$PerformClick.run(View.java:26122)
at android.os.Handler.handleCallback(Handler.java:873)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:201)
at android.app.ActivityThread.main(ActivityThread.java:6831)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:547)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:927)
My adapter looks like:
public class TagsManagerAdapter extends RecyclerView.Adapter<TagsManagerAdapter.ViewHolder>{
private LayoutInflater mInflater;
private ImageView mDeleteImage;
private List<TagsManagerObject> mTagsManagerObject;
private ItemClickListener mItemClickListener;
// data is passed into the constructor
public TagsManagerAdapter(Context context,List<TagsManagerObject> TagsManagerObject) {
this.mInflater = LayoutInflater.from(context);
this.mTagsManagerObject = TagsManagerObject;
}
// inflates the row layout from xml when needed
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = mInflater.inflate(R.layout.item_tags_manager, parent, false);
return new ViewHolder(view);
}
#Override
public void onBindViewHolder(#NonNull ViewHolder holder, int position) {
// String tag = TagsManagerObject.get(position);
holder.tagName.setText("#"+mTagsManagerObject.get(position).getTagName());
holder.gender.setText(mTagsManagerObject.get(position).getGender());
holder.distance.setText(mTagsManagerObject.get(position).getmDistance());
holder.tagAge.setText(mTagsManagerObject.get(position).getmAgeMin() + "-" + mTagsManagerObject.get(position).getmAgeMax());
}
// binds the data to the TextView in each row
// total number of rows
#Override
public int getItemCount() {
return mTagsManagerObject.size();
}
public void setClickListener(ItemClickListener mItemClickListener) {
this.mItemClickListener = mItemClickListener;
}
// stores and recycles views as they are scrolled off screen
public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
private TextView tagName,gender,tagAge,distance;
ViewHolder(View itemView) {
super(itemView);
tagName= itemView.findViewById(R.id.tag);
gender = itemView.findViewById(R.id.tag_gender);
distance = itemView.findViewById(R.id.tag_distance);
tagAge = itemView.findViewById(R.id.tag_age);
mDeleteImage = itemView.findViewById(R.id.tag_delete);
itemView.setOnClickListener(this);
mDeleteImage.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
int position = getAdapterPosition();
if(position!=RecyclerView.NO_POSITION){
mItemClickListener.onDeleteClick(position); <<--- HERE IS THE ERROR
}
}
});
}
#Override
public void onClick(View view) {
if (mItemClickListener != null) mItemClickListener.onItemClick(view, getAdapterPosition());
}
}
// convenience method for getting data at click position
String getItem(int id) {
return mTagsManagerObject.get(id).toString();
}
// allows clicks events to be caught
// parent activity will implement this method to respond to click events
public interface ItemClickListener {
void onItemClick(View view, int position);
void onDeleteClick(int position);
}
}
So when i click delete button it crashes. Does anyone knows why?
Thanks.
You got this exception as you you call onDeleteClick(position) on an object that is null, so you have to set a value to the mItemClickListener before calling this method onDeleteClick(position)
In your code, you have to call setClickListener() of your adapter in your activity/fragment that uses this adapter.
You need to do two things.
First, you need to check mItemClickListener for null. Doing this will prevent from null pointer exception.
mDeleteImage.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
int position = getAdapterPosition();
if(position!=RecyclerView.NO_POSITION){
if(mItemClickListener !=null){
mItemClickListener.onDeleteClick(position); <<--- HERE IS THE ERROR
}
}
}
}
);
And the second thing is you should set/call the setClickListener for the Activity/Fragment, using the instance of RecyclerviewAdapter.
Doing this will make the Activity/Fragment implement your ItemClickListener and onDeleteClick() method.
adapter.setClickListener(this);
Or, you can also use the Anonymous class here.
Thanks guys for help. The problem was that in my acvitivy I have:
1.Created adapter with empty taglist
2.set onclicklistener
adapter.setOnItemClickListener(new TagsManagerAdapter.OnItemClickListener() {
#Override
public void onItemClick(int position) {
}
#Override
public void onDeleteClick(int position) {
System.out.println("works?");
removeItem(position);
}
});
And then add items.
Instead
adapter.notifyDataSetChanged();
Ive used:
adapter = new TagsManagerAdapter(myTagsList);
and that caused problem, that it coudnt find my listener

How to use buttons inside a recyclerview? [duplicate]

This question already has answers here:
android.content.res.Resources$NotFoundException: String resource ID #0x0
(8 answers)
Unfortunately MyApp has stopped. How can I solve this?
(23 answers)
Closed 2 years ago.
I have this restaurant app and I want to make a checkout looking like FoodPanda's (if you have the layout in mind). The recyclerview items should contain a textview containing the quantity of one product, surrounded by two buttons for decrement and increment that quantity, then two textviews with product name and the price.
I've tried this so far in my adapter class:
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {
private ArrayList<Product> product;
private OnItemClickListener mListener;
public interface OnItemClickListener {
void decrementQ(int position);
void incrementQ(int position);
}
public void setOnItemClickListener(OnItemClickListener listener){
mListener = listener;
}
public static class MyViewHolder extends ViewHolder {
public ImageView incrementQty, decrementQty;
public TextView productName, price, qty;
public MyViewHolder(View itemView, final OnItemClickListener listener) {
super(itemView);
incrementQty = itemView.findViewById(R.id.incrementProduct);
decrementQty = itemView.findViewById(R.id.decrementProduct);
qty = itemView.findViewById(R.id.qtyProduct);
productName = itemView.findViewById(R.id.productName);
price = itemView.findViewById(R.id.productPrice);
decrementQty.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if(listener != null){
int position = getAdapterPosition();
if(position != RecyclerView.NO_POSITION)
listener.decrementQ(position);
}
}
});
incrementQty.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if(listener != null){
int position = getAdapterPosition();
if(position != RecyclerView.NO_POSITION)
listener.incrementQ(position);
}
}
});
}
}
// Provide a suitable constructor (depends on the kind of dataset)
public MyAdapter(ArrayList<Product> thisProduct) {
product = thisProduct;
}
// Create new views (invoked by the layout manager)
#Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.products, parent, false);
MyViewHolder mv = new MyViewHolder(v, mListener);
return mv;
}
// Replace the contents of a view (invoked by the layout manager)
#Override
public void onBindViewHolder(MyViewHolder holder, int position) {
// - get element from your dataset at this position
// - replace the contents of the view with that element
Product currentProduct = product.get(position);
holder.decrementQty.setImageResource(currentProduct.getDecrementSource());
holder.qty.setText(currentProduct.getQty());
holder.incrementQty.setImageResource(currentProduct.getIncrementSource());
holder.productName.setText(currentProduct.getProductName());
holder.price.setText(currentProduct.getProductPrice());
}
// Return the size of your dataset (invoked by the layout manager)
#Override
public int getItemCount() {
return product.size();
}
}
And this is my checkout activity class:
public class checkout extends AppCompatActivity {
private ArrayList<Product> product;
private RecyclerView recyclerView;
private MyAdapter mAdapter;
private RecyclerView.LayoutManager layoutManager;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_checkout);
product = new ArrayList<>();
product.add(new Product(R.drawable.minus, "1", R.drawable.plus, "Buttered Noodles", "15 RON"));
product.add(new Product(R.drawable.minus, "2", R.drawable.plus, "Teryiaki","20 RON"));
product.add(new Product(R.drawable.minus, "3", R.drawable.plus, "ginger", "3 RON"));
buildCheckout();
}
public void removeItem(int position) {
product.remove(position);
mAdapter.notifyItemRemoved(position);
}
public void buildCheckout(){
recyclerView = findViewById(R.id.checkoutList);
recyclerView.setHasFixedSize(true);
layoutManager = new LinearLayoutManager(this);
mAdapter = new MyAdapter(product);
recyclerView.setLayoutManager(layoutManager);
recyclerView.setAdapter(mAdapter);
mAdapter.setOnItemClickListener(new MyAdapter.OnItemClickListener() {
#Override
public void decrementQ(int position) {
TextView q = recyclerView.findViewHolderForAdapterPosition(position).itemView.findViewById(R.id.qtyProduct);
int qty = Integer.parseInt((String) q.getText());
qty--;
if(qty < 1)
removeItem(position);
else
q.setText(qty);
}
#Override
public void incrementQ(int position) {
TextView q = recyclerView.findViewHolderForAdapterPosition(position).itemView.findViewById(R.id.qtyProduct);
int qty = Integer.parseInt((String) q.getText());
qty++;
q.setText(qty);
}
});
}
}
The problem is, when I try to interact with the increment and decrement buttons, the app keeps stoping and I don't know what should I do next or what I did wrong.
you are setting Int value instead of String value to TextView and you will get ResourceNotFoundException:
#Override
public void incrementQ(int position) {
TextView q = recyclerView.findViewHolderForAdapterPosition(position).itemView.findViewById(R.id.qtyProduct);
int qty = Integer.parseInt((String) q.getText());
qty++;
q.setText(qty);//ERROR change to q.setText("" + qty);
}
you have same problem in decrementQ

Removing items from RecyclerView.Adapter causes OutOfBounds exception

I'm trying to build a simple list using RecyclerView, that would allow me to add/remove items.
I'm able to remove the items, but it seems like the position is not being 're-calculated' after I remove an item.
For example, I have 20 items in the list, if I remove the last item, the position is 19 (as it should be). The item gets removed from the list, but when I click to remove the last item again, the position is still 19, it should be 18:
D/RecycleViewTest: Remove at Position: 19
D/RecycleViewTest: Remove at Position: 19
Which results in the following exception:
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.recycleviewtest, PID: 1763
java.lang.IndexOutOfBoundsException: Index: 19, Size: 19
at java.util.ArrayList.remove(ArrayList.java:477)
at com.example.recycleviewtest.MainActivity$1.onRemoveClick(MainActivity.java:45)
at com.example.recycleviewtest.SimpleViewHolder$2.onClick(SimpleViewHolder.java:42)
at android.view.View.performClick(View.java:6205)
at android.view.View$PerformClick.run(View.java:23653)
at android.os.Handler.handleCallback(Handler.java:751)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:154)
at android.app.ActivityThread.main(ActivityThread.java:6682)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1520)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1410)
I'm removing the items the following way:
itemList.remove( position );
adapter.notifyItemRemoved( position );
Do I need to make additional call to some kind of function, so that the list gets "re-calculated"?
Here is the MainActivity:
public class MainActivity extends AppCompatActivity
{
List<SimpleViewModel> itemList;
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
itemList = generateSimpleList();
final SimpleAdapter adapter = new SimpleAdapter(itemList);
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.simple_recyclerview);
recyclerView.setHasFixedSize(true);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setItemAnimator( new DefaultItemAnimator() );
recyclerView.setAdapter(adapter);
adapter.setOnItemClickListener(new SimpleAdapter.onItemClickListener() {
#Override
public void onItemClick(int position)
{
Log.d( "RecycleViewTest", "Click at Position: " + position );
adapter.notifyItemChanged( position );
}
#Override
public void onRemoveClick( int position )
{
Log.d( "RecycleViewTest", "Remove at Position: " + position );
itemList.remove( position );
adapter.notifyItemRemoved( position );
}
});
}
private List<SimpleViewModel> generateSimpleList()
{
List<SimpleViewModel> simpleViewModelList = new ArrayList<>();
for (int i = 0; i < 20; i++) {
simpleViewModelList.add(new SimpleViewModel(String.format(Locale.US, "This is item %d", i)));
}
return simpleViewModelList;
}
}
This is my RecyclerView.Adapter (SimpleAdapter):
public class SimpleAdapter extends RecyclerView.Adapter {
private List<SimpleViewModel> models = new ArrayList<>();
private onItemClickListener mListener;
public SimpleAdapter(final List<SimpleViewModel> viewModels)
{
if (viewModels != null) {
this.models.addAll(viewModels);
}
}
public void setOnItemClickListener( onItemClickListener listener )
{
mListener = listener;
}
public interface onItemClickListener
{
void onItemClick( int position );
void onRemoveClick( int position);
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(final ViewGroup parent, final int viewType) {
final View view = LayoutInflater.from(parent.getContext()).inflate(viewType, parent, false);
return new SimpleViewHolder(view, mListener);
}
#Override
public void onBindViewHolder(final RecyclerView.ViewHolder holder, final int position) {
((SimpleViewHolder) holder).bindData(models.get(position));
}
#Override
public int getItemCount() {
return models.size();
}
#Override
public int getItemViewType(final int position) {
return R.layout.item;
}
}
Here is my RecyclerView.ViewHolder (SimpleViewHolder):
public class SimpleViewHolder extends RecyclerView.ViewHolder {
private TextView simpleTextView;
private ImageView imgRemove;
public SimpleViewHolder(final View itemView, final SimpleAdapter.onItemClickListener listener) {
super(itemView);
simpleTextView = (TextView) itemView.findViewById(R.id.simple_text);
imgRemove = (ImageView) itemView.findViewById(R.id.imgRemove);
itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
if( listener != null )
{
int position = getAdapterPosition();
if( position != RecyclerView.NO_POSITION )
{
listener.onItemClick( position );
}
}
}
});
imgRemove.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
if( listener != null )
{
int position = getAdapterPosition();
if( position != RecyclerView.NO_POSITION )
{
listener.onRemoveClick( position );
}
}
}
});
}
public void bindData(final SimpleViewModel viewModel) {
simpleTextView.setText(viewModel.getSimpleText() );
}
}
Any hints please on where am I going wrong with this? Thank you!
UPDATE:
I just realized that even though the animation for removal is 'played', no actual items are removed, they simply 're-appear'..
The problem is in your Adapter's constructor:
private List<SimpleViewModel> models = new ArrayList<>();
private onItemClickListener mListener;
public SimpleAdapter(final List<SimpleViewModel> viewModels)
{
if (viewModels != null) {
this.models.addAll(viewModels);
}
}
The list you're passing into this constructor isn't retained by the adapter. Instead, the contents of the list are copied into the adapter's own, separate and different list.
That means that later on, when you execute
#Override
public void onRemoveClick( int position )
{
Log.d( "RecycleViewTest", "Remove at Position: " + position );
itemList.remove( position );
adapter.notifyItemRemoved( position );
}
The item is removed from itemList, but it is not removed from the list in the adapter.
You must either remove the item from the adapter's copied list, or you must change the adapter so that it shares the same list as the activity. Probably the easiest thing would be to change the adapter:
private List<SimpleViewModel> models = new ArrayList<>();
private onItemClickListener mListener;
public SimpleAdapter(final List<SimpleViewModel> viewModels)
{
if (viewModels != null) {
this.models = viewModels; // instead of copying the contents
}
}
Use this after remove the item
mRecyclerView.getRecycledViewPool().clear();
mAdapter.notifyDataSetChanged();
After digging around, it looks like other people have been using an additional method called notifyItemRangeChanged which sounds like what is going on. So after doing itemList.remove(position);
try also doing
adapter.notifyItemRemoved( position );
adapter.notifyItemRangeChanged(position, itemList.size());
The getAdapterPosition() return a range positions of 1 - n and your list has a range positions of 0 - n.
Try as follow
itemList.remove( position - 1 ); //to normalize
adapter.notifyItemRemoved( position );

RecyclerView using item from a Realm database crashes

I have a fragment that is suppose to show a list of Game objects. When the fragment starts, I read all the Game objects from the database and add them to a List. It all works fine until the size of the list reaches 12. If it does, the App crashes even before the fragment is shown. If the list is bigger than 13 then it only crashes if I scroll all the way down and try and show the Games that are below.
Any ideas on what could cause this? I don´t really believe the RecyclerView is the problem.
Here are my classes. Please let me know if you need more information.
Any help would be very welcome.
My Game class
public class Game extends RealmObject{
#PrimaryKey
private long id;
private RealmList<Coordinates> mGameCoordinates;
private RealmList<Coordinates> mDrinkingStopsCoordinates;
private RealmList<Team> mTeams;
private String mDate;
private boolean mIsGameFinished;
private long mStartTime;
private long mFinishTime;
private long mTimeTaken;
public Game(RealmList<Team> teams, String date, long id) {
mTeams = teams;
mDate = date;
this.id = id;
}
public int getGameSize(){
int size = mTeams.size();
return size;
}
public String getDate() {
return mDate;
}
public void setDate(String date) {
mDate = date;
}
public RealmList<Team> getTeams() {
return mTeams;
}
public void setTeams(RealmList<Team> teams) {
mTeams = teams;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
//Default empty constructor - must be present
public Game(){
}
public RealmList<Coordinates> getDrinkingStopsCoordinates() {
return mDrinkingStopsCoordinates;
}
public void setDrinkingStopsCoordinates(RealmList<Coordinates> drinkingStopsCoordinates) {
mDrinkingStopsCoordinates = drinkingStopsCoordinates;
}
public RealmList<Coordinates> getGameCoordinates() {
return mGameCoordinates;
}
public void setGameCoordinates(RealmList<Coordinates> gameCoordinates) {
mGameCoordinates = gameCoordinates;
}
public long getStartTime() {
return mStartTime;
}
public void setStartTime(long startTime) {
mStartTime = startTime;
}
public long getFinishTime() {
return mFinishTime;
}
public void setFinishTime(long finishTime) {
mFinishTime = finishTime;
}
public long getTimeTaken() {
mTimeTaken = mFinishTime - mStartTime;
return mTimeTaken;
}
public void setTimeTaken(long timeTaken) {
mTimeTaken = timeTaken;
}
public boolean isGameFinished() {
return mIsGameFinished;
}
public void setGameFinished(boolean gameFinished) {
mIsGameFinished = gameFinished;
}
}
My Fragment
public class OldGameFragment extends Fragment {
private static final String TAG = "OldGameFragment";
private RecyclerView mRecyclerView;
private TextView mTextViewPlayers;
private TextView mTextViewDate;
private RecyclerAdapter mRecyclerAdapter;
private Toolbar myToolbar;
private String mStringReadyToShow;
private List<Game> mGames;
private RealmResults<Game> playerRealmResults;
private List<Game> mGameRealmList = new ArrayList<>();
private Realm mRealm;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_old_game, container,false);
//DB
mRealm = Realm.getDefaultInstance();
//Singleton
ActivityStateSingleton activityStateSingleton = ActivityStateSingleton.getInstance();
activityStateSingleton.setActivityFlag(1);
mRecyclerView = (RecyclerView)v.findViewById(R.id.recyclerview_player_list);
mRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
//Toolbar
myToolbar = (Toolbar)v.findViewById(R.id.my_toolbar_old_game);
myToolbar.setTitle(R.string.drawer_old_game);
myToolbar.setTitleTextColor(0xffffffff);
((AppCompatActivity)getActivity()).setSupportActionBar(myToolbar);
((AppCompatActivity)getActivity()).getSupportActionBar().setDisplayHomeAsUpEnabled(true);
readFromDB();
return v;
}
//Fragment
public static OldGameFragment newInstance(){
return new OldGameFragment();
}
public void readFromDB(){
RealmResults<Game> playerRealmResults = mRealm.where(Game.class).findAll();
int count = 0;
for(Game game : playerRealmResults){
mGameRealmList.add(game);
count++;
Log.d(TAG, "Count " + count);
}
mRecyclerAdapter = new RecyclerAdapter(mGameRealmList);
mRecyclerView.setAdapter(mRecyclerAdapter);
mRecyclerView.addItemDecoration(new SimpleDividerItemDecoration(getContext()));
mRecyclerAdapter.setOnItemClickListener(new OnItemClickListener() {
#Override
public void onItemClick(long id) {
Intent intent = GameOverviewActivity.toGameOverview(getActivity(), id);
startActivity(intent);
}
});
mRecyclerAdapter.notifyDataSetChanged();
}
/*RecyclerView*/
public class RecyclerHolder extends RecyclerView.ViewHolder {
private Game mGame;
private SwipeRevealLayout mSwipeRevealLayout;
private View mDeleteView;
public RecyclerHolder(View itemView) {
super(itemView);
mTextViewPlayers = (TextView)itemView.findViewById(R.id.list_item_player_teams);
mTextViewDate = (TextView)itemView.findViewById(R.id.list_item_date);
mSwipeRevealLayout = (SwipeRevealLayout)itemView.findViewById(R.id.swipe_layout);
mDeleteView = (View) itemView.findViewById(R.id.delete_layout);
}
public void bindPlayer(Game game){
mGame = game;
String name = game.getTeams().get(0).getTeamName();
int num = game.getTeams().get(0).getPlayers().size();
String text = name + "(" + num + ")";
for(Team t : game.getTeams()){
if(!text.contains(t.getTeamName())){
name = t.getTeamName();
num = t.getPlayers().size();
text += " vs. " + name + "(" + num + ")";
}
mDeleteView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
AlertDialog.Builder builder = new AlertDialog.Builder(new ContextThemeWrapper(getContext(),
R.style.AlertDialogCustom));
builder.setTitle(R.string.dialog_alert_title).setMessage(R.string.delete_game)
.setPositiveButton("Ok", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialogInterface, int i) {
mRecyclerAdapter.dismissGame(getAdapterPosition(), mGames.get(getAdapterPosition()).getId());
}
}).setNegativeButton("Nein", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialogInterface, int i) {
Toast.makeText(getContext(), "No", Toast.LENGTH_SHORT).show();
}
});
AlertDialog dialog = builder.create();
dialog.show();
//So it won´t show the white borders around the round egdges
dialog.getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
}
});
}
mTextViewPlayers.setText(text);
String date = getResources().getString(R.string.date) + game.getDate();
String space = " ";
mTextViewDate.setText(space + date);
}
}
public class RecyclerAdapter extends RecyclerView.Adapter<RecyclerHolder>{
/* This object helps you save/restore the open/close state of each view*/
private final ViewBinderHelper mViewBinderHelper = new ViewBinderHelper();
private OnItemClickListener mOnItemClickListener;
public RecyclerAdapter(List<Game> realmResults){
mGames = realmResults;
/*To only open one row at the time*/
mViewBinderHelper.setOpenOnlyOne(true);
}
#Override
public RecyclerHolder onCreateViewHolder(ViewGroup parent, int viewType) {
LayoutInflater layoutInflater = LayoutInflater.from(getActivity());
View v = layoutInflater.inflate(
R.layout.list_item_game_rows, parent, false);
return new RecyclerHolder(v);
}
#Override
public void onBindViewHolder(RecyclerHolder holder, final int position) {
final Game game = mGames.get(position);
final String data = mGames.get(position).toString();
// You need to provide a String id which uniquely defines the data object.
mViewBinderHelper.bind(holder.mSwipeRevealLayout, data);
holder.bindPlayer(game);
View.OnClickListener listener = new View.OnClickListener() {
#Override
public void onClick(View v) {
mOnItemClickListener.onItemClick(game.getId());
}
};
mTextViewDate.setOnClickListener(listener);
mTextViewPlayers.setOnClickListener(listener);
}
#Override
public int getItemCount() {
return mGames.size();
}
/*Dismiss Game*/
public void dismissGame(int position, final long id){
mGames.remove(position);
this.notifyItemRemoved(position);
mRealm.executeTransaction(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
RealmResults<Game> gameRealmResults = mRealm.where(Game.class).equalTo("id", id).findAll();
gameRealmResults.deleteAllFromRealm();
}
});
}
public OnItemClickListener getOnItemClickListener(){
return mOnItemClickListener;
}
public void setOnItemClickListener(OnItemClickListener onItemClickListener){
this.mOnItemClickListener = onItemClickListener;
}
}
}
EDIT:
Here is the Log:
FATAL EXCEPTION: main Process: bosseln.swenden.de.bosseln, PID: 23424
Theme: themes:{default=overlay:com.init.designloper.init_v001, iconPack:com.init.designloper.init_v001, fontPkg:com.init.designloper.init_v001, com.android.systemui=overlay:com.init.designloper.init_v001, com.android.systemui.navbar=overlay:com.init.designloper.init_v001}
java.lang.ArrayIndexOutOfBoundsException: rowIndex > available rows: 0 > 0
at io.realm.internal.LinkView.nativeGetTargetRowIndex(Native Method)
at io.realm.internal.LinkView.getTargetRowIndex(LinkView.java:81)
at io.realm.RealmList.get(RealmList.java:448)
at bosseln.swenden.de.bosseln.fragments.OldGameFragment$RecyclerHolder.bindPlayer(OldGameFragment.java:149)
at bosseln.swenden.de.bosseln.fragments.OldGameFragment$RecyclerAdapter.onBindViewHolder(OldGameFragment.java:223)
at bosseln.swenden.de.bosseln.fragments.OldGameFragment$RecyclerAdapter.onBindViewHolder(OldGameFragment.java:195)
at android.support.v7.widget.RecyclerView$Adapter.onBindViewHolder(RecyclerView.java:6279)
at android.support.v7.widget.RecyclerView$Adapter.bindViewHolder(RecyclerView.java:6312)
at android.support.v7.widget.RecyclerView$Recycler.tryBindViewHolderByDeadline(RecyclerView.java:5258)
at android.support.v7.widget.RecyclerView$Recycler.tryGetViewHolderForPositionByDeadline(RecyclerView.java:5521)
at android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:5363)
at android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:5359)
at android.support.v7.widget.LinearLayoutManager$LayoutState.next(LinearLayoutManager.java:2141)
at android.support.v7.widget.LinearLayoutManager.layoutChunk(LinearLayoutManager.java:1525)
at android.support.v7.widget.LinearLayoutManager.fill(LinearLayoutManager.java:1488)
at android.support.v7.widget.LinearLayoutManager.onLayoutChildren(LinearLayoutManager.java:585)
at android.support.v7.widget.RecyclerView.dispatchLayoutStep2(RecyclerView.java:3506)
at android.support.v7.widget.RecyclerView.dispatchLayout(RecyclerView.java:3254)
at android.support.v7.widget.RecyclerView.onLayout(RecyclerView.java:3767)
at android.view.View.layout(View.java:16639)
at android.view.ViewGroup.layout(ViewGroup.java:5437)
at android.widget.RelativeLayout.onLayout(RelativeLayout.java:1079)
at android.view.View.layout(View.java:16639)
at android.view.ViewGroup.layout(ViewGroup.java:5437)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:336)
at android.widget.FrameLayout.onLayout(FrameLayout.java:273)
at android.view.View.layout(View.java:16639)
at android.view.ViewGroup.layout(ViewGroup.java:5437)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:336)
at android.widget.FrameLayout.onLayout(FrameLayout.java:273)
at android.view.View.layout(View.java:16639)
at android.view.ViewGroup.layout(ViewGroup.java:5437)
at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1735)
at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1579)
at android.widget.LinearLayout.onLayout(LinearLayout.java:1488)
at android.view.View.layout(View.java:16639)
at android.view.ViewGroup.layout(ViewGroup.java:5437)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:336)
at android.widget.FrameLayout.onLayout(FrameLayout.java:273)
at android.view.View.layout(View.java:16639)
at android.view.ViewGroup.layout(ViewGroup.java:5437)
at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1735)
at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1579)
at android.widget.LinearLayout.onLayout(LinearLayout.java:1488)
at android.view.View.layout(View.java:16639)
at android.view.ViewGroup.layout(ViewGroup.java:5437)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:336)
at android.widget.FrameLayout.onLayout(FrameLayout.java:273)
at com.android.internal.policy.PhoneWindow$DecorView.onLayout(PhoneWindow.java:2934)
at android.view.View.layout(View.java:16639)
at android.view.ViewGroup.layout(ViewGroup.java:5437)
at android.
Please check the rows in the database, I think there may be some null value you are getting at row number 13.

ClassCastException in Android RecyclerAdapter

I'm having a very difficult time understanding why I'm getting a ClassCastException in my RecyclerAdapter class. I have set it up to accept two different types of views, one for regular feed items and the other for native advertisements. What is going wrong?
In my main activity, I call my constructor as follows:
feedItems = new ArrayList<>();
List<Feed> adItems = new ArrayList<>();
recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
marketFeedRecyclerAdapter = new MarketFeedRecyclerAdapter(this, feedItems, new ImageLoader(new FeedItemFileCache(this)), adItems);
I get a complaint at this line in my RecyclerAdapter class, with the stack trace to follow:
bindAdItemView((AdViewHolder) viewHolder);
My stack trace:
java.lang.ClassCastException:
com.elgami.utility.LoadingRowRecyclerAdapter$LoadingViewHolder cannot be cast to com.elgami.market.MarketFeedRecyclerAdapter$AdViewHolder
at com.elgami.market.MarketFeedRecyclerAdapter.onBindViewHolder(MarketFeedRecyclerAdapter.java:80)
at android.support.v7.widget.RecyclerView$Adapter.onBindViewHolder(RecyclerView.java:5768)
at android.support.v7.widget.RecyclerView$Adapter.bindViewHolder(RecyclerView.java:5801)
at android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:5037)
at android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:4913)
at android.support.v7.widget.LinearLayoutManager$LayoutState.next(LinearLayoutManager.java:2029)
at android.support.v7.widget.LinearLayoutManager.layoutChunk(LinearLayoutManager.java:1414)
at android.support.v7.widget.LinearLayoutManager.fill(LinearLayoutManager.java:1377)
at android.support.v7.widget.LinearLayoutManager.onLayoutChildren(LinearLayoutManager.java:578)
at android.support.v7.widget.RecyclerView.dispatchLayoutStep2(RecyclerView.java:3260)
at android.support.v7.widget.RecyclerView.dispatchLayout(RecyclerView.java:3069)
at android.support.v7.widget.RecyclerView.onLayout(RecyclerView.java:3518)
at android.view.View.layout(View.java:16636)
at android.view.ViewGroup.layout(ViewGroup.java:5437)
at android.support.v4.widget.SwipeRefreshLayout.onLayout(SwipeRefreshLayout.java:598)
at android.view.View.layout(View.java:16636)
at android.view.ViewGroup.layout(ViewGroup.java:5437)
at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1743)
at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1586)
at android.widget.LinearLayout.onLayout(LinearLayout.java:1495)
at android.view.View.layout(View.java:16636)
at android.widget.RelativeLayout.onLayout(RelativeLayout.java:1079)
at android.view.View.layout(View.java:16636)
at android.view.ViewGroup.layout(ViewGroup.java:5437)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:336)
at android.widget.FrameLayout.onLayout(FrameLayout.java:273)
at android.view.View.layout(View.java:16636)
at android.view.ViewGroup.layout(ViewGroup.java:5437)
at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1743)
at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1586)
at android.widget.LinearLayout.onLayout(LinearLayout.java:1495)
at android.view.View.layout(View.java:16636)
at android.view.ViewGroup.layout(ViewGroup.java:5437)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:336)
at android.widget.FrameLayout.onLayout(FrameLayout.java:273)
at android.view.View.layout(View.java:16636)
at android.view.ViewGroup.layout(ViewGroup.java:5437)
at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1743)
at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1586)
at android.widget.LinearLayout.onLayout(LinearLayout.java:1495)
at android.view.View.layout(View.java:16636)
at android.view.ViewGroup.layout(ViewGroup.java:5437)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:336)
at android.widget.FrameLayout.onLayout(FrameLayout.java:273)
at com.android.internal.policy.PhoneWindow$DecorView.onLayout(PhoneWindow.java:2678)
at android.view.View.layout(View.java:16636)
at android.view.ViewGroup.layout(ViewGroup.java:5437)
at android.view.ViewRootImpl.performLayout(ViewRootImpl.java:2171)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1931)
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1107)
at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:6013)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:858)
at android.view.Choreographer.doCallbacks(Choreographer.java:670)
at android.view.Choreographer.doFrame(Choreographer.java:606)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:844)
at an
The following is my RecyclerAdapter class:
public class MarketFeedRecyclerAdapter extends LoadingRowRecyclerAdapter {
private static final int VIEW_TYPE_MARKET_FEED = 0;
private static final int VIEW_TYPE_AD = 1;
private final Context context;
private final List<Feed> feedItems;
private final ImageLoader feedItemImageLoader;
private FeedItemClickListener feedItemClickListener;
private boolean isLongPressed = false;
public MarketFeedRecyclerAdapter(Context context, List<Feed> feedItems, ImageLoader feedItemImageLoader, List<Feed> adItems) {
this.context = context;
this.feedItems = feedItems;
this.feedItemImageLoader = feedItemImageLoader;
this.feedItems.addAll(adItems);
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
switch (viewType) {
case VIEW_TYPE_MARKET_FEED:
return new MarketFeedViewHolder(new FeedItemView(context));
case VIEW_TYPE_AD:
return new AdViewHolder(new MarketFeedAdItemView(context));
}
return super.onCreateViewHolder(parent, viewType);
}
// Differentiate between feedItem views and nativeAds
#Override
public int getViewType(int position) {
int viewType = VIEW_TYPE_MARKET_FEED;
if ((position % 25 == 0)) {
viewType = VIEW_TYPE_AD;
}
return viewType;
}
#Override
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
if (getViewType(position) == VIEW_TYPE_MARKET_FEED) {
bindMarketFeedItemView((MarketFeedViewHolder) viewHolder, position - position / 6);
} else {
bindAdItemView((AdViewHolder) viewHolder);
}
}
// For Ad Objects
private void bindAdItemView(AdViewHolder viewHolder) {
showNativeAd(viewHolder);
}
private void bindMarketFeedItemView(MarketFeedViewHolder viewHolder, int position) {
final FeedItemView feedItemView = viewHolder.feedItemView;
final Feed feedDesign = feedItems.get(position);
// TODO we can probably conditionally show or hide these based on the type of feed item, same as in FeedActivitySingle
feedItemView.showOrHideEditButton(false);
feedItemView.showOrHideBuyButton(true);
feedItemView.showOrHideFlipButton(feedDesign.getDesign().getCompressedBackImage() != null);
feedItemView.showOrHidePriceText(true);
// Set the results into TextViews
feedItemView.setProductPriceText(String.valueOf(feedDesign.getDesign().getPrice()));
feedItemView.setDownloadsText(String.valueOf(feedDesign.getDesign().getDownloadCount()));
feedItemView.setLikesText(String.valueOf(feedDesign.getDesign().getLikesCount()));
feedItemView.setUsernameText(feedDesign.getDesign().getAuthor().getUsername());
feedItemView.setTimestampText(feedDesign.getTimestampText());
feedItemView.getSaveImage().setImageResource(feedDesign.isInPersonalGallery() ? R.drawable.ic_action_saved : R.drawable.ic_not_saved);
feedItemView.getLikeImage().setImageResource(feedDesign.isLiked() ? R.drawable.ic_action_like_feed_full : R.drawable.ic_action_like_feed);
feedItemView.getTrashImage().setVisibility(ParseHelper.isCurrentUser(feedDesign.getDesign().getAuthor().getObjectId()) ? View.VISIBLE : View.GONE);
feedItemView.getFeedSocialShareImage().setVisibility(View.VISIBLE);
switch(feedDesign.getDisplayedSide()) {
case FRONT:
feedItemImageLoader.DisplayImage(feedDesign.getDesign().getCompressedImage().getUrl(), feedItemView.getImage(), feedItemView.getProgressBar());
break;
case BACK:
feedItemImageLoader.DisplayImage(feedDesign.getDesign().getCompressedBackImage().getUrl(), feedItemView.getImage(), feedItemView.getProgressBar());
break;
}
if(feedDesign.getDesign().getAuthor().getProfilePicture() != null) {
feedItemImageLoader.DisplayImage(feedDesign.getDesign().getAuthor().getProfilePicture().getUrl(), feedItemView.getProfilePicture(), null); // TODO should this use profilePictureFileCache?
} else {
viewHolder.feedItemView.getProfilePicture().setImageResource(R.drawable.ic_anonymous);
}
SetCommentViews(feedItemView, feedDesign.getComments());
SetClickListeners(feedItemView, feedDesign, position);
}
#Override
protected int getContentDataSize() {
return feedItems.size();
}
class MarketFeedViewHolder extends RecyclerView.ViewHolder {
FeedItemView feedItemView;
public MarketFeedViewHolder(FeedItemView view) {
super(view);
this.feedItemView = view;
}
}
class AdViewHolder extends RecyclerView.ViewHolder {
MarketFeedAdItemView adItemView;
public AdViewHolder(MarketFeedAdItemView view) {
super(view);
this.adItemView = view;
}
}
private NativeAd nativeAd;
private AdChoicesView adChoicesView;
private void showNativeAd(AdViewHolder viewHolder){
AdSettings.addTestDevice("a6ffb7bec7af13f768f033dbfea042df");
nativeAd = new NativeAd(context, "846223392142435_1025413774223395");
nativeAd.setAdListener(new AdListener() {
#Override
public void onError(Ad ad, AdError adError) {
}
#Override
public void onAdLoaded(Ad ad) {
final MarketFeedAdItemView adItemView = viewHolder.adItemView;
// Setting the Text
adItemView.nativeAdSocialContext.setText(nativeAd.getAdSocialContext());
adItemView.nativeAdCallToAction.setText(nativeAd.getAdCallToAction());
adItemView.nativeAdTitle.setText(nativeAd.getAdTitle());
adItemView.nativeAdBody.setText(nativeAd.getAdBody());
// Downloading and setting the ad icon
NativeAd.Image adIcon = nativeAd.getAdIcon();
NativeAd.downloadAndDisplayImage(adIcon, adItemView.nativeAdIcon);
// Download and setting the cover image
/*NativeAd.Image adCoverImage = nativeAd.getAdCoverImage();*/
adItemView.nativeAdMedia.setNativeAd(nativeAd);
// Add adChoices icon
if (adChoicesView == null) {
adChoicesView = new AdChoicesView(context, nativeAd, true);
adItemView.addView(adChoicesView, 0);
}
nativeAd.registerViewForInteraction(adItemView);
}
#Override
public void onAdClicked(Ad ad) {
}
});
nativeAd.loadAd();
}
}
My RecyclerAdapter extends the following class, which helps with loading more feedItems:
public abstract class LoadingRowRecyclerAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
public static final int ROW_VIEW_TYPE_LOADING = 72398; // obscure number
private boolean mContainsLoadingRow;
protected abstract int getContentDataSize();
protected abstract int getViewType(int position);
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
switch (viewType) {
case ROW_VIEW_TYPE_LOADING:
return new LoadingViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.loading_row, parent, false));
}
throw new IllegalArgumentException("viewType is not ROW_VIEW_TYPE_LOADING. You must handle all other values of viewType (defined by getViewType) before calling super.");
}
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
// empty
}
#Override
public final int getItemCount() {
return mContainsLoadingRow ? (getContentDataSize() + 1) : getContentDataSize();
}
#Override
public int getItemViewType(int position) {
return (position == getContentDataSize()) ? ROW_VIEW_TYPE_LOADING : getViewType(position);
}
/**
* Sets a boolean which is used by getItemCount and in turn getItemViewType to determine which view type the row should be (loading view vs. other view).
* Should only be called when there is more results to load in an upcoming api request (determined by calling fragment).
*/
public void toggleLoadingRowOn() {
mContainsLoadingRow = true;
}
/**
* Checks to see if a loading row exists by checking an instance boolean and removes the row / clears the boolean.
* This helps to 'replace' a loading row with a different row.
*/
public void toggleLoadingRowOff() {
if (mContainsLoadingRow) {
mContainsLoadingRow = false;
// removes the loading row explicitly instead of allowing it to be 'pushed' down when new user suggestion rows are added.
// this is only required to maintain consistency with the rest of the app.
int position = getContentDataSize();
if (position >= 0) {
notifyItemRemoved(position);
}
}
}
protected class LoadingViewHolder extends RecyclerView.ViewHolder {
public LoadingViewHolder(View v) {
super(v);
}
}
}
Finally, the class which determines the threshold for loading more items:
public abstract class EndlessRecyclerOnScrollListener extends RecyclerView.OnScrollListener {
private static final int VISIBLE_THRESHOLD = 5; // The minimum amount of items to have below your current scroll position before loading more
private LinearLayoutManager mLinearLayoutManager;
public abstract void onLoadMore();
public EndlessRecyclerOnScrollListener(LinearLayoutManager linearLayoutManager) {
this.mLinearLayoutManager = linearLayoutManager;
}
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
int totalItemCount = mLinearLayoutManager.getItemCount();
int firstVisibleItem = mLinearLayoutManager.findFirstVisibleItemPosition();
int visibleItemCount = recyclerView.getChildCount();
int lastItemVisible = firstVisibleItem + visibleItemCount;
// once the last visible item is within VISIBLE_THRESHOLD from the bottom, we want to load more
if ((totalItemCount - lastItemVisible) <= VISIBLE_THRESHOLD) {
onLoadMore();
}
}
}
The problem in your code is there:
#Override
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
if (getViewType(position) == VIEW_TYPE_MARKET_FEED) {
bindMarketFeedItemView((MarketFeedViewHolder) viewHolder, position - position / 6);
} else {
bindAdItemView((AdViewHolder) viewHolder);
}
}
You are checking for current row viewType (and that's good), but you can actually have 3 possible viewType: market, ad and loading.
So when this
#Override
public int getItemViewType(int position) {
return (position == getContentDataSize()) ? ROW_VIEW_TYPE_LOADING : getViewType(position);
}
return ROW_VIEW_TYPE_LOADING you are casting a LoadingViewHolder to AdViewHolder.
That cause the ClassCastException.
For solving that just change the onBindViewHolder method to either include all three possible view types or use an else if instead of else.
Hope this helps

Categories