0

I am attempting to implement a feature that allows me to search all my songs, which are stored in a HashMap called songsList. I have the songsList HashMap successfully implemented. I attempt to filter the results using a string typed by the user that is input when user click search icon, but nothing happens.

Any ideas why the songsList HashMap is not being filtered?

I tried case sensitive & case insensitive searches. I tried searching the full and exact song title for several songs. So I don't think it's the search strings I am testing with that is a problem, but rather something in my code.

playlist.xml layout

PlaylistActivity.java

import android.app.ListActivity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.SimpleAdapter;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Locale;

public class PlayListActivity extends ListActivity {

    private EditText editSearch;
    private ImageView searchIcon;
    private ListAdapter adapter;
    private ArrayList<HashMap<String, String>> songsListData;
    private boolean userEnteredSearchString = false;

    // Songs playlist_item
    public ArrayList<HashMap<String, String>> songsList = new ArrayList<HashMap<String, String>>();
    public ArrayList<HashMap<String, String>> filteredSongsList = new ArrayList<HashMap<String, String>>();

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.playlist);

        songsListData = new ArrayList<HashMap<String, String>>();

        final SongsManager songsManager = new SongsManager();
        // get all songs from SD card
        this.songsList = songsManager.getPlayList();

        createListViewUsingSongs();


        // Set up the layout elements for this activity
        editSearch = (EditText) findViewById(R.id.search);
        searchIcon = (ImageView) findViewById(R.id.search_icon);

        // Capture Text in EditText when user clicks search icon
        searchIcon.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View arg0) {
                String text = editSearch.getText().toString().toLowerCase(Locale.getDefault());
                filteredSongsList = songsManager.filter(text);
                userEnteredSearchString = true;
                createListViewUsingSongs();
            }
        });

        // selecting single ListView item
        ListView listView = getListView();
        // listening to single playlist_item item click
        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {

                                            @Override
                                            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                                                // getting list item index
                                                int songIndex = position;

                                                // Starting new intent
                                                Intent intent = new Intent(getApplicationContext(), MainActivity.class);
                                                // Sending songIndex to PlayerActivity
                                                intent.putExtra("songIndex", songIndex);
                                                setResult(100, intent);
                                                // Closing PlayListView
                                                finish();
                                            }
                                        }

        );
    }

    private void createListViewUsingSongs() {

        if (userEnteredSearchString) {
            // looping through playlist
            for (int i = 0; i < filteredSongsList.size(); i++){
                // creating new HashMap
                HashMap<String, String> song = songsList.get(i);
                // adding HashList to ArrayList
                songsListData.add(song);
            }
        } else {
            // looping through playlist
            for (int i = 0; i < songsList.size(); i++){
                // creating new HashMap
                HashMap<String, String> song = songsList.get(i);
                // adding HashList to ArrayList
                songsListData.add(song);
            }
        }

        // Adding menuItems to ListView
        adapter = new SimpleAdapter(this, songsListData,
                R.layout.playlist_item, new String[]{"songTitle"}, new int[]{
                R.id.songTitle});

        setListAdapter(adapter);
    }
}

SongsManager.java

import java.io.File;
import java.io.FilenameFilter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Locale;

public class SongsManager {

    private HashMap<String, String> song;
    private String songString;

    // SDCard Path
    final String MEDIA_PATH = "/storage/extSdCard/music";
    private ArrayList<HashMap<String, String>> songsList = new ArrayList<HashMap<String, String>>();
    private ArrayList<HashMap<String, String>> filteredSongsList = new ArrayList<HashMap<String, String>>();


    // Constructors
    public SongsManager(String song) {
        this.songString = songString;
    }

    public SongsManager() {
    }

    /**
     * Function to read all mp3 files from sdcard
     * and store the details in ArrayList
     */
    public ArrayList<HashMap<String, String>> getPlayList() {
        File home = new File(MEDIA_PATH);

        if (home.listFiles(new FileExtensionFilter()).length > 0) {
            for (File file : home.listFiles(new FileExtensionFilter())) {
                HashMap<String, String> song = new HashMap<String, String>();
                song.put("songTitle", file.getName().substring(0, (file.getName().length() - 4)));
                song.put("songPath", file.getPath());

                // Adding each song to SongList
                songsList.add(song);
            }
        }
        // return songs playlist_item array
        return songsList;
    }

    public String getSong() {
        return this.songString;
    }

    /**
     * Class to filter files which are having .mp3 extension
     */
    class FileExtensionFilter implements FilenameFilter {
        public boolean accept(File dir, String name) {
            return (name.endsWith(".mp3") || name.endsWith(".MP3") || name.endsWith(".wma"));
        }
    }

    // Filter Class
    public ArrayList<HashMap<String, String>> filter(String searchString) {
        searchString = searchString.toLowerCase(Locale.getDefault());

        songsList.clear();
        songsList = getPlayList();

        //searchString is empty, so show all songs in results
        if (searchString.length() == 0) {

            if (filteredSongsList != null){
                filteredSongsList.clear();
            }
            filteredSongsList = songsList;
        }

        //only return songs that match the search string
        else {

            if (filteredSongsList != null){
                filteredSongsList.clear();
            }

            for (HashMap<String, String> song : songsList) {
                if (song != null) {
                    String songTitle = song.get("songTitle");
                    if (songTitle.toLowerCase().contains(searchString)) {
                        filteredSongsList.add(song);
                    }
                }
            }
        }

        return filteredSongsList;
    }
}

playList.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="fill_parent"
              android:layout_height="fill_parent"
              android:orientation="vertical">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

    <EditText
        android:id="@+id/search"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_weight="4"
        android:hint="Type song &amp; click search icon"
        android:inputType="text"
        android:maxLength="40">
        <requestFocus />
    </EditText>

    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/search_icon"
        android:layout_weight="1"
        android:src="@drawable/ic_search"/>

    </LinearLayout>

    <ListView
        android:id="@android:id/list"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:divider="#242424"
        android:dividerHeight="1dp"
        android:listSelector="@drawable/list_selector" />

</LinearLayout>

Full code download available on Github: https://github.com/jogold9/Simple_Music_Player

joshgoldeneagle
  • 4,616
  • 2
  • 23
  • 30
  • Oops deleted my comment...I only see you adding to `songsListData` even after searching. Don't you need to clear it first to avoid showing duplicates? – George Mulligan Jan 27 '16 at 03:46
  • I think you may be correct about needing to clear songsListData to avoid duplicates. But duplicates is not my issue just yet. I am not seeing any changes in the ListView when I run a search. Probably multiple errors in this code. I'm hoping if I slowly step through code with debugger & look at the values of the vars, I might be able figure this out. – joshgoldeneagle Jan 27 '16 at 07:17
  • FYI I added the Github URL if anyone wants to try running the app & doing a search. You will need to change the Media path to the folder for your audio. I know that hard coding path is a no-no. That is my next thing to fix after this. – joshgoldeneagle Jan 27 '16 at 07:21
  • I added a counter to check if songs are actually getting add to the HashMap when searching. And the correct number of songs are being added! Well the search seems to be case sensitive, but I can fix that later. It seems I just have to figure out how to get the listView adapter to refresh properly. – joshgoldeneagle Jan 27 '16 at 22:26
  • 1
    Typically you do not replace an adapter on a `ListView` after it has been set. You just modify the collection the adapter is working with and then call `adapter.notifyDataSetChanged();` which then updates the `ListView`. – George Mulligan Jan 27 '16 at 22:29
  • @GeorgeMulligan Hmm can you show me with a code example what you mean? I agree there is probably something wrong with the last few lines of my PlayListActivity.java... looking into this now. – joshgoldeneagle Jan 27 '16 at 22:32
  • The accepted answer [here](http://stackoverflow.com/questions/9747553/listview-not-updating-with-notifydatasetchanged-call) should cover it. I am surprised though that simply setting a new adapter on the `ListView` is not updating it. Maybe there is something else going wrong there. – George Mulligan Jan 27 '16 at 22:35
  • @GeorgeMulligan Looks like I cannot call NotifyDataSetChanged method or clear method on ListAdapter. – joshgoldeneagle Jan 28 '16 at 00:18
  • 1
    Change it to `private SimpleAdapter adapter;` instead of using `ListAdapter`. Really it could even by a `BaseAdapter` since that is where `NotifyDataSetChanged` is specified. – George Mulligan Jan 28 '16 at 00:21
  • By the way I tried looking at the code in the GitHub repo but it wouldn't compile due to missing resources (images, layouts, etc...) – George Mulligan Jan 28 '16 at 00:29
  • @GeorgeMulligan Oops, I will get the resources added to Github. Probably really close to getting this resolved anyhow, but I definitely want all the icons and resources in the Github backup! – joshgoldeneagle Jan 28 '16 at 00:32
  • Uploaded all resources to Github – joshgoldeneagle Jan 28 '16 at 01:02
  • Stepped through the code with debugger finally, and found the solution! Shouldn't haven't waited so long to use the debugger. I will post the answer. Thanks @GeorgeMulligan for your help and encouragement! – joshgoldeneagle Jan 28 '16 at 01:50
  • No problem. Glad you got things figured out. – George Mulligan Jan 28 '16 at 01:51

1 Answers1

0

Resolution: The search was working just fine all along, but I neglected to do songsListData.clear();

So when refreshing the adapter, the full data set was shown instead of the filtered data set.

Don't be afraid to use the debugger everyone. I could have saved hours if I had followed that simple advice.

import android.app.ListActivity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.SimpleAdapter;
import android.widget.Toast;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Locale;

public class PlayListActivity extends ListActivity {

    private EditText editSearch;  //search text input by user
    private ImageView searchIcon;
    public SimpleAdapter simpleAdapter;
    private ListView listView;
    private ArrayList<HashMap<String, String>> songsListData;
    public ArrayList<HashMap<String, String>> songsList = new ArrayList<>();  //stores all the songs
    public ArrayList<HashMap<String, String>> filteredSongsList = new ArrayList<>();  //stores songs that match search
    private int songsAddedCounter = 0;  //counter for debugging -> are songs being added to list?

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.playlist);

        // Set up the layout elements for this activity
        editSearch = (EditText) findViewById(R.id.search);
        searchIcon = (ImageView) findViewById(R.id.search_icon);

        songsListData = new ArrayList<>();  //Stores all the songs to put into ListView

        final SongsManager songsManager = new SongsManager();
        // get all songs from SD card
        this.songsList = songsManager.getPlayList();  //gets all the songs from the phone and puts them in the HashMap

        createListViewUsingSongs();  //draws the ListView on the screen using the songsList HashMap

        // selecting single ListView item
        listView = getListView();

        // listening to single playlist_item item click
        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
                                            @Override
                                            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {

                                                int songIndex = position; // getting list item index

                                                // Starting new intent
                                                Intent intent = new Intent(getApplicationContext(), MainActivity.class);
                                                // Sending songIndex to PlayerActivity
                                                intent.putExtra("songIndex", songIndex);
                                                setResult(100, intent);
                                                // Closing PlayListView
                                                finish();
                                            }
                                        }

        );

        /**
         * When user clicks search icon, execute a search, then update the ListView simpleAdapter
         */
        searchIcon.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View arg0) {
                String text = editSearch.getText().toString().toLowerCase(Locale.getDefault());
                filteredSongsList = songsManager.filter(text);
                updateListViewUsingSongs();
            }
        });
    }

    private void createListViewUsingSongs() {
        // looping through playlist
        for (int i = 0; i < songsList.size(); i++) {
            // creating new HashMap
            HashMap<String, String> song = songsList.get(i);
            // adding HashList to ArrayList
            songsListData.add(song);

        }

        // Adding menuItems to ListView
        simpleAdapter = new SimpleAdapter(this, songsListData,
                R.layout.playlist_item, new String[]{"songTitle"}, new int[]{
                R.id.songTitle});

        setListAdapter(simpleAdapter);
    }

    private void updateListViewUsingSongs() {

        songsAddedCounter = 0;
        songsListData.clear();  //super important that we start from zero, and add only the filtered songs!

        // looping through playlist
        for (int i = 0; i < filteredSongsList.size(); i++) {
            // creating new HashMap
            HashMap<String, String> song = filteredSongsList.get(i);
            // adding HashList to ArrayList
            songsListData.add(song);
            songsAddedCounter++;
        }
        Toast.makeText(getApplicationContext(), "Search results: " + songsAddedCounter + " songs", Toast.LENGTH_SHORT).show();

        simpleAdapter = null;

        simpleAdapter = new SimpleAdapter(this, songsListData,
                R.layout.playlist_item, new String[]{"songTitle"}, new int[]{
                R.id.songTitle});


        setListAdapter(simpleAdapter);
        simpleAdapter.notifyDataSetChanged();
    }
}
joshgoldeneagle
  • 4,616
  • 2
  • 23
  • 30