I have some tables in the database, every table has many rows,I want to show the tables's data in a list, so I use a RecyclerView to show the data, and a spinner to select the table which I want to show.
But now, when I select an item of spinner, the RecyclerView doesn't have any changes ,it can't show the new data only after I reopen the fragment.
My code:
In adapter:
1.removeItem works
the item in the RecyclerView will be removed when removeItem called.
public void removeItem(WordCls wordCls) {
int position = wordsList.indexOf(wordCls);
wordsList.remove(position);
notifyItemRemoved(position);
}
2.updateList doesn't work
But when updateList called,nothing happened in the view
public void updateList(List<WordCls> wordsList) {
//this.wordsList = wordsList;
this.wordsList.clear();
this.wordsList.addAll(wordsList);
notifyDataSetChanged();
}
In fragment:
in onCreateView():
recyclerView = (RecyclerView) view.findViewById(R.id.wordbook_recycler_list);
recyclerView.setLayoutManager(new LinearLayoutManager(activity));
recyclerView.setItemAnimator(new DefaultItemAnimator());
recyclerView.setHasFixedSize(true);
wordsList = WordsManager.getWordsList(tableName);
wordCardAdapter = new WordCardAdapter(activity, tableName, wordsList);
recyclerView.setAdapter(wordCardAdapter);
wordCardAdapter.setOnItemClickListener(new WordCardAdapter.OnRecyclerViewItemClickListener() {
#Override
public void onItemClick(View view, WordCls wordCls) {
Toast.makeText(getActivity(), wordCls.getWord(), Toast.LENGTH_SHORT).show();
wordCardAdapter.removeItem(wordCls);
WordsManager.deleteWord(tableName, wordCls.getWord());
}
});
in SpinnerItemSelectedListener:
#Override
public void onItemSelected(AdapterView<?> parent, View view, int position,long id) {
tableName= parent.getItemAtPosition(position).toString();
prefEditorSettings.putInt(KEY_SPINNER_SELECTED_ITEM, position);
prefEditorSettings.commit();
tableName = getWordbookList().get(spnSelectedPosition);
wordsList = WordsManager.getWordsList(tableName);
wordCardAdapter.updateList(wordsList);
Toast.makeText(activity, tableName, 2000).show();
}
==============================================
update:
the method updateList is right.
just my careless to have passed a wrong param at
tableName = getWordbookList().get(spnSelectedPosition);
it should be
tableName = getWordbookList().get(position);
now, it works.
Actually, I don't know if RecyclerView is the best way to fulfill your purpose.
RecyclerView(s) are to be used when you have to show multiple instances of your List, in a list-y way :)
If you have just one View to update (that contains multiple fields to be updated), I'd choose some data-binding library like RoboBinding or the new beta version of google binding library (MVVM paradigm)
I'm not going into further details as I just want to warn you about MVVM programming paradigm that can be a worthy solution in your case.
instead of wordCardAdapter.updateList(wordsList) , add new data to wordsList and wordCardAdapter.notifyDataSetChanged() inside spinner
hope it works.
Related
I have pondered on this for days now, I need help. I have 2 fragments in a viewpager. They are both showing the same values despite feeding them with different data.
Link to image
Fragment A has a spinner dropdown of countries same as Fragment B. Fragment A is supposed to show list of Hospitals when country is selected, Fragment B is supposed to show list of Schools depending on country selected.
PROBLEM
When I select a country in Fragment A, I get the list of hospitals in fragment A. When I swipe to fragment B, I find the list of hospitals which are supposed to be in A displayed. When I select a country in B, the list refreshes and I get the list of schools as should be, but when I swipe back again to fragment A I find the same list of schools instead of list of hospitals previously selected. I am using dagger2 with databinding.
Below code depicts one scenario but they are basically the same just different adapters and also different layouts.
CODE
private void initRecyclerview() {
binding.rvSchools.setHasFixedSize(true);
adapter = new SchoolsAdapter(getContext());
binding.rvSchools.setLayoutManager(new LinearLayoutManager(getContext()));
binding.rvSchools.setAdapter(adapter);
adapter.notifyDataSetChanged();
}
private void countries() {
viewModel.getCountries().removeObservers(getViewLifecycleOwner());
viewModel.getCountries().observe(getViewLifecycleOwner(), new Observer<Resource<List<Countries>>>() {
#Override
public void onChanged(Resource<List<Countries>> countriesList) {
switch (countriesList.status) {
case SUCCESS:
ArrayAdapter<String> spinnerAdapter = new ArrayAdapter<>(getApplicationContext,
R.layout.simple_spinner_item, countriesList);
spinnerAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
binding.spinner.setAdapter(spinnerAdapter);
spinnerAdapter.notifyDataSetChanged();
break;
}
}
});
binding.spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
#Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
String country = countriesList.get(position);
schools(country);
}
#Override
public void onNothingSelected(AdapterView<?> parent) {}
});
}
private void schools(String country) {
viewModel.getSchoolsHospitals(country, "School").removeObservers(getViewLifecycleOwner());
viewModel.getSchoolsHospitals(country, "School").observe(getViewLifecycleOwner(), new Observer<Resource<List<SchoolsHospitals>>>() {
#Override
public void onChanged(Resource<List<SchoolsHospitals>> schoolsList) {
switch (schoolsList.status) {
case SUCCESS:
binding.setSchools(schoolsList.data);
}
}
});
}
This is a rather odd behaviour considering how I wanted to re-use code. I was using the same viewModel class because the data is the same. After creating a separate viewModel class for Schools i.e a duplicate ViewModel just different class names, the respective contents were set as needed.
If there is a better approach, it will be much appreciated.
I'm using firebase-ui to retrieve from firebase and I want to remove from displaying all the status that are equal to unlive I don't want to delete it from firebase database but only removed it from displaying on the recyclerview tried also doing the solution Cannot call this method while RecyclerView is computing a layout or scrolling when try remove item from recyclerview here still encountering the error. Please help I'm stuck here for days. Can't find any solution. Thanks
mAdapter = new FirebaseRecyclerAdapter<Hotels_Model, Adapter_HotelsHolder>(mOptions) {
#Override
protected void onBindViewHolder(Adapter_HotelsHolder holder,int position, Hotels_Model model) {
String status = model.getStatus();
if (status.equals("unlive")) {
mAdapter.notifyItemRemoved(holder.getAdapterPosition());
mAdapter.notifyItemRangeChanged(holder.getAdapterPosition(), getItemCount());
}
}
This is my error if I scroll to the position where the status is unlive
Cannot call this method while RecyclerView is computing a layout or scrolling android.support.v7.widget.RecyclerView
So you can do it easily in that way:
mAdapter = new FirebaseRecyclerAdapter<Hotels_Model, Adapter_HotelsHolder>(mOptions) {
#Override
protected void onBindViewHolder(Adapter_HotelsHolder holder,int position, Hotels_Model model) {
String status = model.getStatus();
if (status.equals("unlive")) {
holder.yourRowView.setVisibility(View.GONE);
}
}
You don't need to remove your items from recyclerview.
i am using a recyclerView and loading the data from database.The recyclerView item have 2 buttons. The recyclerView loads fine and clicks work fine for the following code.
allData=db.getCatData(CATEGORYLIST_CIDD,1);
textlistadapter = new TextListAdapter(getActivity(), allData);
recyclerView.setAdapter(textlistadapter);
later i rearrange the adapter data for displaying the data in descending order by using this code
db=new DatabaseHandler(getActivity().getApplicationContext());
allData=db.getCatData(CATEGORYLIST_CIDD,order_id);
textlistadapter = new TextListAdapter(getActivity(), allData);
recyclerView.setAdapter(textlistadapter);
textlistadapter.notifyDataSetChanged();
Now the items are rearranged on the recyclerView, but the onShareButtonClickListener returns null, here is the code of the adapter.
private OnShareButtonClickListener onShareButtonClickListener;
public interface OnShareButtonClickListener {
void onItemClick(View view, Pojo obj, int position);
}
public void setOnShareButtonClickListener(final OnShareButtonClickListener onShareButtonClickListener) {
this.onShareButtonClickListener = onShareButtonClickListener;
}
the click listener code is here
holder.bt_share.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
//Snackbar.make(view, "Share Clicked", Snackbar.LENGTH_SHORT).show();
if (onShareButtonClickListener != null) {
onShareButtonClickListener.onItemClick(view, c, position);
}
}
});
The click does not work since the onShareButtonClickListener is null.
The same code works well for the first time and once i change the order then the click events are not working, all other functions are working well.
you should only need to do this once:
textlistadapter = new TextListAdapter(getActivity(), allData);
recyclerView.setAdapter(textlistadapter);
later on, if you want to update/change the data, then you should do something like this:
textlistadapter.setListItems(allData);
textlistadapter.notifyDataSetChanged();
Where setListItems() is a public method in your adapter, an example like this:
public void setListItems(List<> allData) {
this.data.clear();
this.data.addAll(allData);
}
It's easy to understand what you are doing wrong. You populate an array which will feed your adapter with data, that meaning that, after setting the adapter, if you want it's data to change, you only need to change the array you populated the data with, and notify the adapter for the data change.
For example:
myData = db.getData();
myAdapter = new MyAdapter(this,myData);
recyvlerview.setAdapter(myAdapter);
Now every time you want to change the data, you only need to change your myData and notifity the adapter
myAdapter.notifyDataSetChange();
Basically instead of doing this:
db=new DatabaseHandler(getActivity().getApplicationContext());
allData=db.getCatData(CATEGORYLIST_CIDD,order_id);
textlistadapter = new TextListAdapter(getActivity(), allData);
recyclerView.setAdapter(textlistadapter);
textlistadapter.notifyDataSetChanged();
This should suffice
db=new DatabaseHandler(getActivity().getApplicationContext());
allData=db.getCatData(CATEGORYLIST_CIDD,order_id);
textlistadapter.notifyDataSetChanged();
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.
I have created a recycler view adapter. Based on the the item in the adapter I am trying to change the UI for that row. When the screen is first loaded the UI is rendered correctly . But once I scroll through, the screen gets updated even for the rows where it should not hav.
Here is the code :
#Override
public void onBindViewHolder(ViewHolder viewHolder, int i) {
final OrderItem order = mItems.get(i);
viewHolder.tvName.setText(getCustomers().get(order.getmConsumerId()).getProfile().getFirstName().toUpperCase() );
viewHolder.tvDelType.setText(getDeliveryType(order.getmDeliveryType()));
viewHolder.tvTime.setText(formatTime(order.getmETATime()));//format time before present
viewHolder.tvAmt.setText("Rs " + order.getmAmount());
viewHolder.rlvName.setTitleText(order.getmConsumerId().substring(0, 1));
viewHolder.rlvName.setTitleSize(82f);
viewHolder.tvDetails.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View view) {
Intent intent = new Intent(context, OrderDetailsActivity.class);
Gson gson = new Gson();
String ord = gson.toJson(order);
String user = gson.toJson(getCustomers().get(order.getmConsumerId()));
intent.putExtra("orderItem", ord);
intent.putExtra("user", user);
context.startActivity(intent);
}
});
setListeners(viewHolder, order, i);
//setStatus(viewHolder,order,i);
if(!order.getmOrderStatus().equals(Constants.ENUM_ORDERS_STATUS_PENDING))
{
viewHolder.tvOrderStatus.setText((order.getmOrderStatus().toUpperCase()));
viewHolder.tvOrderStatus.setVisibility(View.VISIBLE);
viewHolder.llButtons.setVisibility(View.GONE);
}
//viewHolder.rlvName.setBackgroundColor(Color.parseColor("FF0000"));
}
So based on the value my order has, I need to change the UI of the row . After scrolling, it changes the values of the row where it should not have.
Please suggest.
Because of recyler views RAM Management,the recyler view try to load cards from cache and because of your recyler view's cache, the items loads incorrectly.
for preventing this you must add a else to your if like this:
if(!order.getmOrderStatus().equals(Constants.ENUM_ORDERS_STATUS_PENDING))
{
viewHolder.tvOrderStatus.setText((order.getmOrderStatus().toUpperCase()));
viewHolder.tvOrderStatus.setVisibility(View.VISIBLE);
viewHolder.llButtons.setVisibility(View.GONE);
}
else
{
//The Default Item View Settings Here...
}