Skip to content

Instantly share code, notes, and snippets.

@ajharry69
Last active March 10, 2020 00:05
Show Gist options
  • Save ajharry69/e04d41adf48d3fee8ea9c49917382476 to your computer and use it in GitHub Desktop.
Save ajharry69/e04d41adf48d3fee8ea9c49917382476 to your computer and use it in GitHub Desktop.
An explanation on how to implement a standard Android search interface with Fragment(s) in SingleActivity pattern. N/B: Some code snippets contains AndroidX's navigation component codes that might not be useful to your project(as you could be handling your fragment navigation differently)
<?xml version="1.0" encoding="utf-8"?>
<manifest ...>
<application ...>
<!-- launchMode=singleTop is IMPORTANT to avoid relaunching MainActivity every time search is initiated -->
<activity
android:name=".MainActivity"
android:launchMode="singleTop">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<action android:name="android.intent.action.SEARCH" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<meta-data
android:name="android.app.searchable"
android:resource="@xml/searchable" />
</activity>
</application>
</manifest>
<menu ...>
<item
android:id="@+id/menu_list_search"
android:icon="@drawable/ic_action_search"
android:orderInCategory="1"
android:title="Search"
app:showAsAction="ifRoom" />
</menu>
class MainActivity : AppCompatActivity() {
private lateinit var binding: MainActivityBinding
private val navController: NavController by lazy {
findNavController(R.id.navigation_host)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = MainActivityBinding.inflate(layoutInflater)
setContentView(binding.root)
handleIntent(intent)
}
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
setIntent(intent)
handleIntent(intent)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean =
item.onNavDestinationSelected(navController) || super.onOptionsItemSelected(item)
override fun onSearchRequested(): Boolean {
val customAppData = Bundle().apply{
putString("key1", "value1")
}
startSearch(null, false, customAppData, false)
return true
// return super.onSearchRequested() // Use when there is no need to send custom app data
}
private fun handleIntent(intent: Intent) {
// Verify the action and get the query
if (Intent.ACTION_SEARCH == intent.action) {
val query = intent.getStringExtra(SearchManager.QUERY) ?: return
// Pass the [query] received to a fragment in which actual data-searching process will be done
val navOptions = NavOptions.Builder().setLaunchSingleTop(true).build()
navController.navigate(R.id.dest_search_results, SearchResultsFragmentArgs(query=query).toBundle(), navOptions)
}
}
}
<?xml version="1.0" encoding="utf-8"?>
<searchable xmlns:android="http://schemas.android.com/apk/res/android"
android:hint="@string/search_hint"
android:label="@string/app_name"/>
class SearchInitiatorFragment: Fragment(){
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Needed to help show options menu from fragments
setHasOptionsMenu(true)
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.list_menu, menu)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean = when (item.itemId) {
/*
Launch SearchDialog when "search" menu item is clicked.
N/B: I chose to go with a SearchDialog instead of SearchView since it was easier for me
to send/pass custom app-data to my searchable activity (MainActivity in this case) after
submiting a search query
*/
R.id.menu_list_search -> activity?.onSearchRequested() ?: false
else -> super.onOptionsItemSelected(item)
}
}
class SearchResultsFragment: Fragment(){
private val args: SearchResultsFragmentArgs by navArgs()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
Log.i("SearchResultsFragment", "Received Query: ${args.query}")
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment