I have a fragment in which I have a RecyclerView:
public class AlarmListFragment extends Fragment{
public AlarmListAdapter alarmListAdapter;
RecyclerView recyclerViewAlarms;
public AlarmListFragment() { }
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_alarm_list, container, false);
recyclerViewAlarms = (RecyclerView) rootView.findViewById(R.id.fragment_alarm_list_card_list);
recyclerViewAlarms.setHasFixedSize(true); // performance!
LinearLayoutManager llm = new LinearLayoutManager(getActivity());
recyclerViewAlarms.setLayoutManager(llm);
alarmListAdapter = new AlarmListAdapter(getActivity());
recyclerViewAlarms.setAdapter(alarmListAdapter);
return rootView;
}
It uses the following Adapter:
public class AlarmListAdapter extends RecyclerView.Adapter<AlarmListViewHolder> {
private List<AlarmModel> alarmModelDataSet;
private Context context;
public AlarmListAdapter(Context context) {
this.alarmModelDataSet = AlarmModel.listAll(AlarmModel.class);
this.context = context;
}
@Override
public AlarmListViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
View view = inflater.inflate(R.layout.alarm_list_card, parent, false);
return new AlarmListViewHolder(view);
}
@Override
public int getItemCount() {
return (int) AlarmModel.count(AlarmModel.class, null, null);
}
@Override
public void onBindViewHolder(AlarmListViewHolder holder, int position) {
// todo wait here until alarmModel is added to DataSet
AlarmModel alarmModel = alarmModelDataSet.get(position);
*** update GUI stuff ***
if (alarmModel.isEnabled()) {
*** update GUI stuff ***
}
if (alarmModel.isRepeatWeekly()) {
*** update GUI stuff ***
} else {
*** update GUI stuff ***
}
if (!alarmModel.isEnabled()){
*** update GUI stuff ***
}
}
public List<AlarmModel> getDataSet(){
return alarmModelDataSet;
}
public int getIndex(AlarmModel alarmModel){
for (AlarmModel _item : alarmModelDataSet){
if (_item.getId().equals(alarmModel.getId())){
return alarmModelDataSet.indexOf(_item);
}
}
return -1;
}
public void addOrUpdateAlarm(AlarmModel alarmModel){
int position = getIndex(alarmModel);
if (position >= 0) {
updateAlarm(alarmModel, position);
} else {
addAlarm(alarmModel);
}
}
private void addAlarm(AlarmModel alarmModel){
alarmModelDataSet.add(alarmModel);
notifyItemInserted(alarmModelDataSet.size() - 1);
}
private void updateAlarm(AlarmModel alarmModel, int position){
alarmModelDataSet.set(getIndex(alarmModel), alarmModel);
notifyItemChanged(position);
}
public void deleteAlarm(AlarmModel alarmModel) {
alarmModel.setIsEnabled(false);
AlarmManagerBroadcastReceiver.setAlarms(context);
int position = getIndex(alarmModel);
alarmModelDataSet.remove(position); // deletes out of class internal List
notifyItemRemoved(position); // notifies list fragment of deletion
}
public void enableOrDisableAlarm(int position){
AlarmModel alarmModel = alarmModelDataSet.get(position);
alarmModel.setIsEnabled(!alarmModel.isEnabled());
notifyItemChanged(position);
}
Every alarmModel is saved in a database which is working fine thus I removed the code about the database.
The fragment from the first code snippet uses startActivityForResult
to open up an activity that allows the creation of a new alarm that is stored in another alarmModel
. This new alarmModel is saved to the data base and its ID is returned to my fragment by this code
Intent intent = new Intent();
intent.putExtra("id", alarmDetails.getId());
intent.putExtra("delete", false);
setResult(RESULT_OK, intent);
supportFinishAfterTransition();
This result is received by the onActivityResult method of the fragment:
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == Activity.RESULT_OK && requestCode == REQUEST_CODE_ADD_ALARM_ID) {
long id = data.getLongExtra("id", -7777);
if (data.getBooleanExtra("delete", false)) { // delete
alarmListAdapter.deleteAlarm(AlarmModel.findById(AlarmModel.class, id));
} else { // add or update
alarmListAdapter.addOrUpdateAlarm(AlarmModel.findById(AlarmModel.class, id));
}
}
}
After all this code I finally am able to describe my problem:
Before the code even reaches the addOrUpdate
method, the adapter's onBindViewHolder
method is called and produces an IndexOutOfBoundsException
because AlarmModel alarmModel = alarmModelDataSet.get(position);
the just added alarmModel wants to get drawn but is not yet added to the DataSet.
Everything works fine (although the app becomes pretty slow) when the RecyclerView has ~10+ items since onBindViewHolder
is called for every other item first and after calculating 9 other items the addOrUpdate
method has finished.
Is there a fatal mistake I made or a method I did not find yet, which could help me? I thought about using two threads and letting one wait until the other one has finished but am unsure how to do that since I allready know that you should never block the Ui-Thread.
Edit 1
In an attempt to make the add, update and delete method in the adapter static I removed the List alarmModelDataSet
completely and queried the data base every time instead. Although my initial plan did not work out, I noticed that it solved my problem by making the methods so slow that everything worked fine again. But since this is not a real solution but simply bad coding that works slowly I am not really satisfied with it...
Thanks to everybody reading this long question/problem
Tafelbomber