I want a spinner with checkbox (custom adapter xml will have one image,textview and checkbox in horizontal line)and whatever checkbox I check it should be checked even when I reopen application. That specific checkbox state should be save and when I open application again that specific checkbox should be checked in spinner that's it.
Description:
I am making a text to speech application and when I click on spinner country name,flag and one checkbox is in one horizontal line and there are almost 50 countries using custom adapter but what I need is whenever user click any country language to translate language application should save that checkbox or that particular position if the user comes again to click on the spinner the user should know which language was selected before by showing checkbox checked that's all.
Thanks looking forward to your answers
This is what I have and want to mark one checkbox checked
try this
//This is how to add in adapter from activity/fragment
CountryAdapter adapter = new CountryAdapter(getApplicationContext(), new CountryAdapter.CountryChangeListener() {
#Override
public void onCountryChange(CountryModel countryModel) {
//persist selected country name in preference
}
});
List<CountryModel> list = loadAllCountries();
adapter.add(list);
String selectedCountry = getSelectedCountry();
if(!TextUtils.isEmpty(selectedCountry)){
adapter.setCountrySelected(selectedCountry);
}
adapter.notifyDataSetChanged();
CountryModel is model class for your adapter.
public class CountryModel {
private String name;
private Drawable countryFlagIcon; //it could be bitmap or resource Id of the icon
/*
isSelected would be true when user selects that particular Country
*/
private boolean isSelected;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Drawable getCountryFlagIcon() {
return countryFlagIcon;
}
public void setCountryFlagIcon(Drawable countryFlagIcon) {
this.countryFlagIcon = countryFlagIcon;
}
public boolean isSelected() {
return isSelected;
}
public CountryModel(String _name,Drawable _countryFlagIcon){
name = _name;
countryFlagIcon = _countryFlagIcon;
}
public CountryModel(String _name,Drawable _countryFlagIcon,boolean _isSelected){
this(_name,_countryFlagIcon);
isSelected = _isSelected;
}
public void setSelected(boolean _isSelected){
isSelected = _isSelected;
}
#Override
public int hashCode() {
int result = 1;
result = 31 * result + (name == null ? 0 : name.hashCode());
result = 31 * result + Boolean.valueOf(isSelected).hashCode();
return result;
}
#Override
public boolean equals(Object obj) {
if(obj == null || obj.getClass() != getClass()) return false;
if(this == obj) return true;
CountryModel model = (CountryModel)obj;
return name != null && name.equals(model.name) && isSelected == model.isSelected;
}
#Override
public String toString() {
return "[country = " + name + " isSelected = " + isSelected + "]";
}
}
CountryAdapter adapter class for Spinner
public class CountryAdapter extends BaseAdapter {
private static final int VIEW_TYPES = 2;
private static final int TYPE_SELECTED = 0;
private static final int TYPE_UNSELECTED = 1;
private List<CountryModel> countryModelList;
private LayoutInflater layoutInflater;
private CountryChangeListener countryChangeListener;
public CountryAdapter(Context context,CountryChangeListener _countryChangeListener){
countryModelList = new ArrayList<>();
layoutInflater = LayoutInflater.from(context);
countryChangeListener = _countryChangeListener;
}
public void add(List<CountryModel> list){
countryModelList.addAll(list);
}
public synchronized void setCountrySelected(int position) {
updateCountryListDta(position);
if(countryChangeListener != null){
countryChangeListener.onCountryChange(getItem(position));
}
}
public synchronized void setCountrySelected(String selectedCountry) {
for(CountryModel model:countryModelList){
if(model.getName().equals(selectedCountry)){
model.setSelected(true);
}else{
model.setSelected(true);
}
}
}
private void updateCountryListDta(int position) {
for(CountryModel model:countryModelList){
model.setSelected(false);
}
getItem(position).setSelected(true);
}
#Override
public int getCount() {
return countryModelList.size();
}
#Override
public CountryModel getItem(int position) {
return countryModelList.get(position);
}
#Override
public long getItemId(int position) {
return 0;
}
#Override
public int getViewTypeCount() {
return VIEW_TYPES;
}
#Override
public int getItemViewType(int position) {
CountryModel model = getItem(position);
return model.isSelected() ? TYPE_SELECTED : TYPE_UNSELECTED;
}
#Override
public View getView(final int position, View convertView, ViewGroup parent) {
View view = convertView;
ViewHolder viewHolder = null;
if(view == null ){
view = layoutInflater.inflate(R.layout.country_dropdown_item,parent,false);
viewHolder = new ViewHolder(view);
view.setTag(viewHolder);
}else{
viewHolder = (ViewHolder) view.getTag();
viewHolder.checkBox.setOnCheckedChangeListener(null);//remove previous Attached listener
}
CountryModel model = getItem(position);
int type = getItemViewType(position);
viewHolder.checkBox.setChecked(type == TYPE_SELECTED ? true:false);
viewHolder.name.setText(model.getName());
viewHolder.checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
setCountrySelected(position);
}
});
return view;
}
private class ViewHolder{
private TextView name;
private CheckBox checkBox;
ViewHolder(View view){
name = view.findViewById(R.id.countryName);//replace with your resource ids
checkBox = view.findViewById(R.id.countryCheckBox);//replace with your resource ids
}
}
interface CountryChangeListener{
void onCountryChange(CountryModel countryModel);
}
}
You can save the state of your Checkbox in the onsavedinstancestate and retrieve this state in your oncreate method. This is only for shortterm usage, if you want to save the state of your Checkbox for future use of the application you should use a database for the states of each Checkbox or sharedpreferences. For shortterm you can use something like this
override fun onSaveInstanceState(outState: Bundle) {
val checkBox : CheckBox
outState.putBoolean("checkbox_x_checked",checkBox.isChecked)
super.onSaveInstanceState(outState)
}
override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) {
super.onCreate(savedInstanceState, persistentState)
val checkBox : CheckBox
//assign all Views and Ids
if (savedInstanceState!= null){
checkBox.isChecked = savedInstanceState.getBoolean("checkbox_x_checked",false)
}
}
If you want to save the data for Long term use, you can write the data onchecked into sharedpreferences for example something like this
// get shared preferences
val sharedpref = getSharedPreferences("checked_checkboxes", Context.MODE_PRIVATE)
val sharedpref_editor = sharedpref.edit()
//assign checkboxes
val checkBox_x : CheckBox
//set checkbox old value
checkbox_x.isChecked = sharedpref.getBoolean("checkbox_x_checked",false)
//assign oncheckedchangelistener for the checkboxes
checkBox_x.setOnCheckedChangeListener { buttonView, isChecked ->
sharedpref_editor.putBoolean("checkbox_x_checked",isChecked)
sharedpref_editor.apply()
}
Related
When I press the toggle button, I want to change the units of the list of the currently displayed recycler views at once.
I used ListAdapter + DiffUtil to display the recycler view.
The way I tried to implement this feature is to load the current list when the toggle button is pressed.
Then, after resetting the new toggle unit values for the current lists, I used submitList() to update the list.
But this was the wrong way.
My guess is because the variable created for the value of the list loaded to be updated has the same reference value, so the value changed at the same time.
In other words, there is no change because the values of the update list and the existing list are the same.
What can I do to solve this problem?
RoutineDetailModel.java
public class RoutineDetailModel {
public int id;
private int set = 1;
public static String unit = "kg";
public RoutineDetailModel() {
Random random = new Random();
this.id = random.nextInt();
}
public RoutineDetailModel(int set) {
Random random = new Random();
this.id = random.nextInt();
this.set = set+1;
}
public int getSet() {
return set;
}
public int getId() {
return id;
}
public String getWeight() {
return weight;
}
public String getUnit() {
return unit;
}
#Override
public int hashCode() {
return Objects.hash(set, weight); // getWeight를 호출하면 더 다양하게 되나?
}
#Override
public boolean equals(#Nullable Object obj) {
if(obj != null && obj instanceof RoutineDetailModel) {
RoutineDetailModel model = (RoutineDetailModel) obj;
if(this.id == model.getId()) {
return true;
}
}
return false;
}
}
MainActivity.java
public class WriteRoutineActivity extends AppCompatActivity implements WritingCommentDialogFragment.OnDialogClosedListener {
List<RoutineModel> items;
RoutineListAdapter listAdapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_write_routine);
listAdapter.setOnRoutineClickListener(new RoutineListAdapter.OnRoutineItemClickListener() {
#Override
public void onUnitBtnClicked(int curRoutinePos, String unit) {
Object obj = listAdapter.getRoutineItem(curRoutinePos);
RoutineModel item = (RoutineModel) obj;
if(obj instanceof RoutineModel) {
for(RoutineDetailModel detailItem : item.getDetailItemList()) {
detailItem.setUnit(unit);
}
listAdapter.submitList(getUpdatedList());
}
}
});
}
}
Please tell me if you need more information
To change all the items you need to use notifyDataSetChanged() otherwise you cannot update ALL the items.
In order to do so, you must create a method inside your adapter which does the following ->
public void updateItems(String unit) {
for({YOUR ITEM TYPE} item: {YOUR LIST}) {
item.unit = unit;
}
notifyDataSetChanged();
}
And call this method when you want to change all units.
yourAdapter.updateItems("Kg");
sorry if the explanation is incomplete. I just learned about Android. and in my project this time, I made a filter feature using the radio button
I followed a totorial, but the tutorial uses static data, then I change it to dynamic using my data in the database. all works !! the data appears
but when I type something in the search bar that I make, suddenly my data disappears, I make 3 radio buttons to be selected by the user to be searched / filtered, namely type, price and facilities. the three radio buttons don't work, my data that appears are gone.
How to handle that problem?? if anyone can help me please help, thank you
this is the code
this fragment
public class FilterFragment extends Fragment implements RadioGroup.OnCheckedChangeListener, View.OnClickListener {
private static final String data_url = "http://000.000.00.000/kos/get_kos.php";
RecyclerView mRecyclerView;
ProgressDialog pd;
private Context context;
private RecyclerViewAdapter adapter;
private ArrayList<UserModel> arrayList;
private RadioGroup searchViaRadioGroup;
private EditText searchEditText;
private TextView searchViaLabel;
/* Filter Type to identify the type of Filter */
private FilterType filterType;
/* boolean variable for Filtering */
private boolean isSearchWithPrefix = false;
public FilterFragment() {
}
#Override
public void onAttach(Context context) {
super.onAttach(context);
this.context = context;
}
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_filter, container, false);
pd = new ProgressDialog(getActivity());
mRecyclerView = (RecyclerView) view.findViewById(R.id.recycler_view);
arrayList = new ArrayList<>();
mRecyclerView.setLayoutManager(new GridLayoutManager(getActivity(), LinearLayoutManager.VERTICAL));
adapter = new RecyclerViewAdapter(getActivity(), arrayList);
mRecyclerView.setAdapter(adapter);
loadjson();
return view;
}
private void loadjson(){
pd.setMessage("Mengambil Data");
pd.setCancelable(false);
pd.show();
JsonArrayRequest arrayRequest = new JsonArrayRequest(Request.Method.POST, data_url, null, new Response.Listener<JSONArray>() {
#Override
public void onResponse(JSONArray response) {
pd.cancel();
Log.d("volley", "response : " + response.toString());
for (int i=0; i < response.length(); i++)
try {
JSONObject data = response.getJSONObject(i);
UserModel md = new UserModel();
md.setJudul(data.getString("judul")); // memanggil nama array yang kita buat
md.setAlamat(data.getString("alamat"));
md.setHarga(data.getString("harga"));
md.setTipe_kos(data.getString("tipe_kos"));
md.setFasilitas(data.getString("fasilitas"));
arrayList.add(md);
} catch (JSONException e) {
e.printStackTrace();
}
adapter.notifyDataSetChanged();
}
}, new Response.ErrorListener(){
#Override
public void onErrorResponse(VolleyError error) {
pd.cancel();
Log.d("volley", "error : " + error.getMessage());
}
});
Controller.getInstance().addToRequestQueue(arrayRequest);
}
#Override
public void onViewCreated(View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
findViews(view);
implementEvents();
}
//Bind all Views
private void findViews(View view) {
filterType = FilterType.TIPE_KOS;
searchViaRadioGroup = (RadioGroup) view.findViewById(R.id.search_via_radio_group);
searchEditText = (EditText) view.findViewById(R.id.search_text);
searchViaLabel = (TextView) view.findViewById(R.id.search_via_label);
}
//Populate recycler view
private void implementEvents() {
searchViaRadioGroup.setOnCheckedChangeListener(this);
searchViaLabel.setOnClickListener(this);
searchEditText.addTextChangedListener(new TextWatcher() {
#Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
#Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
//On text changed in Edit text start filtering the list
adapter.filter(filterType, charSequence.toString(), isSearchWithPrefix);
}
#Override
public void afterTextChanged(Editable editable) {
}
});
}
#Override
public void onCheckedChanged(RadioGroup radioGroup, int checkedId) {
int pos = radioGroup.indexOfChild(radioGroup.findViewById(checkedId));//get the checked position of radio button
switch (radioGroup.getId()) {
case R.id.search_via_radio_group:
switch (pos) {
case 0:
filterType = FilterType.TIPE_KOS;//Change filter type to Name if pos = 0
break;
case 1:
filterType = FilterType.NUMBER;//Change filter type to Number if pos = 1
break;
case 2:
filterType = FilterType.EMAIL;//Change filter type to Email if pos = 2
break;
}
}
}
#Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.search_via_label:
//show hide the radio group
if (searchViaRadioGroup.isShown()) {
searchViaLabel.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.up_dropdown, 0);
searchViaRadioGroup.setVisibility(View.GONE);
} else {
searchViaLabel.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.down_dropdown, 0);
searchViaRadioGroup.setVisibility(View.VISIBLE);
}
break;
}
}
}
this adapter
public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.RecyclerViewHolder> {
static class RecyclerViewHolder extends RecyclerView.ViewHolder {
private TextView judul, alamat, tipe_kos, fasilitas, harga;
RecyclerViewHolder(View view) {
super(view);
judul = (TextView) view.findViewById(R.id.judul);
alamat = (TextView) view.findViewById(R.id.alamat);
tipe_kos = (TextView) view.findViewById(R.id.tipe_kos);
fasilitas = (TextView) view.findViewById(R.id.fasilitas);
harga = (TextView) view.findViewById(R.id.harga);
}
}
private ArrayList<UserModel> arrayList;
private ArrayList<UserModel> filterArrayList;//duplicate list for filtering
private Context context;
public RecyclerViewAdapter(Context context, ArrayList<UserModel> arrayList) {
this.arrayList = arrayList;
this.context = context;
this.filterArrayList = new ArrayList<>();//initiate filter list
this.filterArrayList.addAll(arrayList);//add all items of array list to filter list
}
#Override
public RecyclerViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
View v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.custom_list_filter, viewGroup, false);
return new RecyclerViewHolder(v);
}
#Override
public void onBindViewHolder(final RecyclerViewHolder holder, final int i) {
final UserModel model = arrayList.get(i);
holder.judul.setText(model.getJudul());//menampilkan data
holder.alamat.setText(model.getAlamat());
holder.harga.setText(model.getHarga());
holder.tipe_kos.setText(model.getTipe_kos());
holder.fasilitas.setText(model.getFasilitas());
}
#Override
public int getItemCount() {
return (null != arrayList ? arrayList.size() : 0);
}
// Filter Class to filter data
public void filter(FilterType filterType, String charText, boolean isSearchWithPrefix) {
//If Filter type is TIPE_KOS and EMAIL then only do lowercase, else in case of NUMBER no need to do lowercase because of number format
if (filterType == FilterType.TIPE_KOS || filterType == FilterType.EMAIL)
charText = charText.toLowerCase(Locale.getDefault());
arrayList.clear();//Clear the main ArrayList
//If search query is null or length is 0 then add all filterList items back to arrayList
if (charText.length() == 0) {
arrayList.addAll(filterArrayList);
} else {
//Else if search query is not null do a loop to all filterList items
for (UserModel model : filterArrayList) {
//Now check the type of search filter
switch (filterType) {
case TIPE_KOS:
if (isSearchWithPrefix) {
//if STARTS WITH radio button is selected then it will match the exact TIPE_KOS which match with search query
if (model.getTipe_kos().toLowerCase(Locale.getDefault()).startsWith(charText))
arrayList.add(model);
} else {
//if CONTAINS radio button is selected then it will match the TIPE_KOS wherever it contains search query
if (model.getTipe_kos().toLowerCase(Locale.getDefault()).contains(charText))
arrayList.add(model);
}
break;
case EMAIL:
if (isSearchWithPrefix) {
//if STARTS WITH radio button is selected then it will match the exact EMAIL which match with search query
if (model.getFasilitas().toLowerCase(Locale.getDefault()).startsWith(charText))
arrayList.add(model);
} else {
//if CONTAINS radio button is selected then it will match the EMAIL wherever it contains search query
if (model.getFasilitas().toLowerCase(Locale.getDefault()).contains(charText))
arrayList.add(model);
}
break;
case NUMBER:
if (isSearchWithPrefix) {
//if STARTS WITH radio button is selected then it will match the exact NUMBER which match with search query
if (model.getHarga().startsWith(charText))
arrayList.add(model);
} else {
//if CONTAINS radio button is selected then it will match the NUMBER wherever it contains search query
if (model.getHarga().contains(charText))
arrayList.add(model);
}
break;
}
}
}
notifyDataSetChanged();
}
}
this FilterType
public enum FilterType {
TIPE_KOS, NUMBER, EMAIL;
}
and this user model
package com.example.asus.myapplication.fragment.filter;
public class UserModel {
private String judul, alamat, harga, tipe_kos, fasilitas;
public String getJudul() {
return judul;
}
public String getAlamat() {
return alamat;
}
public String getHarga() {
return harga;
}
public String getTipe_kos() {
return tipe_kos;
}
public String getFasilitas() {
return fasilitas;
}
public void setJudul(String mJudul) {
judul = mJudul;
}
public void setAlamat(String mAlamat) {
alamat = mAlamat;
}
public void setHarga(String mHarga) {
harga = mHarga;
}
public void setTipe_kos(String mTipe_kos) {
tipe_kos = mTipe_kos;
}
public void setFasilitas(String mFasilitas) { fasilitas= mFasilitas;
}
}
another one I do not understand at all for this filter API, I just pulled all the data in my database
My database is like this, which I circle, that's the data I want to filter later
I have added a checkbox and the paging library to Google's RoomWithAView codelab and the call to DiffUtil.ItemCallback seems to be passing the updated version of the entity to both the oldItem and newItem parameters.
My checkbox checked state is driven by a boolean field in the database named "isSelected" which gets updated when the row is clicked and this should cause the checkbox state to change.
The problem is that when I update the "isSelected" field (from false to true for example), the following Log printing returns true for both items. My checkbox state doesn't change because areContentsTheSame returns true and onBindViewHolder isn't called. I can force this to return false, but I want to understand what is going wrong:
private static DiffUtil.ItemCallback<WordEntity> DIFF_CALLBACK =
new DiffUtil.ItemCallback<WordEntity>() {
#Override
public boolean areItemsTheSame(WordEntity oldItem, WordEntity newItem) {
Log.i("CLEAN_LOG","areItemsTheSame: " +
Boolean.toString(oldItem.getWordId()==newItem.getWordId()));
return oldItem.getWordId() == newItem.getWordId();
}
#Override
public boolean areContentsTheSame(WordEntity oldItem, WordEntity newItem) {
Log.i("CLEAN_LOG","oldItem: " +
Boolean.toString(oldItem.getIsSelected()));
Log.i("CLEAN_LOG","newItem: " +
Boolean.toString(newItem.getIsSelected()));
Log.i("CLEAN_LOG","areContentsTheSame: " +
Boolean.toString(oldItem.getIsSelected() == newItem.getIsSelected()));
return oldItem.getIsSelected() == newItem.getIsSelected();
}
};
Here is my PagedListAdapter:
public static class WordListAdapter extends PagedListAdapter<WordEntity, WordListAdapter.WordViewHolder> {
protected WordListAdapter() {
super(DIFF_CALLBACK);
setHasStableIds(true);
}
#NonNull
#Override
public WordViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(parent.getContext())
.inflate(R.layout.recyclerview_item, parent, false);
return new WordViewHolder(itemView);
}
#Override
public void onBindViewHolder(#NonNull WordViewHolder holder, int position) {
WordEntity current = getItem(position);
if (current != null) {
holder.bindTo(current);
}
}
private static DiffUtil.ItemCallback<WordEntity> DIFF_CALLBACK =
new DiffUtil.ItemCallback<WordEntity>() {
#Override
public boolean areItemsTheSame(WordEntity oldItem, WordEntity newItem) {
Log.i("CLEAN_LOG","areItemsTheSame: " +
Boolean.toString(oldItem.getWordId()==newItem.getWordId()));
return oldItem.getWordId() == newItem.getWordId();
}
#Override
public boolean areContentsTheSame(WordEntity oldItem, WordEntity newItem) {
Log.i("CLEAN_LOG","oldItem: " +
Boolean.toString(oldItem.getIsSelected()));
Log.i("CLEAN_LOG","newItem: " +
Boolean.toString(newItem.getIsSelected()));
Log.i("CLEAN_LOG","areContentsTheSame: " +
Boolean.toString(oldItem.getIsSelected() == newItem.getIsSelected()));
return oldItem.getIsSelected() == newItem.getIsSelected();
}
};
#Override
public long getItemId(int position) {
WordEntity current = getItem(position);
return current.mWordId;
}
class WordViewHolder extends RecyclerView.ViewHolder {
TextView wordItemView;
CheckBox checkBox;
LinearLayout viewForeground;
public void bindTo(WordEntity word) {
wordItemView.setText(word.mWord);
checkBox.setChecked(word.mIsSelected);
}
private WordViewHolder(View itemView) {
super(itemView);
viewForeground = itemView.findViewById(R.id.viewForeground);
wordItemView = itemView.findViewById(R.id.textView);
checkBox = itemView.findViewById(R.id.checkBox);
checkBox.setClickable(false);
viewForeground.setOnLongClickListener(new View.OnLongClickListener() {
#Override
public boolean onLongClick(View view) {
final WordEntity thisWord = getItem(getAdapterPosition());
if (thisWord != null) {
Toast.makeText(context,
"You long-clicked: " + thisWord.getWord(),
Toast.LENGTH_LONG).show();
}
// returning false here will alow onClickListener to trigger as well
return true;
}
});
viewForeground.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
final WordEntity thisWord = getItem(getAdapterPosition());
if (thisWord != null) {
if (thisWord.getIsSelected()) {
thisWord.setIsSelected(false);
} else {
thisWord.setIsSelected(true);
}
mWordViewModel.update(thisWord);
}
}
});
}
}
}
Here is my observer:
mWordViewModel = ViewModelProviders.of(this).get(WordViewModel.class);
mWordViewModel.getAllWords().observe(this, new Observer<PagedList<WordEntity>>() {
#Override
public void onChanged(#Nullable final PagedList<WordEntity> words) {
// Update the cached copy of the words in the adapter.
adapter.submitList(words);
if (words != null) {
wordCount = words.size();
} else {
wordCount = 0;
}
Log.i(LOG_TAG,"Word Count: " + Integer.toString(wordCount));
}
});
All the Room database updates are happening properly
Based on the Log, it seems that areItemsTheSame is getting called twice when a row is tapped and areContentsTheSame is getting called once
I was expecting oldItem.getIsSelected() to be false and new.Item.getIsSelected() to be true and then onBindViewHolder would be fired. I also expected areItemsTheSame and areContentsTheSame to only get called once for each item.
Can someone help me understand what is going wrong here and if my expectations are in line with what should be happening?
Here is a GitHub with my sample app:
https://github.com/DanglaGT/RoomWithAViewPaging
I meet the same program with you, thanks to this answer, I finally find that to use the Paging Library, you need to make sure that the PagedList and it's item BOTH a new object from the old one.
There is my solution:
if (thisWord.getIsSelected()) {
thisWord.setIsSelected(false);
} else {
thisWord.setIsSelected(true);
}
WordEntity newWord = copyNewObject(thisWord);
mWordViewModel.update(newWord);
I am running into an issue where I use 2 separate adapters, and 2 seperate GridViews (1 Adapter, for 1 GridView), and am basically removing items from either adapter, and adding it to the other if the item is pressed, and vice versa.
When I press and item in one GridView, the item will be removed from its respective adapter and put into the other GridView and its respective adapter. The issue seems to be with the way the data is being stored (potentially) because when I add the item to the adapter, the icons are totally different from what they should be.
Note: I also noticed that this only happens for the items pressed after the first one is pressed. So the first items data will be added/removed properly. However, I have found that when the item that takes on the first position of the associated adapter, will now have the icon of the previously removed item.
What can I do so ensure that the data stays consistent with the item that is pressed?.
Adapter Class
public class MiscChargeOptionsAdapter extends ArrayAdapter<CarClassSettingDetails> {
private LayoutInflater inflater;
public MiscChargeOptionsAdapter(Context context, List<CarClassSettingDetails> settingList) {
super(context, R.layout.misc_charge_option_layout, R.id.option_grid_option_text, settingList);
inflater = LayoutInflater.from(context);
}
#SuppressLint("InflateParams")
#Override
public View getView(int position, View convertView, ViewGroup parent) {
CarClassSettingDetails settingsDetails = this.getItem(position);
ViewHolder vh;
if (convertView == null) {
vh = new ViewHolder();
convertView = inflater.inflate(R.layout.misc_charge_option_layout, null);
vh.optionText = (TextView) convertView.findViewById(R.id.option_grid_option_text);
vh.settingView = (SettingView) convertView.findViewById(R.id.option_grid_option_icon);
convertView.setTag(vh);
} else {
vh = (ViewHolder) convertView.getTag();
}
vh.optionText.setText(settingsDetails.getName());
vh.settingView.setIcon(settingsDetails.getIcon());
vh.settingView.setIconSelected(settingsDetails.getIconSelected());
vh.settingView.setViewBackgroundColor(settingsDetails.getViewBackgroundColor());
vh.settingView.setBackgroundColorSelected(settingsDetails.getBackgroundColorSelected());
vh.settingView.setAmountBackgroundColor(settingsDetails.getAmountBackgroundColor());
vh.settingView.setAmountTextColor(settingsDetails.getAmountTextColor());
vh.settingView.setValue(settingsDetails.getValue());
vh.settingView.setIgnoreSetState(true);
vh.settingView.setTag(position);
return convertView;
}
class ViewHolder {
private TextView optionText;
private SettingView settingView;
/* public ViewHolder(TextView optionText, SettingView settingView) {
this.optionText = optionText;
this.settingView = settingView;
}*/
public TextView getOptionText() {
return optionText;
}
public SettingView getSettingView() {
return settingView;
}
}
}
How the adapters are set
selectedAdapter = new CarClassOptionsAdapter(getActivity(), selectedSettingsList);
unselectedAdapter = new CarClassOptionsAdapter(getActivity(), unselectedSettingsList);
gridSelected.setAdapter(selectedAdapter);
gridSelected.setOnItemClickListener(this);
gridUnselected.setAdapter(unselectedAdapter);
gridUnselected.setOnItemClickListener(this);
How I am adding/removing items from the adapters
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
if (parent.getAdapter() == selectedAdapter) {
//TODO: ADD DIALOGS
unselectedAdapter.add(selectedAdapter.getItem(position));
unselectedAdapter.notifyDataSetChanged();
selectedAdapter.remove(selectedAdapter.getItem(position));
selectedAdapter.notifyDataSetChanged();
} else if (parent.getAdapter() == unselectedAdapter) {
//TODO: ADD DIALOGS
selectedAdapter.add(unselectedAdapter.getItem(position));
selectedAdapter.notifyDataSetChanged();
unselectedAdapter.remove(unselectedAdapter.getItem(position));
unselectedAdapter.notifyDataSetChanged();
}
}
This is how the CarClassSettingDetails class is being populated
private void addMiscChargeSelected(Ws_MiscChargeSelected miscChargeSelected, boolean isSelected) {
try {
CarClassSettingDetails settingDetails = new CarClassSettingDetails(getActivity());
if (miscChargeSelected.getCode() != null && !miscChargeSelected.getCode().equals("")) {
settingDetails.setCode(miscChargeSelected.getCode());
}
if (miscChargeSelected.getName() != null && !miscChargeSelected.getName().equals("")) {
settingDetails.setName(miscChargeSelected.getName());
}
if (miscChargeSelected.getIcon() != null && !miscChargeSelected.getIcon().equals("")) {
Bitmap settingIcon = IconUtils.loadIcon(getActivity(), miscChargeSelected.getIcon());
Bitmap settingIconSelected = IconUtils.loadIcon(getActivity(), miscChargeSelected.getIcon());
settingDetails.setIcon(settingIcon);
settingDetails.setIconSelected(settingIconSelected);
}
if (miscChargeSelected.getValue() != null && !miscChargeSelected.getValue().equals("")) {
settingDetails.setValue(Ws_Value.fromInt(Integer.parseInt(miscChargeSelected.getValue())));
settingDetails.setAmountBackgroundColor(Color.parseColor("#00ffffff"));
settingDetails.setAmountTextColor(Color.parseColor("#ff00428e"));
} else {
settingDetails.setValue(null);
}
settingDetails.setViewBackgroundColor(Color.parseColor("#ffd4d4d4"));
settingDetails.setBackgroundColorSelected(Color.parseColor("#ff616161"));
if (isSelected) {
//TODO: ADD TO TOP ADAPTER
selectedSettingsList.add(settingDetails);
} else {
//TODO: ADD TO BOTTOM ADAPTER
unselectedSettingsList.add(settingDetails);
}
} catch (Exception e) {
Log.e(TAG, e.getMessage());
}
}
Misc Charge Details
public class MiscChargeSettingDetails {
private Context context;
private String code;
private String name;
private String description;
private Bitmap icon = null;
private Bitmap iconSelected = null;
private Ws_Value value = null;
private int amountBackgroundColor, amountTextColor, viewBackgroundColor, backgroundColorSelected;
public MiscChargeSettingDetails(Context context) {
this.context = context;
}
protected Context getContext() {
return context;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Bitmap getIcon() {
return icon;
}
public void setIcon(Bitmap icon) {
this.icon = icon;
}
public Bitmap getIconSelected() {
return iconSelected;
}
public void setIconSelected(Bitmap iconSelected) {
this.iconSelected = iconSelected;
}
public Ws_Value getValue() {
return value;
}
public void setValue(Ws_Value value) {
this.value = value;
}
public int getAmountBackgroundColor() {
return amountBackgroundColor;
}
public void setAmountBackgroundColor(int amountBackgroundColor) {
this.amountBackgroundColor = amountBackgroundColor;
}
public int getAmountTextColor() {
return amountTextColor;
}
public void setAmountTextColor(int amountTextColor) {
this.amountTextColor = amountTextColor;
}
public int getViewBackgroundColor() {
return viewBackgroundColor;
}
public void setViewBackgroundColor(int viewBackgroundColor) {
this.viewBackgroundColor = viewBackgroundColor;
}
public int getBackgroundColorSelected() {
return backgroundColorSelected;
}
public void setBackgroundColorSelected(int backgroundColorSelected) {
this.backgroundColorSelected = backgroundColorSelected;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
}
Can you show the place where you're populating the list of SettingViews? Is your data model (SettingView) extends View? It is not the best idea to use the view object as model..
On my app I have a ListView with Checkboxes and have an adapter that extends BaseAdapterto populate the ListView. The basic function of the list is to show debts and the user can choose which ones to pay by checking the boxes, you have the total at the bottom of the list that gets updated when the user adds/removes an item. Now, some debts are related to another and if the user checks one, any other related debt should be marked as well and same if you uncheck a debt. I also have a button that should clear all the selected debts on the list, and that's where my problem is.
I keep record of the selected debts on an ArrayList and it seems to work for all the program but the clear button. When the button is pressed the selected debts list seems to be always empty. Any idea on what could be happening?
Here is my adapter:
public class ServicesFinancialStatusAdapter extends BaseAdapter implements CompoundButton.OnCheckedChangeListener{
private Context context;
private List<Debts> debtsList;
private List<Debts> selectedDebts;
private LayoutInflater inflater;
private int tabPosition;
private float total;
private OnTotalChangedListener listener;
public ServicesFinancialStatusAdapter(Context context, List<Debts> debtsList, int tabPosition) {
this.context = context;
this.debtsList = debtsList;
this.tabPosition = tabPosition;
this.total = 0;
this.inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
this.selectedDebts = new ArrayList<Debts>();
}
#Override
public int getCount() {
return debtsList.size();
}
#Override
public Object getItem(int i) {
return debtsList.get(i);
}
#Override
public long getItemId(int i) {
return i;
}
#Override
public View getView(int i, View view, ViewGroup viewGroup) {
ViewHolder holder;
if (view == null) {
holder = new ViewHolder();
view = inflater.inflate(R.layout.item_services_financial_status, viewGroup, false);
holder.concept = (TextView) view.findViewById(R.id.payment_concept);
holder.descriptionOrDate = (TextView) view.findViewById(R.id.payment_description_date);
holder.amount = (TextView) view.findViewById(R.id.payment_amount);
holder.expirationDate = (TextView) view.findViewById(R.id.payment_expiration_date);
if (tabPosition > 4) {
holder.checkBox = (CheckBox) view.findViewById(R.id.check_box);
holder.checkBox.setOnCheckedChangeListener(this);
}
view.setTag(holder);
} else
holder = (ViewHolder) view.getTag();
Debts item = debtsList.get(i);
holder.concept.setText(item.getConcept());
holder.amount.setText(item.getAmountToString());
if (item.isExpired())
holder.expirationDate.setText(context.getString(R.string.expired_status_indicator));
else
holder.expirationDate.setText(context.getString(R.string.debts_expiration_date_indicator) + item.getExpirationDate());
if (tabPosition < 3)
holder.descriptionOrDate.setText(item.getDescription());
else if (tabPosition < 5)
holder.descriptionOrDate.setText(item.getDate());
else {
holder.descriptionOrDate.setVisibility(View.GONE);
holder.checkBox.setVisibility(View.VISIBLE);
holder.checkBox.setTag(i);
holder.checkBox.setChecked(item.isSelected());
}
return view;
}
#Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
//Get the position of the item clicked and the data object
Integer position = (Integer) buttonView.getTag();
Debts item = debtsList.get(position);
//Change the status ob the object
item.setSelected(isChecked);
for (Debts debts : debtsList) {
//Check on the list for related objects and marks them as well
if (debts.getConceptId() == item.getRelatedDebt())
debts.setSelected(isChecked);
//Get the amount of the debt and add/remove it from
//the selectedDebts list and update the total
float amount = debts.getAmount();
if (debts.isSelected()) {
if (!selectedDebts.contains(debts)) {
selectedDebts.add(debts);
listener.onTotalChanged(addToTotal(amount), selectedDebts);
}
}
else {
if (selectedDebts.contains(debts)) {
selectedDebts.remove(debts);
listener.onTotalChanged(removeFromTotal(amount), selectedDebts);
}
}
}
//Finally update the UI
notifyDataSetChanged();
}
//Anywhere else in the code selectedDebts has the right data but here
//Here the size of the list is always 0
public void unMarkDebts() {
for (Debts debts : debtsList) {
//Get the amount of the debt and remove it from
//the selectedDebts list, set the data object as unselected
//and update the total
float amount = debts.getAmount();
if (selectedDebts.contains(debts)) {
debts.setSelected(false);
selectedDebts.remove(debts);
listener.onTotalChanged(removeFromTotal(amount), selectedDebts);
}
}
//Update the UI
notifyDataSetChanged();
}
private float addToTotal(float value) {
return total += value;
}
private float removeFromTotal(float value) {
return total -=value;
}
public interface OnTotalChangedListener{
public void onTotalChanged(float total, List<Debts> selectedDebts);
}
public void setOnTotalChangedListener(OnTotalChangedListener listener) {
this.listener = listener;
}
private class ViewHolder {
TextView concept, descriptionOrDate, amount, expirationDate;
CheckBox checkBox;
}
}
And here is my fragment code:
public class CheckoutFragment extends BaseFragment implements View.OnClickListener, ServicesFinancialStatusAdapter.OnTotalChangedListener {
public static final String TAG = CheckoutFragment.class.getSimpleName();
private List<Debts> debtsList;
private List<Debts> selectedDebts;
private ServicesFinancialStatusAdapter adapter;
private TextView totalView, positiveBalanceView;
private View noDebtsView, header, footer;
private LinearLayout mainLayout;
private float total, positiveBalance;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
debtsList = new ArrayList<Debts>();
adapter = new ServicesFinancialStatusAdapter(getActivity(), debtsList, 5);
adapter.setOnTotalChangedListener(this);
webServices = ((MainActivity) getActivity()).getNetworkInstance();
}
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
//Initialize the views
...
reloadData();
onTotalChanged(0, null);
return view;
}
#Override
public void onClick(View view) {
reloadData();
}
#Override
public void onTotalChanged(float total, List<Debts> selectedDebts) {
this.total = total == 0 ? total : total - positiveBalance;
this.selectedDebts = selectedDebts;
totalView.setText(getString(R.string.total_indicator) + Utilities.formatNumberAsMoney(this.total));
getActivity().invalidateOptionsMenu();
}
private void reloadData() {
debtsList.clear();
adapter = new ServicesFinancialStatusAdapter(getActivity(), debtsList, 5);
adapter.setOnTotalChangedListener(this);
loadData();
}
private void loadData() {
//Load debts from server
...
}
private void saveSelectedDebts() {
for (Debts selectedDebt : selectedDebts) {
long id = Debts.insert(getActivity(), selectedDebt);
//Log.d(TAG, "Inserted " + selectedDebt.getConcept() + " with ID " + id);
}
}
#Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
if (!((MainActivity) getActivity()).drawerIsOpen) {
inflater.inflate(R.menu.checkout, menu);
}
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.clear:
adapter.unMarkDebts();
break;
case R.id.checkout:
selectPaymentMode();
break;
}
return super.onOptionsItemSelected(item);
}
private void selectPaymentMode() {
...
}
}
The chat broke. I think one investigation could be:
Everytime you call reload, you are initiating from scracth the adapter with a new object, (call method new). Remove that line and just use the previous adapter (taking care of initiating the adapteer on onCreate). Notice that the vairable selectedDebts is local to the adapter and you are not passing it in the constructor.