Search code examples
javaandroidlistviewonitemclicklistener

ListView hangs when i click on an Item


I have a selectWord() function that populates two strings and an ArrayList, then it puts them(strings) into ListView and TextView.

What I want to do is when someone clicks on the listItem, the strings and ArrayList should change their values and put new values in TextView and ListView.

I created a function that selects words from text file and then shows that data into views, in ClickListener; what I did is call to the same function again so that it selects data from text file and put it into the views. (a quiz type app, select an option and then next question)

I wrote similar code couple of days ago that work.

public class MainActivity extends AppCompatActivity {

    private ArrayList<String> words = new ArrayList<>();    //words list
    private ArrayList<String> defns = new ArrayList<>();    //deffinitions
    private String word;
    private String correct;
    public ArrayList<String> randOptions = new ArrayList<>();
    private Random randy = new Random();
    private TextView wordView;
    private ListView optionView;

    public void readFile() { //works fine
        //populate the ArrayLists
        String word, defn;
        Scanner file = new Scanner(getResources().openRawResource(raw.dictionary1));

        while(file.hasNextLine()) {
              String line = file.nextLine();
              String[] lineArray = line.split(" ");
              if (lineArray.length >= 2) {
                  word = lineArray[0];
                  defn = lineArray[1];
                  words.add(word);
                  defns.add(defn);
              }
          }
    }

    public void selectWord() {

        readFile(); //read file
        //get some data
        int rand = randy.nextInt(words.size());
        this.word = words.get(rand);
        this.correct = defns.get(rand);

        //make 4 diff options
        randOptions.add(correct);

        for(int i=0; i<3; i++) {
            rand = randy.nextInt(defns.size());
            if(randOptions.contains(defns.get(rand)))
                    i--;
            else
                randOptions.add(defns.get(rand));
        }
        Collections.shuffle(randOptions);

        //add the data to views
        wordView.setText(this.word);
        ArrayAdapter<String> adapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, randOptions);
        optionView.setAdapter(adapter);
    }

    @Override
    protected void onCreate(Bundle bundle) {
        super.onCreate(bundle);
        setContentView(layout.activity_main);

        wordView = findViewById(id.currentWord);
        optionView = findViewById(id.options);

        selectWord();

        optionView.setOnItemClickListener(
              new AdapterView.OnItemClickListener() {
                  @Override
                  public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
                       String selected = ((TextView) view).getText().toString();
                       if (correct.equals(selected)) {
                           Toast.makeText(MainActivity.this, "Right", Toast.LENGTH_SHORT).show();
                       } else {
                              Toast.makeText(MainActivity.this, "Wrong", Toast.LENGTH_SHORT).show();
                       }
                       selectWord(); //so that it changes the vlaues in views but when I add that 
                       //line my hangs there soon as I click on the list item
               }
           }
       );    
}

Solution

  • optionView.setOnItemClickListener(...) uses AdapterView. So when you call selectWord(); from inside this ClickListener it hangs... Why?: Simply because you are re-creating the ArrayAdapter<String> and setting it again in the ListView.

    Instead of asking it to kill itself (i.e. recreate itself from ground), you can ask the ArrayAdapter to change its data so the ListView will still use the same ArrayAdapter. In this case, you should notify the changes, something like this:

    1. First remove ArrayAdapter<String> adapter = .. and optionView.setAdapter from selectWord() method.
    2. Create global ArrayAdapter and set it in the ListView outside the selectWord() method.
    3. Then every time you want to change the options ..etc call selectWord().
    4. Then clear the ArrayAdapter and repopulate it again.
    5. Finally notify changes.

    // create this method to update the options (re-populate the ArrayAdapter)
    // of course ArrayAdapter and randOptions should be GLOBAL
    public void updateOptions() {
        adapter.clear(); 
        if (randOptions != null){
           for (String option : randOptions) {
               adapter.insert(option,adapter.getCount());
            }
        }     
        adapter.notifyDataSetChanged();
    } 
    

    // first declare the ArrayAdapter globally as a field
    ArrayAdapter<String> adapter;
    
    // inside onCreate() method, initialize the ArrayAdapter (i.e. outside the `selectWord()` method) 
    // using this constructor: ArrayAdapter(Context context, int resource)
    adapter = new ArrayAdapter<>(this,android.R.layout.simple_list_item_1);
    
    // after that, set the adapter (it's empty by now)
    optionView.setAdapter(adapter);
    
    // fill the Random Options Array
    selectWord();
    // call update options
    updateOptions();
    
    // and use it inside the CLickListener like this
    optionView.setOnItemClickListener(
              new AdapterView.OnItemClickListener() {
                @Override
                public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
                    // whatever ...
                    selectWord(); //so that it changes the options in the array
                    updateOptions(); // update options in the same ArrayAdapter
    })});