-2

My goal is to use ArrayAdapter to load a ListView with 20 custom object of songs and have the adapter to be able to return appropriate song objects whose album name contains the string the user inputs in a search view.

So far the 20 songs show up as expected, however, the filter just doesn't work: change in the searchView yields no effect on the application. I tried setting the filter 'constraint' to a hard coded string, the filter doesn't respond to that either.

class file for the song objects:

public class Track {
private String mSongName;
private String mArtistName;
private String mAlbumName;
private int mAlbumRelease;
private int mAlbumArt;

public Track (String artistName, String albumName, String songName, int releaseDate, int imageID) {
    mArtistName = artistName;
    mAlbumName = albumName;
    mSongName = songName;
    mAlbumRelease = releaseDate;
    mAlbumArt = imageID;
}
public String getArtistName(){
    return mArtistName;
}
public String getAlbumName(){
    return mAlbumName;
}
public String getSongName(){
    return mSongName;
}
public String getReleaseDate(){
    return Integer.toString(mAlbumRelease);
}
public int getAlbumArt(){
    return mAlbumArt;
}
}

class file used to load an ArrayList with 20 songs:

public class RecordMixTape {
ArrayList<Track> mixTape = new ArrayList<>();
public void record (){
    mixTape.add(new Track("Lana del Rey", "Ultraviolence", "Black Beauty", 2014, R.drawable.ultraviolence));
    mixTape.add(new Track("Lana del Rey", "Young and Beautiful", "Young and Beautiful", 2013, R.drawable.young_and_beautiful));
    mixTape.add(new Track("Childish Gambino", "Redbone", "Redbone", 2016, R.drawable.redbone));
    mixTape.add(new Track("Coldplay", "A Head Full Of Dreams", "Fun", 2015, R.drawable.a_head_full_of_dreams));
    mixTape.add(new Track("Eminem", "Revival", "Tragic Endings", 2017, R.drawable.revival));
    mixTape.add(new Track("Phil Collins", "Face Value", "In The Air Tonight", 1981, R.drawable.face_value));
    mixTape.add(new Track("The Beatles", "A Hard Day's Night", "And I Love Her", 1964, R.drawable.a_head_full_of_dreams));
    mixTape.add(new Track("Shayne Ward", "Shayne Ward", "That's My Goal", 2006, R.drawable.shayne_ward));
    mixTape.add(new Track("Kanye West", "808s & Heartbreak", "Heartless", 2008, R.drawable.kanye_808s_heartbreak));
    mixTape.add(new Track("Jay-z & Eminem", "The Blueprint", "Renegade", 2001, R.drawable.the_blueprint));
    mixTape.add(new Track("Frank Ocean", "Novacane", "Novacane", 2011, R.drawable.novacane));
    mixTape.add(new Track("Pink Floyd", "The Dark Side Of The Moon", "Time", 1973, R.drawable.the_dark_side_of_the_moon));
    mixTape.add(new Track("The Antlers", "Hospice", "Kettering", 2009, R.drawable.hospice));
    mixTape.add(new Track("Justin Bieber", "Believe", "As Long As You Love Me", 2012, R.drawable.believe));
    mixTape.add(new Track("Robbie Williams", "Escapology", "Feel", 2002, R.drawable.escapology));
    mixTape.add(new Track("Elton John", "Goodbye Yellow Brick Road", "Candle In The Wind", 1973, R.drawable.goodbye_yellow_brick_road));
    mixTape.add(new Track("Michael Jackson", "Off The Wall", "She's Out Of My Life", 1979, R.drawable.off_the_wall));
    mixTape.add(new Track("Passenger", "All The Little Lights", "Let Her Go", 2012, R.drawable.all_the_little_lights));
    mixTape.add(new Track("Johnny Cash", "Unearthed", "Hurt", 2003, R.drawable.unearthed));
    mixTape.add(new Track("Sinead O'Connor", "I Do Not Want What I Haven't Got", "Nothing Compares 2 U", 1990, R.drawable.i_do_not_want_what_i_havent_got));
}
public ArrayList<Track> getList(){
    return mixTape;
}
}

here comes the main activity java file:

public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    ArrayList<Track> newMixTape = new ArrayList<>();
    //instantiate custom RecordMixTape object, and load an ArrayList with 20 Track objects
    RecordMixTape myMixTape = new RecordMixTape();
    myMixTape.record();
    newMixTape = myMixTape.getList();
    final TrackAdapter newTrackAdapter = new TrackAdapter(this, newMixTape);
    ListView listView = (ListView) findViewById(R.id.mixTapeList);
    final SearchView searchMsg = (SearchView) findViewById(R.id.search_msg);
    listView.setAdapter(newTrackAdapter);
    //have the searchView execute the filter method in the TrackAdapter
    searchMsg.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
        @Override
        public boolean onQueryTextSubmit(String query) {
            newTrackAdapter.getFilter().filter(query);
            return false;
        }

        @Override
        public boolean onQueryTextChange(String query) {
            newTrackAdapter.getFilter().filter(query);
            return false;
        }
    });
}
}

and this is the custom ArrayAdapter:

public class TrackAdapter extends ArrayAdapter<Track> implements Filterable {
private ArrayList<Track> filteredList = null;
private ArrayList<Track> data = null;

public TrackAdapter(Activity context, ArrayList<Track> trackList) {
    super(context, 0, trackList);
    data = trackList;
}

@NonNull
@Override
public View getView(final int position, @Nullable View convertView, @NonNull ViewGroup parent) {
    View listItemView = convertView;
    if (listItemView == null) {
        listItemView = LayoutInflater.from(getContext()).inflate(R.layout.list_item, parent, false);
    }
    final Track currentTrack = getItem(position);
    //clone song information to the current convertView
    final TextView artistNameField = listItemView.findViewById(R.id.artistName);
    artistNameField.setText(currentTrack.getArtistName());
    final TextView albumNameField = listItemView.findViewById(R.id.albumName);
    albumNameField.setText(currentTrack.getAlbumName());
    final TextView songNameField = listItemView.findViewById(R.id.songName);
    songNameField.setText(currentTrack.getSongName());
    TextView albumReleaseDate = listItemView.findViewById(R.id.releaseDate);
    albumReleaseDate.setText(currentTrack.getReleaseDate());
    ImageView albumArtView = listItemView.findViewById(R.id.albumArt);
    albumArtView.setImageResource(currentTrack.getAlbumArt());
    albumArtView.setOnClickListener(new View.OnClickListener() {
        //intent which triggers another activity
        @Override
        public void onClick(View view) {
            Intent playThisTrack = new Intent(getContext(), NowPlayingActivity.class);
            playThisTrack.putExtra("index", position);
            getContext().startActivity(playThisTrack);
        }
    });

    return listItemView;
}

Filter myFilter = new Filter() {
    @Override
    protected FilterResults performFiltering(CharSequence constraint) {
        String filterString = constraint.toString().toLowerCase();
        FilterResults results = new FilterResults();
        ArrayList<Track> resultList = new ArrayList<Track>();
        for (int i = 0; i < data.size(); i++) {
            Track item = data.get(i);
            if (item.getAlbumName().toLowerCase().contains(filterString)) {
                resultList.add(item);
            }
        }
        results.values = resultList;
        results.count = resultList.size();
        return results;
    }

    @SuppressWarnings("unchecked")
    @Override
    protected void publishResults(CharSequence constraint, FilterResults results) {
        filteredList = (ArrayList<Track>) results.values;
        notifyDataSetChanged();
    }
};

@Override
public Filter getFilter() {
    return myFilter;
}
}

and just in case, this is the main activity xml which loads another custom layout file to display the songs:

<LinearLayout
android:id="@+id/parent_view"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.yunfrankzhang.mixtapeofsadness.NowPlayingActivity">
<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal">
    <SearchView
        android:id="@+id/search_msg"
        android:layout_marginTop="8dp"
        android:layout_marginRight="8dp"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:hint="Search here"
        android:textSize="16dp"
        android:layout_weight="1"
        android:layout_marginLeft="8dp"/>
</LinearLayout>
<ListView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/mixTapeList">
</ListView>
</LinearLayout>

Please enlighten me where have I messed up, troll my code all you like. I thank you from the bottom of my heart.

  • Can you create a minimal example that demonstrates the problem? – BBB Feb 07 '18 at 12:49
  • ArrayAdapter is a bad class for such customization ... it is already implement filtering ... I would try to call `oldFilter.publishResults` from your publishResults where `oldFilter` is taken inside Adapter by calling `super.getFilter()` (but I would not reccomend this) ... in TrackAdapter add field `private Filter oldFilter = getFilter();` then in `publishResults` just call` oldFilter.publishResults(constraint, results)` – Selvin Feb 07 '18 at 12:49
  • Did you mean to do `data = (ArrayList) results.values`? – OneCricketeer Feb 07 '18 at 12:50
  • 1
    @BrunoCarvalhal problem is that `filteredList` is never used ... – Selvin Feb 07 '18 at 12:50
  • @cricket_007 it would not help ... `ArrayAdapter` internally is using own reference `mData` ... `ArrayAdapter.getCount() => mData.size()` ... it would be better if he would wrote adapter based on `BaseAdapter` not `ArrayAdapter` – Selvin Feb 07 '18 at 12:50
  • @Selvin Yes, but at the constructor super call `mData = trackList`, so that list needs updated somewhere, and like you said, the filteredData is not used – OneCricketeer Feb 07 '18 at 12:53
  • @cricket_007 just tried setting the results.values to data, app still shows the same old list. filter still didn't perform. – Frank Zhang Feb 07 '18 at 12:54
  • @Selvin I guess the question is how can I have the adapter update the listView from updated data arraylist then? – Frank Zhang Feb 07 '18 at 12:55
  • `data = (ArrayList) results.values` will not change `super.mData` (which is not accessible) – Selvin Feb 07 '18 at 12:55
  • 1
    If the album name is returned from the object in a toString method, none of this filter definition is really required https://stackoverflow.com/a/30416831/2308683 – OneCricketeer Feb 07 '18 at 12:56
  • `data.clear(); data.addAll( (ArrayList) results.values);` would update the list – OneCricketeer Feb 07 '18 at 12:58
  • `data` is never used, too :) ... anyway ... overriding toString seems to be a good choice for this – Selvin Feb 07 '18 at 13:00
  • 1
    @cricket_007 yup. Great fix. did what we wanted, I'll just work on a solution to repopulate the data list in case user wants to do another query. Thanks cricket. – Frank Zhang Feb 07 '18 at 13:03
  • @cricket_007 Yo cricket so it happened that I gave your recommendation a shot. I overrode the .toString() method in my Track object and deleted all the BS filter code in my ArrayAdapter. Guess what, it works beautifully. There's this elegant, default filter here all along and I've clawing my eyes out trying to reinvent the fking wheel. Thanks a lot. – Frank Zhang Feb 08 '18 at 02:18

1 Answers1

0

have the adapter to be able to return appropriate song objects whose album name contains the string the user inputs in a search view

If that's all you're trying to filter on, there is a much easier way.

Add a toString() method to your Track object

public String getAlbumName(){
    return mAlbumName;
}

@Override
public String toString() {
    return getAlbumName();
}

Then this will simply do what you need

newTrackAdapter.getFilter().filter("your input");
OneCricketeer
  • 179,855
  • 19
  • 132
  • 245