0

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 alarmModelDataSetcompletely 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

  • Why not make `getItemCount()` return `alarmModelDataSet.size()`? – Joseph Roque Jun 03 '15 at 19:55
  • @JosephRoque There is no reason. `getItemCount()` is the better alternative. Thanks for that but unfortunately it does not solve my problem. (btw please notice the edit =) – Tafelbomber Jun 05 '15 at 18:53
  • Are you sure? The adapter is trying to create an item for the alarm which you've created because your `getItemCount` method is returning a count of all the instances instead of the ones you have actually added to your dataset. I'm not sure of what else can be done if that doesn't help – Joseph Roque Jun 05 '15 at 18:59
  • @JosephRoque I thought your answer is not the solution to the problem because I changed a piece of code while I was trying to fix it. Figured it all out now and you were a 100% right. Thank you very much! – Tafelbomber Jun 05 '15 at 19:54
  • No problem, happy to help! – Joseph Roque Jun 05 '15 at 19:58

0 Answers0