Search code examples
javaandroidrealmandroid-arrayadapter

Realm calling onChange addChangeListener continuously


Apologies if this is poorly explained, I am having difficulty in understanding it myself. If you point out anything you don't understand I will do my best to correct any issues. Okay so here we go.

Several classes. (D&D sheet, sheet has weapons user can equip, this is about equipping said weapons which is stored in a list)

What I've gathered so far is that when I create a new attackListViewContentAdapter it loops at a rapid and continued pace. So much so that the screen does not respond to me touching any of the widgets. I've done things like log a number each time it passes so it shows when it's doing it again and again. If you need information on that I can show you where I put the logs and what shows in my Logcat when I add an additional view (row).

I believe that it's something to do with the onChangedListener which keeps being triggered, even if I found the reason why how do I then get to a stage where I can create a new view and have the listener so it can record changes.

Please note in the interests of space I will be using abbreviated code. I've ignored things like dialog boxes and widgets which aren't relevant. So if it seems like something missing or you need to view the classes, it's possibly in the file which I've linked above each one.

CombatFragment

public class CombatFragment extends Fragment {
@BindView(R.id.lv_attack_spellcasting_content)
ListView lv_attack_spellcasting_title;


@Nullable
@Override
public View onCreateView(final LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
    View rootView = inflater.inflate(R.layout.content_combat, container, false);
    RealmList<Weapon> weaponList = sheet.getWeaponList();
    final AttackListViewContentAdapter attackListViewContentAdapter = new AttackListViewContentAdapter(getActivity(), sheet, realm, weaponList);
    weaponList.addChangeListener(new RealmChangeListener<RealmList<Weapon>>() {
        @Override
        public void onChange(RealmList<Weapon> weapons) {
            /* Gives the adaptor a kick to know that the weapon realm list has changed */
            attackListViewContentAdapter.notifyDataSetChanged();
            loopOnChanged++;
        }
    });
    lv_attack_spellcasting_title.setAdapter(attackListViewContentAdapter);
    playerInit();
    return rootView;
}

   // This is a fake method, this is just to show that the .add is in it's own method which is triggered by a button press and not in onCreate
   public void buttonPress() {
       sheet.getWeaponList().add(realm.createObject(Weapon.class));
   }
} `

AttackListViewContentAdapter

public class AttackListViewContentAdapter extends ArrayAdapter<Weapon> {
        public AttackListViewContentAdapter(Context context, Sheet sheet, Realm realm, List<Weapon> weaponList) {
        super(context, 0, weaponList);
        this.sheet = sheet;
        this.realm = realm;
    }
    @Override
    @NonNull
    public View getView(int position, View convertView, ViewGroup parent) {
        if (convertView == null)
        //Because you're returning the view (AttachToRoot is false) the ArrayAdaptor (This class) will handle adding the view to the list.
        convertView = 
        LayoutInflater.from(getContext()).inflate(R.layout.attack_list_item, parent, false);
        return convertView;
    }
}

Weapon

public class Weapon extends RealmObject {
    @PrimaryKey
    int weaponID;
    //properties, set get methods etc.
}

Sheet

public class Sheet extends RealmObject {
    @PrimaryKey
    private int sheetID;
    private RealmList<Weapon> weaponList;

    public RealmList<Weapon> getWeaponList() {
        return weaponList;
    }

    public void setWeaponList(RealmList<Weapon> weaponList) {
        this.weaponList = weaponList;
    }
}

content_combat

            <ListView
            android:id="@+id/lv_attack_spellcasting_content"
            android:layout_width="fill_parent"
            android:layout_height="fill_parent"

            android:columnCount="7"
            android:rowCount="1" />

attack_list_item

Nothing really in there to include


Solution

  • Your problem is happening because of bad initialization of your spinner widgets within AttackListViewContentAdapter class.

    1. You must set your spinners selection before setOnItemSelectedListener is set.
    2. You must check whether your spinner selection is not equal to current selection, to avoid an infinite loop between onChange and onItemSelected methods. I mean, your spinners onItemSelected callbacks, execute a realm transanctions, then, those transanctions fire your onChange callback and finally, your onChange callback invokes notifyDataSetChanged() which make cycle start again going into an infinite loop.

    To solve your problem, you should follow the next steps inside AttackListViewContentAdapter.java:

    A) Remove the following lines from addWeaponToUI() method:

    private void addWeaponToUI() {
        et_name_value.setText(weapon.getWeaponName());
        np_damage_number_of_die_value.setValue(weapon.getWeaponDamageNumberOfDie());
        SheetEnum.Ability ability = SheetEnum.Ability.getEnumValue(weapon.getWeaponAbilityBonusInt());
        tv_attack_bonus_value.setText(String.valueOf(sheet.getAbilityBonus(ability)));
    
        // REMOVE below lines!
        //s_damage_die_type_value.setSelection(weapon.getWeaponDamageDieTypeInt());
        //s_damage_type_value.setSelection(weapon.getWeaponDamageTypeInt());
        //s_ability_bonus_value.setSelection(weapon.getWeaponAbilityBonusInt());
    }
    

    B) Invoke spinner setSelection() before setOnItemSelectedListener(), then check selected item is not equal to selected position to avoid an infinite loop:

    ArrayAdapter<CharSequence> damageDieTypeAdapter = ArrayAdapter.createFromResource(getContext(), R.array.die_type, android.R.layout.simple_spinner_dropdown_item);
        s_damage_die_type_value.setAdapter(damageDieTypeAdapter);
        //Set selection before listener
        s_damage_die_type_value.setSelection(weapon.getWeaponDamageDieTypeInt());
        s_damage_die_type_value.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
            @Override
            public void onItemSelected(AdapterView<?> parent, View v, final int position, long id) {
                //Check selected position is not equal to current position to avoid an infinite loop
                if (position != weapon.getWeaponDamageDieTypeInt()) {
                    String[] value = getContext().getResources().getStringArray(R.array.die_type);
                    realm.executeTransaction(new Realm.Transaction() {
                    @Override
                    public void execute(Realm realm) {
                        weapon.setWeaponDamageDieType(position);
                    }
                });
            }
        }
    
        @Override
        public void onNothingSelected(AdapterView<?> parent) {
        }
    });
    

    C) Repeat Step B for s_damage_type_value and s_ability_bonus_value spinners