android_search_view 

In the last post, I had explained how you can use Retrofit API to consume Feedly’s feed search API. In this post, I will cover the integration of a SearchView with the feed search API. 

To integrate SearchView with the custom data source, you need to implement the following  interfaces  in the fragment or activity.

  • SearchView.OnQueryTextListener: This handles firing a query with the data provider as the user starts typing in the SearchView.
  • SearchView.OnSuggestionListener: This handles click or selection of the suggested result from search.

The code for the implemented methods of these interfaces is show below:-

//for onQueryTextListener
@Override
public boolean onQueryTextSubmit(String s) {
if (s.length() > 2) {
loadData(s);
}
return true;
}

@Override
public boolean onQueryTextChange(String s) {
if (s.length() > 2) {
loadData(s);
}
return true;
}

//for OnSuggestionListener

@Override
public boolean onSuggestionSelect(int position) {
Cursor cursor = (Cursor) searchView.getSuggestionsAdapter().getItem(position);
String feedName = cursor.getString(4);
searchView.setQuery(feedName, false);
searchView.clearFocus();
return true;
}

@Override
public boolean onSuggestionClick(int position) {
Cursor cursor = (Cursor) searchView.getSuggestionsAdapter().getItem(position);
String feedName = cursor.getString(4);
searchView.setQuery(feedName, false);
searchView.clearFocus();
return true;
}


 



The layout for the  customized list item which will appear when the user starts typing in the SearchView is shown below:-



<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal" android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/icon_feed"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_centerVertical="true"
android:layout_centerHorizontal="true"
android:layout_alignParentStart="true"
android:contentDescription="@string/ImgContentDescription"
/>
<TextView
android:id="@+id/feed_url_text"
android:layout_width="wrap_content"
android:layout_height="23dp"
android:paddingLeft="5dp"
android:layout_toRightOf="@id/icon_feed"
android:paddingTop="0dp"
android:paddingBottom="2dp"
/>
<LinearLayout
android:id="@+id/subscriber_layout"
android:orientation="horizontal"
android:layout_below="@+id/feed_url_text"
android:layout_toRightOf="@id/icon_feed"
android:layout_width="wrap_content"
android:layout_height="25dp">
<ImageView
android:src="@drawable/icon_user_light"
android:paddingLeft="5dp"
android:layout_width="24dp"
android:layout_height="24dp" />
<TextView
android:id="@+id/subscriber_count"
android:text="@string/feed_name"
android:layout_width="wrap_content"
android:paddingTop="2dp"
android:paddingLeft="2dp"
android:layout_height="23dp" />
</LinearLayout>

</RelativeLayout>


As, I am using a custom layout for the list item that appears in the search, So I have extended the SimpleCursorAdapter to  bind the view to the underlying data as shown below.



public class SearchFeedResultsAdaptor extends SimpleCursorAdapter {
private static final String tag=SearchFeedResultsAdaptor.class.getName();
private Context context=null;
public SearchFeedResultsAdaptor(Context context, int layout, Cursor c, String[] from, int[] to, int flags) {
super(context, layout, c, from, to, flags);
this.context=context;
}

@Override
public void bindView(View view, Context context, Cursor cursor) {
ImageView imageView=(ImageView)view.findViewById(R.id.icon_feed);
TextView textView=(TextView)view.findViewById(R.id.feed_url_text);
TextView subscribersView=(TextView)view.findViewById(R.id.subscriber_count);
ImageTagFactory imageTagFactory = ImageTagFactory.newInstance(context, R.drawable.rss_icon);
imageTagFactory.setErrorImageId(R.drawable.rss_icon);
ImageTag tag = imageTagFactory.build(cursor.getString(2),context);
imageView.setTag(tag);
FeedReaderApplication.getImageManager().getLoader().load(imageView);
textView.setText(cursor.getString(4) + " : " + cursor.getString(1));
subscribersView.setText(cursor.getString(3));


}
}


In this adapter class, I am binding the layout components to the elements returned by cursor in the overridden bindView method . I am also using the novoda image loader to load the icon image from the URL returned by Feedly service.



Since, I am using a fragment , so the code to initialize the SearchView  is placed in the  OnActivityCreated method as shown below.



 @Override
//list of columns
public static String[] columns = new String[]{"_id", "FEED_URL", "FEED_ICON", "FEED_SUBSCRIBERS", "FEED_TITLE"};

public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
searchView = (SearchView) getView().findViewById(R.id.searchFeedView);
searchView.setOnQueryTextListener(this);
searchView.setOnSuggestionListener(this);
mSearchViewAdapter = new SearchFeedResultsAdaptor(this.getActivity(), R.layout.search_feed_list_item, null, columns,null, -1000);
searchView.setSuggestionsAdapter(mSearchViewAdapter);
}


The loadData method which is invoked from the onQueryTextChanged  method loads the Feedly search results as shown below:-



 private void loadData(String searchText) {
//specify endpoint and build the restadapter instance
RestAdapter restAdapter = new RestAdapter.Builder()
.setEndpoint("http://feedly.com")
.build();
//Now use restadapter to create an instance of your interface
FeedlySuggestionsService searchService = restAdapter.create(FeedlySuggestionsService.class);
//populate the request parameters
HashMap queryMap = new HashMap();
queryMap.put("query", searchText);
//implement the Callback method for retrieving the response
searchService.searchFeeds(queryMap, new Callback<FeedlyResponse>() {
@Override
public void success(FeedlyResponse feedlyResponse, Response response) {
MatrixCursor matrixCursor = convertToCursor(feedlyResponse.getResults());
mSearchViewAdapter.changeCursor(matrixCursor);
}

@Override
public void failure(RetrofitError error) {
Log.e(tag, error.toString());
}
});
}


If you notice the highlighted lines in the above method, you will see that the result returned by the REST service is in List format. Now, as the SearchView accepts only CursorAdapter as its SuggestionAdapter, so we have to convert the List into a Cursor object.  To do so, I have created a  convertToCursor method which iterates the List to return a MatrixCursor object.  After the conversion is done, I am just replacing the existing cursor of the Suggestion Adapter with this new value.  The method to convert the list to MatrixCursor is shown below:-



 private MatrixCursor convertToCursor(List<FeedlyResult> feedlyResults) {
MatrixCursor cursor = new MatrixCursor(columns);
int i = 0;
for (FeedlyResult feedlyResult : feedlyResults) {
String[] temp = new String[5];
i = i + 1;
temp[0] = Integer.toString(i);

String feedUrl = feedlyResult.getFeedId();
if (feedUrl != null) {
int index = feedUrl.indexOf("feed/");
if (index != -1) {
feedUrl = feedUrl.substring(5);
}
}
temp[1] = feedUrl;
temp[2] = feedlyResult.getIconUrl();
temp[3] = feedlyResult.getSubscribers();
temp[4] = feedlyResult.getTitle();
cursor.addRow(temp);
}
return cursor;
}


The above method takes the column names for the Cursor from a static String array as shown above the onActivityCreated  method.



That’s it ! now you have a SearchView  integrated with a REST service.