Search within TV apps
Stay organized with collections
Save and categorize content based on your preferences.
Users frequently have specific content in mind when using a media app on TV. If your app contains
a large catalog of content, browsing for a specific title might not be the most efficient way for
users to find what they are looking for. A search interface can help your users get to the
content they want faster than browsing.
The androidx.leanback library provides a set of classes to enable a standard search interface
within your app that is consistent with other search functions on TV and provides features like
voice input.
This guide discusses how to provide a search interface in your app using Leanback support
library classes.
Add a search action
When you use the BrowseFragment
class for a media
browsing interface, you can enable a search interface as a standard part of the user
interface. The search interface is an icon that appears in the layout when you set View.OnClickListener
on the BrowseFragment
object. The following sample code demonstrates this technique.
Kotlin
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.browse_activity)
browseFragment = fragmentManager.findFragmentById(R.id.browse_fragment) as BrowseFragment
browseFragment.setOnSearchClickedListener { view ->
val intent = Intent(this@BrowseActivity, SearchActivity::class.java)
startActivity(intent)
}
browseFragment.setAdapter(buildAdapter())
}
Java
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.browse_activity);
browseFragment = (BrowseFragment)
getFragmentManager().findFragmentById(R.id.browse_fragment);
...
browseFragment.setOnSearchClickedListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent(BrowseActivity.this, SearchActivity.class);
startActivity(intent);
}
});
browseFragment.setAdapter(buildAdapter());
}
Note: You can set the color of the search icon using the
setSearchAffordanceColor(int)
method.
Add a search input and results
When a user selects the search icon, the system invokes a search activity using the
defined intent. For your search activity, use a linear layout containing a
SearchFragment
.
This fragment must also implement the SearchFragment.SearchResultProvider
interface to display the results of a search.
The following code sample shows how to extend the SearchFragment
class
to provide a search interface and results:
Kotlin
class MySearchFragment : SearchFragment(), SearchFragment.SearchResultProvider {
private val rowsAdapter = ArrayObjectAdapter(ListRowPresenter())
private val handler = Handler()
private val delayedLoad = SearchRunnable()
val resultsAdapter: ObjectAdapter
get() {
return rowsAdapter
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setSearchResultProvider(this)
setOnItemClickedListener(getDefaultItemClickedListener())
}
fun onQueryTextChange(newQuery: String): Boolean {
rowsAdapter.clear()
if (!TextUtils.isEmpty(newQuery)) {
delayedLoad.setSearchQuery(newQuery)
handler.removeCallbacks(delayedLoad)
handler.postDelayed(delayedLoad, SEARCH_DELAY_MS)
}
return true
}
fun onQueryTextSubmit(query: String): Boolean {
rowsAdapter.clear()
if (!TextUtils.isEmpty(query)) {
delayedLoad.setSearchQuery(query)
handler.removeCallbacks(delayedLoad)
handler.postDelayed(delayedLoad, SEARCH_DELAY_MS)
}
return true
}
companion object {
private val SEARCH_DELAY_MS = 300
}
}
Java
public class MySearchFragment extends SearchFragment
implements SearchFragment.SearchResultProvider {
private static final int SEARCH_DELAY_MS = 300;
private ArrayObjectAdapter rowsAdapter;
private Handler handler = new Handler();
private SearchRunnable delayedLoad;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
rowsAdapter = new ArrayObjectAdapter(new ListRowPresenter());
setSearchResultProvider(this);
setOnItemClickedListener(getDefaultItemClickedListener());
delayedLoad = new SearchRunnable();
}
@Override
public ObjectAdapter getResultsAdapter() {
return rowsAdapter;
}
@Override
public boolean onQueryTextChange(String newQuery) {
rowsAdapter.clear();
if (!TextUtils.isEmpty(newQuery)) {
delayedLoad.setSearchQuery(newQuery);
handler.removeCallbacks(delayedLoad);
handler.postDelayed(delayedLoad, SEARCH_DELAY_MS);
}
return true;
}
@Override
public boolean onQueryTextSubmit(String query) {
rowsAdapter.clear();
if (!TextUtils.isEmpty(query)) {
delayedLoad.setSearchQuery(query);
handler.removeCallbacks(delayedLoad);
handler.postDelayed(delayedLoad, SEARCH_DELAY_MS);
}
return true;
}
}
The previous example code is meant to be used with a SearchRunnable
class
that runs the search query on a separate thread. This technique keeps potentially slow-running
queries from blocking the main user interface thread.
Content and code samples on this page are subject to the licenses described in the Content License. Java and OpenJDK are trademarks or registered trademarks of Oracle and/or its affiliates.
Last updated 2025-04-17 UTC.
[[["Easy to understand","easyToUnderstand","thumb-up"],["Solved my problem","solvedMyProblem","thumb-up"],["Other","otherUp","thumb-up"]],[["Missing the information I need","missingTheInformationINeed","thumb-down"],["Too complicated / too many steps","tooComplicatedTooManySteps","thumb-down"],["Out of date","outOfDate","thumb-down"],["Samples / code issue","samplesCodeIssue","thumb-down"],["Other","otherDown","thumb-down"]],["Last updated 2025-04-17 UTC."],[],[],null,["# Search within TV apps\n\nUsers frequently have specific content in mind when using a media app on TV. If your app contains\na large catalog of content, browsing for a specific title might not be the most efficient way for\nusers to find what they are looking for. A search interface can help your users get to the\ncontent they want faster than browsing.\n\n\nThe [androidx.leanback library](/training/tv/get-started/create#leanback) provides a set of classes to enable a standard search interface\nwithin your app that is consistent with other search functions on TV and provides features like\nvoice input.\n\n\nThis guide discusses how to provide a search interface in your app using Leanback support\nlibrary classes.\n\nAdd a search action\n-------------------\n\n\nWhen you use the [BrowseFragment](/reference/androidx/leanback/app/BrowseFragment) class for a media\nbrowsing interface, you can enable a search interface as a standard part of the user\ninterface. The search interface is an icon that appears in the layout when you set [View.OnClickListener](/reference/android/view/View.OnClickListener) on the `BrowseFragment`\nobject. The following sample code demonstrates this technique. \n\n### Kotlin\n\n```kotlin\noverride fun onCreate(savedInstanceState: Bundle?) {\n super.onCreate(savedInstanceState)\n setContentView(R.layout.browse_activity)\n browseFragment = fragmentManager.findFragmentById(R.id.browse_fragment) as BrowseFragment\n browseFragment.setOnSearchClickedListener { view -\u003e\n val intent = Intent(this@BrowseActivity, SearchActivity::class.java)\n startActivity(intent)\n }\n\n browseFragment.setAdapter(buildAdapter())\n}\n```\n\n### Java\n\n```java\n@Override\npublic void onCreate(Bundle savedInstanceState) {\n super.onCreate(savedInstanceState);\n setContentView(R.layout.browse_activity);\n\n browseFragment = (BrowseFragment)\n getFragmentManager().findFragmentById(R.id.browse_fragment);\n\n ...\n\n browseFragment.setOnSearchClickedListener(new View.OnClickListener() {\n @Override\n public void onClick(View view) {\n Intent intent = new Intent(BrowseActivity.this, SearchActivity.class);\n startActivity(intent);\n }\n });\n\n browseFragment.setAdapter(buildAdapter());\n}\n```\n\n\n**Note:** You can set the color of the search icon using the\n[setSearchAffordanceColor(int)](/reference/androidx/leanback/app/BrandedFragment#setSearchAffordanceColor(int))\nmethod.\n\nAdd a search input and results\n------------------------------\n\n\nWhen a user selects the search icon, the system invokes a search activity using the\ndefined intent. For your search activity, use a linear layout containing a\n[SearchFragment](/reference/androidx/leanback/app/SearchFragment).\nThis fragment must also implement the [SearchFragment.SearchResultProvider](/reference/androidx/leanback/app/SearchFragment.SearchResultProvider)\ninterface to display the results of a search.\n\n\nThe following code sample shows how to extend the `SearchFragment` class\nto provide a search interface and results: \n\n### Kotlin\n\n```kotlin\nclass MySearchFragment : SearchFragment(), SearchFragment.SearchResultProvider {\n private val rowsAdapter = ArrayObjectAdapter(ListRowPresenter())\n private val handler = Handler()\n private val delayedLoad = SearchRunnable()\n\n val resultsAdapter: ObjectAdapter\n get() {\n return rowsAdapter\n }\n\n override fun onCreate(savedInstanceState: Bundle?) {\n super.onCreate(savedInstanceState)\n setSearchResultProvider(this)\n setOnItemClickedListener(getDefaultItemClickedListener())\n }\n\n fun onQueryTextChange(newQuery: String): Boolean {\n rowsAdapter.clear()\n if (!TextUtils.isEmpty(newQuery)) {\n delayedLoad.setSearchQuery(newQuery)\n handler.removeCallbacks(delayedLoad)\n handler.postDelayed(delayedLoad, SEARCH_DELAY_MS)\n }\n return true\n }\n\n fun onQueryTextSubmit(query: String): Boolean {\n rowsAdapter.clear()\n if (!TextUtils.isEmpty(query)) {\n delayedLoad.setSearchQuery(query)\n handler.removeCallbacks(delayedLoad)\n handler.postDelayed(delayedLoad, SEARCH_DELAY_MS)\n }\n return true\n }\n\n companion object {\n private val SEARCH_DELAY_MS = 300\n }\n}\n```\n\n### Java\n\n```java\npublic class MySearchFragment extends SearchFragment\n implements SearchFragment.SearchResultProvider {\n\n private static final int SEARCH_DELAY_MS = 300;\n private ArrayObjectAdapter rowsAdapter;\n private Handler handler = new Handler();\n private SearchRunnable delayedLoad;\n\n @Override\n public void onCreate(Bundle savedInstanceState) {\n super.onCreate(savedInstanceState);\n\n rowsAdapter = new ArrayObjectAdapter(new ListRowPresenter());\n setSearchResultProvider(this);\n setOnItemClickedListener(getDefaultItemClickedListener());\n delayedLoad = new SearchRunnable();\n }\n\n @Override\n public ObjectAdapter getResultsAdapter() {\n return rowsAdapter;\n }\n\n @Override\n public boolean onQueryTextChange(String newQuery) {\n rowsAdapter.clear();\n if (!TextUtils.isEmpty(newQuery)) {\n delayedLoad.setSearchQuery(newQuery);\n handler.removeCallbacks(delayedLoad);\n handler.postDelayed(delayedLoad, SEARCH_DELAY_MS);\n }\n return true;\n }\n\n @Override\n public boolean onQueryTextSubmit(String query) {\n rowsAdapter.clear();\n if (!TextUtils.isEmpty(query)) {\n delayedLoad.setSearchQuery(query);\n handler.removeCallbacks(delayedLoad);\n handler.postDelayed(delayedLoad, SEARCH_DELAY_MS);\n }\n return true;\n }\n}\n```\n\n\nThe previous example code is meant to be used with a `SearchRunnable` class\nthat runs the search query on a separate thread. This technique keeps potentially slow-running\nqueries from blocking the main user interface thread."]]