In this article I will explain how to use Architecture components, specifically Navigation for creation of a Fragment using a searching UI.

As an example I will use the project Viktor-Events. Please note that it is not production ready, so you will not find it on Google Play, but you can clone the repository, compile it and run it from Android Studio.

Adding Navigation

To use Navigation (Architecture component), use the following dependencies:

implementation 'android.arch.navigation:navigation-fragment-ktx:1.0.0-alpha09'
implementation 'android.arch.navigation:navigation-ui-ktx:1.0.0-alpha09'

Unfortunately, the libraries are still in alpha, but I’ve been able to use it in this project, as well as in the production-ready Wiktor-Navigator.

The navigation graph, though too big to embed in this article, can be vieved in XML form directly on GitHub.

The code that actually embeds the Fragments, using Navigation Architecture component, into their Activity is as follows.

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    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"
    tools:context=".main.EventsActivity">

    <com.google.android.material.appbar.AppBarLayout
        android:id="@+id/appbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:theme="@style/AppTheme.AppBarOverlay"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toTopOf="@+id/nav_host_fragment">

        <androidx.appcompat.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            app:popupTheme="@style/AppTheme.PopupOverlay"/>

    </com.google.android.material.appbar.AppBarLayout>

    <fragment
        android:layout_width="match_parent"
        android:layout_height="0dp"
        app:layout_constraintTop_toBottomOf="@+id/appbar"
        app:layout_constraintBottom_toBottomOf="parent"
        android:id="@+id/nav_host_fragment"
        android:name="androidx.navigation.fragment.NavHostFragment"
        app:navGraph="@navigation/nav_graph"
        app:defaultNavHost="true"
        />

</androidx.constraintlayout.widget.ConstraintLayout>

Implementing the Activity and Fragment

The Activity and Fragment share one instance of ViewModel:

class EventsViewModel : ViewModel() {
    val query by lazy {
        MutableLiveData<String>()
    }
    val commToCreate by lazy {
        MutableLiveData<String?>()
    }
}

An instance of it is acquired in the Activity this way:

private lateinit var eventsModel: EventsViewModel

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    ...
    eventsModel = ViewModelProviders.of(this).get(EventsViewModel::class.java)
    ....
}

eventsModel = ViewModelProviders.of(this).get(EventsViewModel::class.java)

And in the Fragment this way, together with observing the value of query:

private lateinit var eventsModel: EventsViewModel

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    eventsModel = ViewModelProviders.of(activity!!).get(EventsViewModel::class.java)
    eventsModel.query.observe(this) { query ->
        if (!query.isEmpty()) {
            eventsModel.query.value = ""
            search(query)
        }
    }
}

Please note the shared instance of ViewModel needs to be retrieved specifically in the function onViewCreated(), and not in onCreateView().

Setting the value of query is performed in the Activity’s function onNewIntent():

override fun onNewIntent(intent: Intent) {
    super.onNewIntent(intent)
    if (Intent.ACTION_SEARCH == intent.action) {
        eventsModel.query.value = intent.getStringExtra(SearchManager.QUERY)
    }
}

Because both Activity and the Fragment share the same instance of the ViewModel, the Fragment may listen to the value changes of the LiveData that is set in the Activity.

Configuring the Activity in AndroidManifest.xml

This is the way the Activity is configured in AndroidManifest.xml so that it handles android.intent.action.SEARCH Intents, and that only one instance of it may be run (the parameter singleTop):

<activity
        android:name=".main.EventsActivity"
        android:label="@string/app_name"
        android:launchMode="singleTop"
        android:theme="@style/AppTheme.NoActionBar">
        <intent-filter>
            <action android:name="android.intent.action.MAIN"/>
            <category android:name="android.intent.category.LAUNCHER"/>
        </intent-filter>
        <intent-filter>
            <action android:name="android.intent.action.SEARCH" />
        </intent-filter>
        <meta-data android:name="android.app.searchable"
                   android:resource="@xml/searchable_community"/>
</activity>

This is the content of the XML file configuring searchable content in the Activity:

<?xml version="1.0" encoding="utf-8"?>
<searchable xmlns:android="http://schemas.android.com/apk/res/android"
            android:label="@string/searchable_add_community"
            android:hint="@string/searchable_add_community_hint">
</searchable>

Adding the Search option menu in the Fragment

This is the code that adds Search option menu to the Fragment:

private val searchManager get() =
        activity!!.getSystemService(Context.SEARCH_SERVICE) as SearchManager

override fun onCreateOptionsMenu(menu: Menu, menuInflater: MenuInflater) {
    fun MenuItem.prepareSearchView() = with (actionView as SearchView) {
        setSearchableInfo(searchManager.getSearchableInfo(activity!!.componentName))
        setOnSearchClickListener { onSearchClicked() }
        setOnCloseListener { onSearchViewClosed() }
    }
        
    menuInflater.inflate(R.menu.add_community, menu)
    val searchMenuItem = menu.findItem(R.id.action_search)
    searchMenuItem.collapseActionView()
    searchMenuItem.prepareSearchView()
}

This is, finally, the XML file that adds the search edit box to the GUI as an option menu:

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
      xmlns:app="http://schemas.android.com/apk/res-auto">
    <item
        android:id="@+id/action_search"
        android:orderInCategory="100"
        android:icon="@drawable/ic_search_white_24dp"
        android:title="@string/action_search"
        app:showAsAction="always"
        app:actionViewClass="androidx.appcompat.widget.SearchView"/>
</menu>