Xamarin Forms - ContentPage with SearchBar in the Navigation bar

With the growth of Material design (one of my favourite UI style) there is been a tremendous amount of request for adding the Searchbar to the Navigation tool bar of Xamarin Forms when using Navigation Page (with YouTube being one of the most common, and all other apps from Google).

Some forum and QA posts to back my assumption about Xamarin Forms Navigation bar customization :

  1. https://forums.xamarin.com/discussion/39690/searchbar-in-navigation-bar-or-tabbedpage
  2. http://stackoverflow.com/questions/29047041/placing-a-searchbar-in-the-top-navigation-bar
  3. http://stackoverflow.com/questions/36846074/how-to-include-view-in-navigationbar-of-xamarin-forms

And yes the iOS implementation almost works perfect. You can just copy paste it from the SO Answer.

As you have noticed, I could not find any Android Implementation. Is it so hard? No way. If you are running the latest Xamarin.Forms with FormsAppCompatActivity that uses AppCompat features provided by Android to implement Material Design themes then all you need is a couple of lines of codes. Yes you heard me right.

Lets Code!!!

Create an XML file in your android project under Resources/menu/mainmenu.xml

<?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:title="Search" android:icon="@android:drawable/ic_menu_search" app:showAsAction="always|collapseActionView" app:actionViewClass="android.support.v7.widget.SearchView"/> </menu>

In your MainActivity that extends FormsAppCompactActivity override OnCreateOptionsMenu method

[Activity(Label = "Droid", Icon = "@drawable/icon", Theme = "@style/MainTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]`

public class MainActivity : FormsAppCompatActivity
{
    public static Toolbar ToolBar { get; private set; }

    protected override void OnCreate(Bundle bundle)
    {
        TabLayoutResource = Resource.Layout.Tabbar;
        ToolbarResource = Resource.Layout.Toolbar;

        base.OnCreate(bundle);

        Forms.Init(this, bundle);

        LoadApplication(new App());
    }

    public override bool OnCreateOptionsMenu(IMenu menu)
    {
        ToolBar = FindViewById<Toolbar>(Resource.Id.toolbar);
        return base.OnCreateOptionsMenu(menu);
    }
}`

Now we have got hold of our toolbar. You can look up why we need it here and here.

Let's customise the toolbar.

Create a Renderer for your SearchPage. If you are new to custom renderer then learn more at Customizing Controls on Each Platform.

using Android.Runtime;
using Android.Text;
using Android.Views.InputMethods;
using MyApp.CustomRenderer;
using MyApp.Droid.Activities;
using MyApp.Droid.CustomRenderer;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
using SearchView = Android.Support.V7.Widget.SearchView;

[assembly: ExportRenderer(typeof(SearchPage), typeof(SearchPageRenderer))]
namespace MyApp.Droid.CustomRenderer
{
    public class SearchPageRenderer : PageRenderer
    {
        private SearchView _searchView;

        protected override void OnElementChanged(ElementChangedEventArgs<Page> e)
        {
            base.OnElementChanged(e);

            if (e?.NewElement == null || e.OldElement != null)
            {
                return;
            }

            AddSearchToToolBar();
        }

        protected override void Dispose(bool disposing)
        {
            if (_searchView != null)
            {
                _searchView.QueryTextChange += searchView_QueryTextChange;
                _searchView.QueryTextSubmit += searchView_QueryTextSubmit;
            }
                  MainActivity.ToolBar?.Menu?.RemoveItem(Resource.Menu.mainmenu);
            base.Dispose(disposing);
        }

        private void AddSearchToToolBar()
        {
            MainActivity.ToolBar.Title = Element.Title;

MainActivity.ToolBar.InflateMenu(Resource.Menu.mainmenu);

            _searchView = MainActivity.ToolBar.Menu?.FindItem(Resource.Id.action_search)?.ActionView?.JavaCast<SearchView>();

            _searchView.QueryTextChange += searchView_QueryTextChange;
            _searchView.QueryTextSubmit += searchView_QueryTextSubmit;
            _searchView.QueryHint = (Element as SearchPage)?.SearchPlaceHolderText;
            _searchView.ImeOptions = (int)ImeAction.Search;
            _searchView.InputType = (int)InputTypes.TextVariationNormal;
            _searchView.MaxWidth = int.MaxValue;                         }

        private void searchView_QueryTextSubmit(object sender, SearchView.QueryTextSubmitEventArgs e)
        {
            var searchPage = Element as SearchPage;
            searchPage.SearchText = e.Query;
          searchPage.SearchCommand?.Execute(e.Query);
            e.Handled = true;
        }

        private void searchView_QueryTextChange(object sender, SearchView.QueryTextChangeEventArgs e)
        {
            var searchPage = Element as SearchPage;
            searchPage.SearchText = e?.NewText;
        }
    }
}

If you are lazy and want to use my Abstraction from PCL, then here you go :

public class SearchPage : ContentPage
{
    public static readonly BindableProperty SearchPlaceHolderTextProperty = BindableProperty.Create(nameof(SearchPlaceHolderText), typeof(string), typeof(SearchPage), string.Empty);
    public static readonly BindableProperty SearchTextProperty = BindableProperty.Create(nameof(SearchText), typeof(string), typeof(SearchPage), string.Empty);
    public static readonly BindableProperty SearchCommandProperty = BindableProperty.Create(nameof(SearchCommand), typeof(ICommand), typeof(SearchPage));

    public string SearchPlaceHolderText
    {
        get
        {
            return (string)GetValue(SearchPlaceHolderTextProperty);
        }
        set    
        {
            SetValue(SearchPlaceHolderTextProperty, value);
        }
    }

    public string SearchText
    {
        get
        {
            return (string)GetValue(SearchTextProperty);
        }
        set
        {
            SetValue(SearchTextProperty, value);
        }
    }

    public ICommand SearchCommand
    {
        get
        {
            return (ICommand)GetValue(SearchCommandProperty);
        }
        set
        {
            SetValue(SearchCommandProperty, value);
        }
    }
}

Now go ahead and use it in your PCL of Xamarin Forms by pushing your new SearchPage (like how you use ContentPage) into the navigation stack.

Here is the github link to the project - https://github.com/rohitvipin/Xamarin-Forms-Custom-Renderer-Samples