Android: Query model before adding to RecyclerView Adapter - android

I have Tab/ViewPager Layout all consists of RecyclerView with datas depending on 'ID', What I tried so far is to get all the data in my model and add to adapter, then set the visibility to hide others data but it just leaves a blank space without the layout in my recyclerview. So i would like to query it first and just pick all the ID's with 245 before adding the result to my adapter.
Guys I'm trying to remove some items in recycler view by
#Override
public void onBindViewHolder(#NonNull RecyclerViewAdapter.ViewHolder holder, final int position) {
final int i = holder.getAdapterPosition();
int id = mDataset.getCargoItem().get(i).getCargoStatusId();
if (id != 245){
holder.itemView.setVisibility(View.GONE);
}
}
Please comment if you need more of my codes

change this ..
int id = mDataset.getCargoItem().get(position).getCargoStatusId();

Related

Is there a way to populate a RecyclerView row with more than one ViewHolder object? What should the adapter implement?

I am implementing a recycler view with two ViewHolder types. After creating the first item as first-type view holder, I have a list of items of the same, second type. Now, I would like to juxtapose the second type items in order to define a two-column recycler view list considering the second type. Is it possible to do that? I have no idea of what should be implemented in the adapter and honestly I have found no good suggestions here. I guess I might not post my adapter code, since I don't know whether it is possible to do what I aim, I hope a conceptual answer may be sufficient too. I have an image, made with a not good image editor of my smartphone, I hope it is clear:
I am late but I found out a solution.
I associate to the recyclerview a GridLayoutManager with span count = 2.
Since I have, at the top of the recyclerview, an EditText (I use it as a search bar), as you can see in the image, I have to set the lookup of the grid. This is done by setSpanSizeLookup(). So, in the onViewCreated() method of my fragment class that initializes the recyclerview (I have a Fragment hosting the recyclerview), I insert the following code:
GridLayoutManager gridLayoutManager = new GridLayoutManager(getContext(), 2);
gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
#Override
public int getSpanSize(int position) {
int type = adapter.getItemViewType(position);
if (type == RecyclerAdapter.SEARCH)
return 2;
else
return 1;
}
});
recyclerView.setLayoutManager(gridLayoutManager);
where the part "if (type == RecyclerAdapter.SEARCH) return 2;" means that the view holder for the search bar is going to occupy one whole row, 2 grid cells (since the span = 2).
In my recyclerview adapter, the method getItemViewType is essential:
#Override
public int getItemViewType(int position) {
if (position == 0)
return SEARCH;
else
return DESCRIPTION;
}
where SEARCH and DESCRIPTION are two static final variables.

Saving the content of recycler view after user has made changes onto it and generate a new list of object

This is how my viewHolder looks like. It has the spinner element. I have called the getItemCount() method using adapter to get the size of the list that is populated on recycler view. Now User can select an item from the spinner. After user has made all the changes i.e. by changing the spinners in all the views needed, i want to retrieve the changed position of spinners for all the viewholders in a array list. I am working on android recyclerview. There is a list of objects that is passed to the adapter and the list is displayed perfectly. Each of my viewholder have three views in it. These are two text views and one spinner. User can select one of the options available from the spinner and that's how my recyclerview will be updated.
I need help for saving the updated list of object i.e., the spinner position selected by the user for all the list items to generate an xml out of it.
my code looks like this:
int size = mAdapter.getItemCount();
int val[]= new int[size]; //till here it's working perfectly fine
for(int i=0; i<size; i++){
View view_temporary = recyclerView.getChildAt(i);
Spinner spin = (Spinner)view_temporary.findViewById(R.id.action);
val[i] = spin.getSelectedItemPosition();
Log.d("OfficeSpace", ""+val[i]);
}
//here activity crashes
I have resolved the issue. I wanted to save the status of my spinner item present in my view holder after scrolling back to the top. Inside my onBind method of recyclerView, i added this code:
#Override
public void onBindViewHolder(final MyViewHolder holder, final int position) {
final Features feature = mFeatures.get(position); //to get the current object from the list
holder.mSpinner.setSelection(feature.getVal());
holder.mSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
#Override
public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
mFeatures.get(position).setVal(pos);
}
#Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
}

RecyclerView scroll makes findViewHolderForAdapterPosition return null

I have a RecyclerView with a Horizontal LinerLayout. It displays numbers from 10 to 1, that is used to rate something.
When I select 10 and scroll back to 1 and select 1. I have to update the UI to remove selection on 10 and update selection on 1. But, when I use findViewHolderForAdapterPosition() to remove the selection on 10 it gives me a NullPointerException
I am getting the position in the ViewHolder with getAdapterPosition().
Then, I use that position to get the ViewHolder by calling findViewHolderForAdapterPosition() on my recycler view object and update the UI to remove the selection from 10.
vh = (RatingRecyclerAdapter.ViewHolder)
mRecycler.findViewHolderForAdapterPosition(previousPosition);
vh.textRating.setBackgroundResource(R.drawable.rating_background_selected_orange);;
With some tests, I found out when I try to do the same thing without scrolling it works fine. However, only when I am scrolling it gives me a NullPointerException
How do I fix this?
As requested here is some important code from Adapter class.
#Override
public void onBindViewHolder(RatingRecyclerAdapter.ViewHolder holder, int position) {
String itemText = itemList.get(position);
holder.textRating.setText(itemText);
}
public class ViewHolder extends RecyclerView.ViewHolder {
TextView textRating;
public ViewHolder(View itemView) {
super(itemView);
textRating = (TextView) itemView.findViewById(R.id.text_rating);
textRating.setOnClickListener(ratingClickListener);
}
private final View.OnClickListener ratingClickListener = new View.OnClickListener() {
#Override
public void onClick(View v) {
int position = getAdapterPosition();
if (callback != null) {
callback.onClickRating(v, position);
}
}
};
}
Activity Class
#Override
public void onClickRating(View view, int position) {
RatingRecyclerAdapter.ViewHolder vh;
int color;
int previousPosition = mAdapter.getSelectedPosition(); //Get previously clicked postion if any.
if (previousPosition == Constants.NO_ITEM_SELECTED) {
// An item was selected first time
vh = (RatingRecyclerAdapter.ViewHolder)
mRecycler.findViewHolderForAdapterPosition(position);
mAdapter.setSelectedPosition(position); // Save new item selected position.
color = Utility.getItemColor(mAdapter.getSelectedRating());
mAdapter.setSelectedRatingResource(vh, color);
return;
}
if (position == previousPosition) // Same item was selected
return;
vh = (RatingRecyclerAdapter.ViewHolder)
mRecycler.findViewHolderForAdapterPosition(previousPosition);
color = Utility.getItemColor(mAdapter.getSelectedRating());
mAdapter.setUnselectedRatingResource(vh, color); // Remove the previous selected item drawables.
vh = (RatingRecyclerAdapter.ViewHolder)
mRecycler.findViewHolderForAdapterPosition(position);
mAdapter.setSelectedPosition(position); // Save new item selected position.
color = Utility.getItemColor(mAdapter.getSelectedRating());
mAdapter.setSelectedRatingResource(vh, color); // Set the new selected item drawables. Setting some background to indicate selection.
}
As Sevastyan has written in the comment, the RecyclerView immediately recycles the view as soon as the item is out of the screen. So if we call findViewHolderForAdapterPosition() for a view which is outside the screen we get a null value. (I am not confirming this is the actual case. But, this is what it seems to me.)
So I created a class that stores all the data about an item in the RecyclerView and stored all the colours and value of that item in the class. And when we are populating the view, set the all the colours based on data stored in that class.
PS: I THANK Sevastyan for not giving me the answer directly. But, only giving me the reason for getting that Exception.
If your view is out of the screen, it can be recycled OR cached.
In case it's recycled, you can handle in onViewRecycled() method or setup the view again inside onBind() when the view becomes visible (you can save the state on the object of your list if needed).
In case it's not recycled (onViewRecycled method not called for that position), it's probably cached. You can set the cache size to zero to prevent this state from happening.
recycler.setItemViewCacheSize(0)

RecyclerView individual viewTypes

I want to display many [CardViews] within a [RecyclerView].
Therefore I am using an adapter that extends
RecyclerView.Adapter < RecyclerView.ViewHolder >
Everything works fine so far.
But now, I want to populate the CardView with different widgets(checkboxes, textviews, buttons.. ) in different order, dependent on the data of the current dataset position. Defining a ViewHolder class for every possible combination of the widgets order is not an option due too many possibilities.
I managed it already with two RecyclerView-Adapters. One for the RecyclerView "list" and one for populating the CardView inside each item.
But that leads to bad performance.
So I came back to use only one RecyclerView and one Adapter.
But, how to populate each CardView in a good way then? Thank you for your help!
Update1:
This is my current and result updated_screenshot. Looks "fine" so far but is achieved not in a proper way.
I did it as following:
Setting an Adapter (OuterAdapter) to the recyclerView, that holds each CardView:
public class OuterAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
.
.
.
#Override
public void onBindViewHolder(final RecyclerView.ViewHolder holder, final int position) {
rviewHolder = (RviewHolder) holder;
llm = new LinearLayoutManager(context);
rviewHolder.rv.setLayoutManager(llm);
innerAdapter = new VHAdapter(context, questionList);
rviewHolder.rv.setAdapter(innerAdapter);
}
.
.
.
}
Now, every Item has its own "VHAdapter" which fills the CardViews like:
#Override
public void onBindViewHolder(RecyclerView.ViewHolder h, final int position) {
switch (h.getItemViewType()) {
case ONLY_HEADER_TEXT:
HeaderTextHolder holder = (HeaderTextHolder) h;
holder.tv.setText(q.getText());
break;
case ANSWER_TYPE_NUMBER:
NumberHolder nh = (NumberHolder) h;
nh.tv.setText(q.getText());
nh.et.setOnEditorActionListener(new TextView.OnEditorActionListener() {
#Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
tempAnswer.setNumber(Integer.parseInt(v.getText().toString()));
tempAnswer.setQuestionType(Question.ANSWER_TYPE_NUMBER);
return false;
}
});
break;
.
.
.
What I am trying to do now is getting rid the VHAdapter. But that means, I'd have to manually add widgets to ViewHolders rootLayout at OuterAdapter's onBindViewHolder.. Is that correct?

RecyclerView.Adapter onBindViewHolder() gets wrong position

I'll show the code and after the steps to get the problem.
I have a recyclerview inside a tabbed fragment that takes the dataset from a custom object:
mRecyclerView = (RecyclerView) v.findViewById(R.id.recyclerview);
mRecyclerView.setLayoutManager(mLayoutManager);
mRecyclerAdapter = new MyRecyclerAdapter(mMes.getListaItens(), this, getActivity());
mRecyclerView.setAdapter(mRecyclerAdapter);
I set the longclick behavior of the list items in onBindViewHolder() of the adapter:
#Override
public void onBindViewHolder(final ViewHolder holder, final int position) {
ItemMes item = mListaItens.get((position));
holder.descricao.setText(item.getDescrição());
holder.valor.setText(MainActivity.decimalFormatWithCod.format(item.getValor()));
...
holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
#Override
public boolean onLongClick(View v) {
new MaterialDialog.Builder(mContext)
.title(holder.descricao.getText().toString())
.items(R.array.opcoes_longclick_item)
.itemsCallbackSingleChoice(-1, new MaterialDialog.ListCallbackSingleChoice() {
#Override
public boolean onSelection(MaterialDialog dialog, View view, int which, CharSequence text) {
switch (which) {
case 0:
mParentFragment.showUpdateItemDialog(position);
return true;
case 1:
mParentFragment.showDeleteItemDialog(position);
return true;
}
return false;
}
})
.show();
return true;
}
});
}
Then, the methods in the fragment that take care of delete the item itself:
public void showDeleteItemDialog(int position) {
final ItemMes item = mMes.getListaItens().get(position);
new MaterialDialog.Builder(getActivity())
.title("Confirmar Remoção")
.content("Tem certeza que deseja remover " + item.getDescrição() + "?")
.positiveText("Sim")
.negativeText("Cancelar")
.onPositive(new MaterialDialog.SingleButtonCallback() {
#Override
public void onClick(#NonNull MaterialDialog dialog, #NonNull DialogAction which) {
deleteItem(item);
}
})
.show();
}
public void deleteItem(ItemMes item) {
getMainActivity().deleteItemFromDatabase(item.getID());
int position = mMes.getListaItens().indexOf(item);
mMes.getListaItens().remove(position);
mRecyclerAdapter.notifyItemRemoved(position);
atualizaFragment();
}
And finally the method in activity that do the DB operation:
public int deleteItemFromDatabase(long id) {
SQLiteDatabase db = dataBaseHelper.getWritableDatabase();
String where = DBHelper.COLUNA_ID + " = ?";
String[] args = {String.valueOf(id)};
int rowsAffected = db.delete(DBHelper.TABELA_ITEM, where, args);
db.close();
return rowsAffected;
}
Now i'll reproduce the steps:
I'm showing 3 itens in the listview. Then I try to remove the first:
1 - The longclick is intercepted passing the correct index:
2 - The item is correctly deleted from the database:
3 - After all this, as expected, the adapter is storing and showing 2 items...
SO, if I try to delete the first item of this 2 item list I get the wrong position (should be 0, is 1):
And also if I try to delete the last item of this 2 item list I get the wrong position (should be 1, is 2):
The question is: If I have a dataset of size 2 (and the adapter knows it), how can it call onBindViewHolder(ViewHolder holder, int [last index +1])?
I have no idea what could be wrong. So I ask help cause I'm thinking about give up this project cause I do everything right but always something dont works, and Im tired.
Thanks in advance.
I've noticed that in method onBindViewHolder(VH holder, int position) while the position was comming wrong, the holder.getAdapterPosition() gives me always the correct position.
So I changed my code from:
ItemMes item = mListaItens.get((position));
...
mParentFragment.showUpdateItemDialog(position);
...
mParentFragment.showDeleteItemDialog(position);
....
To:
ItemMes item = mListaItens.get((holder.getAdapterPosition()));
...
mParentFragment.showUpdateItemDialog(holder.getAdapterPosition());
...
mParentFragment.showDeleteItemDialog(holder.getAdapterPosition());
....
And everything works well. This is very strange but...
Thanks everybody.
Took a look at the adapter code you provided in the comment and it's pretty straightforward. Try this: rather than call notifyItemRemoved(), call notifyDataSetChanged(). This is rather expensive as it will cause your adapter to re-bind the data set (and re-create ViewHolders), but since you're using an ArrayList where you are removing an element, it's really the simplest way to do it. Otherwise you'll have to track the position of the items and when an item is removed it cannot change the position of other items - or handle the case where items shift their position in the data set.
Try this code in onBindViewHolder()
int adapterPos=holder.getAdapterPosition();
if (adapterPos<0){
adapterPos*=-1;
}
ItemMes item = mListaItens.get((adapterPos));
mParentFragment.showUpdateItemDialog(adapterPos);
Use adapterPos instead of position variable.
According to RecyclerView's getAdapterPosition documentation:
RecyclerView does not handle any adapter updates until the next layout traversal. This
may create temporary inconsistencies between what user sees on the screen and what
adapter contents have. This inconsistency is not important since it will be less than
16ms but it might be a problem if you want to use ViewHolder position to access the
adapter. Sometimes, you may need to get the exact adapter position to do
some actions in response to user events. In that case, you should use this method which
will calculate the Adapter position of the ViewHolder.
So in case of implementing user events, using getAdapterPosition is a recommended way to go.

Categories

Resources