Custom Dialog with Xamarin Android

This is a continuation of my last post where I showed how to save and retrieve an item to the Android SQLite database using SQLite.Net ORM. In this post I want to show how to use Custom Dialog with Xamarin Android to add an item to the same database.

Tutorials on Android Master List details normally show either two Activities or an Activity and a Fragment. In this post, I will use the Main Activity already added in the last post and then create a Custom Dialog with Android. Below is what the completed dialog will look like and the code for this tutorial can be found in Github.


Just to re-state what I will be implementing in this tutorial, there will be a button on the Main Activity called “Add Services” that when clicked will launch a custom dialog over the Activity. A little interesting twist is that there is a drop down list for the Categories.

This could have been a text field, but in real life Categories are often drop down list that are backed by a data source. In Android, drop down list are implemented with the Spinner class. So I have not only added a Spinner but I have added the ability to populate that Spinner on the fly so the user can add a Category that does not yet exist and have that Category become immediately available on the drop down list. The data source for the drop down list is yet another table in the SQLite database. Let’s start with the UI for the Custom dialog.

User Interface for a Custom Dialog with Xamarin Android

If you are following from the last tutorial with this source code, go ahead and add a layout file under layout sub-folder of the Resources folder. I have named my file AddServiceDialog.xaml and here is what the design tab will look like. You can design iwth the designer or you can just go ahead and copy and paste the XML code below.

<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:p1="http://schemas.android.com/apk/res/android"
    p1:minWidth="25px"
    p1:minHeight="25px"
    p1:layout_width="match_parent"
    p1:layout_height="match_parent"
    p1:id="@+id/scrollView1">
    <LinearLayout
        p1:orientation="vertical"
        p1:minWidth="25px"
        p1:minHeight="25px"
        p1:layout_width="match_parent"
        p1:layout_height="wrap_content"
        p1:id="@+id/linearLayoutContainer"
        p1.gravity="center">
        <TextView
            p1:text="Add Service Item"
            p1:textAppearance="?android:attr/textAppearanceLarge"
            p1:layout_width="wrap_content"
            p1:layout_height="wrap_content"
            p1:layout_gravity="left"
            p1:id="@+id/textTitle" />
        <TextView
            p1:text="Name"
            p1:textAppearance="?android:attr/textAppearanceMedium"
            p1:layout_width="wrap_content"
            p1:layout_height="wrap_content"
            style="@style/LabelStyle"
            p1:id="@+id/textViewName" />
        <View
            style="@style/DividerStyle" />
        <EditText
            p1:layout_width="match_parent"
            p1:layout_height="wrap_content"
            p1:id="@+id/editTextName" />
        <TextView
            p1:text="Description"
            p1:textAppearance="?android:attr/textAppearanceMedium"
            p1:layout_width="wrap_content"
            p1:layout_height="wrap_content"
            style="@style/LabelStyle"
            p1:id="@+id/textViewDescription" />
        <View
            style="@style/DividerStyle" />
        <EditText
            p1:layout_width="match_parent"
            p1:layout_height="wrap_content"
            p1:id="@+id/editTextDescription" />
        <TextView
            p1:text="Price"
            p1:textAppearance="?android:attr/textAppearanceMedium"
            p1:layout_width="wrap_content"
            p1:layout_height="wrap_content"
            style="@style/LabelStyle"
            p1:id="@+id/textViewPrice" />
        <View
            style="@style/DividerStyle" />
        <EditText
            p1:layout_width="match_parent"
            p1:layout_height="wrap_content"
            p1:id="@+id/editTextPrice" />
        <LinearLayout
            p1:orientation="horizontal"
            p1:layout_width="match_parent"
            p1:layout_height="wrap_content"
            p1:id="@+id/linearLayout1">
            <Spinner
                p1:layout_width="0dp"
                p1:layout_height="match_parent"
                p1:layout_weight="1"
                p1:gravity="center_vertical"
                p1:id="@+id/spinnerCategory" />
            <CheckBox
                p1:text="Add New Category?"
                p1:layout_width="wrap_content"
                p1:layout_height="wrap_content"
                p1:id="@+id/checkBoxAddCategory" />
        </LinearLayout>
        <LinearLayout
            p1:orientation="horizontal"
            p1:layout_width="match_parent"
            p1:layout_height="wrap_content"
            p1:id="@+id/linearLayoutCategorySection">
            <EditText
                p1:layout_width="0dp"
                p1:layout_weight="1"
                p1:layout_height="wrap_content"
                p1:id="@+id/editTextAddCategory" />
            <Button
                p1:text="Button"
                p1:layout_width="wrap_content"
                p1:layout_height="wrap_content"
                p1:id="@+id/buttonCategory" />
        </LinearLayout>
    </LinearLayout>
</ScrollView>

What we have here is a Linear layout with two nested Linear Layout wrapped with a ScrollView. Notice the Checkbox, what that does is it hides and shows a field that is used to add a Category. For example if a user is adding a Service called Income Tax Preparation Service and the Category for this service is called “Professional Service” if this category already exist in the drop down list then the user can just select it there is no need to show the field to add category in this case but if the Category does not exist then the user can click the Checkbox which will expand the fields to show the Add Category field.

Category Table

Since we are saving the categories to the database, we need to create a Table to hold the list of categories. And since we are using the SQLite.Net to manage persistence we need to to create a class that the SQLite.net ORM will work with. So go ahead and add a class to the Model folder called ServiceCategory.cs and the this class only has a Name property for now and it inherits from the IBusinessEntity this way we can re-use the existing database logic which works with anything that extends IBusinessEntity.

public class ServiceCategory : IBusinessEntity
    {
        [PrimaryKey, AutoIncrement, Column("_id")]
        public int Id { get; set; }
        public string Name { get; set; }
    }

And then you add a statement to create the Category table to the ServiceDatabase.cs file like so

CreateTable<ServiceCategory>()

We will also need to create CategoryRepository class that will handle the CRUD operation for the Categories class and also create a ServiceManager.cs that uses Repository. This may appear like unneccessary overhead but it does allow for code maintainability in the long run. I believe that it is possible to have a Generic Repository of type T so that we do not have to create a new repository class each time. I will look into that later. Look at the Github repo for the repository and manager class code.

Creating a Custom Xamarin Android Catalog
Add a file to the root of the project called ServiceDialog.cs, this is a standard C# class and we will go ahead and make it inherit from DialogFragment. In this class we will do the following:

  • Create a method that instantiates and returns a new instance of this dialog
  • Inflate the view for this dialog which is the layout we created above
  • Create and initialize the properties for this Dialog
  • Add event handler for the drop down list (Spinner)
  • Add event handler for the Save button
  • Add listener that tells the calling Activity that an item has been added through the dialog

And here is the code for the Custom dialog with Xamarin Android

namespace XamarinDroidCustomListView
{
    public class ServiceDialog : DialogFragment
    {
        //Create class properties
        protected EditText NameEditText;
        protected EditText DescriptionEditText;
        protected EditText PriceEditText;
        protected EditText AddCategoryEditText;
        protected Spinner CategorySpinner;
        protected LinearLayout CategoryLayout;
        protected CheckBox CategoryCheckBox;
        protected Button CategoryButton;

        //Create the string that will hold the value
        //Of the category drop down selected item
        protected string SelectedCategory = "";

        /// <summary>
        /// Method that creates and returns and instance of this dialog
        /// </summary>
        /// <returns></returns>
        public static ServiceDialog NewInstance()
        {
            var dialogFragment = new ServiceDialog();
            return dialogFragment;
        }

        
       
        public override Dialog OnCreateDialog(Bundle savedInstanceState)
        {
            base.OnCreate(savedInstanceState);

            // Begin building a new dialog.
            var builder = new AlertDialog.Builder(Activity);

            //Get the layout inflater
            var inflater = Activity.LayoutInflater;

            //Inflate the layout for this dialog
            var dialogView = inflater.Inflate(Resource.Layout.AddServiceDialog, null);

            if (dialogView != null)
            {
                //Initialize the properties
                NameEditText = dialogView.FindViewById<EditText>(Resource.Id.editTextName);
                DescriptionEditText = dialogView.FindViewById<EditText>(Resource.Id.editTextDescription);
                PriceEditText = dialogView.FindViewById<EditText>(Resource.Id.editTextPrice);
                AddCategoryEditText = dialogView.FindViewById<EditText>(Resource.Id.editTextAddCategory);
                CategorySpinner = dialogView.FindViewById<Spinner>(Resource.Id.spinnerCategory);
               
                CategoryLayout = dialogView.FindViewById<LinearLayout>(Resource.Id.linearLayoutCategorySection);
                //Hide this section for now
                CategoryLayout.Visibility = ViewStates.Invisible;
                CategoryCheckBox = dialogView.FindViewById<CheckBox>(Resource.Id.checkBoxAddCategory);

                CategoryCheckBox.Click += (sender, args) =>
                {
                    //If checked, show the Category section otherwise hide
                    CategoryLayout.Visibility = 
                        CategoryCheckBox.Checked ? ViewStates.Visible : ViewStates.Invisible;
                };

                CategoryButton = dialogView.FindViewById<Button>(Resource.Id.buttonCategory);
                CategoryButton.Click += (sender, args) =>
                {
                    var category = AddCategoryEditText.Text.ToString();

                    //insert new category into the database
                    if (category.Trim().Length <= 0)
                    {
                        Toast.MakeText(Activity, "Please enter category name", ToastLength.Short);
                    }
                    else
                    {
                        var newCategory = new ServiceCategory {Name = category};
                        //Call the Category Manager to save the category
                        CategoryManager.SaveCategory(newCategory);
                        AddCategoryEditText.Text = "";
                        //Now load call the method that loads the Spinner
                        //So the Category you just added will be an available choice
                        //in the drop down
                        LoadSpinnerData();
                    }
                };
                //populate Spinner with data from database
                //Good candidate for async
                LoadSpinnerData(); 

                //Set default selection for the spinner
                //CategorySpinner.SetSelection(0);
                //SelectedCategory = CategorySpinner.SelectedItem.ToString();
                
                //Set on item selected listener for the spinner
                CategorySpinner.ItemSelected += spinner_ItemSelected;

                builder.SetView(dialogView);
                builder.SetPositiveButton("Add Service", HandlePositiveButtonClick);
                builder.SetNegativeButton("Cancel", HandleNegativeButtonClick);
            }


            //Create the builder 
            var dialog = builder.Create();
           
            //Now return the constructed dialog to the calling activity
            return dialog;
        }

        //Handler for the drop down list
        private void spinner_ItemSelected(object sender, AdapterView.ItemSelectedEventArgs e)
        {
            var spinner = (Spinner)sender;
            SelectedCategory = string.Format("{0}", spinner.GetItemAtPosition(e.Position));
        }

        private void HandlePositiveButtonClick(object sender, DialogClickEventArgs e)
        {
            var dialog = (AlertDialog) sender;

            //Create an instance of a ServiceItem object
            var service = new ServiceItem();

            //Extact the name that was given to this Service
            var name = NameEditText.Text.ToString
                (CultureInfo.InvariantCulture).Trim();

            //Check if the name is null
            //If not assign, set the name of the Service to this name
            if (string.IsNullOrEmpty(name))
            {
                NameEditText.Error = "Service name empty";
            }
            else
            {
                service.Name = name;
            }

            //No need to check if description is null
            //This field could be optional
            service.Description = DescriptionEditText.Text.ToString
                (CultureInfo.InvariantCulture).Trim();

            var price = double.Parse(PriceEditText.Text.ToString
                (CultureInfo.InvariantCulture));
            if (price > 0)
            {
                service.Price = price;
            }
            else
            {
                PriceEditText.Error = "Set price";
            }

            //Set the category to the value of the selected item
            //From the drop down list
            service.Category = SelectedCategory;

            //Save the ServiceItem to the database and check if the
            //result is successful
            var result = ServicesManager.SaveServiceItem(service);
            if (result == 1)
            {
                ((MainActivity) this.Activity).ServiceAdded(true);
                dialog.Dismiss();
            }
            
        }

        private void HandleNegativeButtonClick(object sender, DialogClickEventArgs e)
        {
            var dialog = (AlertDialog)sender;
            dialog.Dismiss();
        }

        /// <summary>
        /// This method fetches the Categories from the database and 
        /// Populates the drop down list (Spinner) with the return value
        /// </summary>
        private void LoadSpinnerData()
        {
            var tempCategories = (List<ServiceCategory>) CategoryManager.GetCategories();
            var categories = tempCategories.Select(category => category.Name).ToList();

            var categoryAdapter = new ArrayAdapter<string>(
                Activity, Android.Resource.Layout.SimpleSpinnerItem, categories);

            categoryAdapter.SetDropDownViewResource
                (Android.Resource.Layout.SimpleSpinnerDropDownItem);
            CategorySpinner.Adapter = categoryAdapter;
        }

    }
}

Notice how we are not showing the dialog after creating it but returning it to the calling Activity. Now from the calling Activity, the MainActivity.cs in this case we just need to add a standard menu item to the Menu with text set to show always like so:

<?xml version="1.0" encoding="utf-8" ?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
  <item android:id="@+id/action_add_services"
        android:title="Add Services"
        android:showAsAction="always" />

</menu>

With this we can now instantiate the custom dialog when someone clicks on the “Add Services” button in the menu overflow like so:

public override bool OnOptionsItemSelected(IMenuItem item)
        {
            var id = item.ItemId;
            //If someone clicks on the Add Service button
            if (id == Resource.Id.action_add_services)
            {
                //Create a new dialog and all show on
                //That dialog
                var dialog = ServiceDialog.NewInstance();
                dialog.Show(FragmentManager, "dialog");
            }
            return base.OnOptionsItemSelected(item);
        }

And that is it, with this you are now able to add an item to the Custom list and you will be able to add categories dynamically. And with a little massaging you should be able to re-use the same dialog to edit the item.

Because of the way that the custom listview was created, the new items will show up immediately after you add them because after you dismiss the custom dialog box and return back to the MainActivity, the onResume method is called and in there is a method called LoadData() which reloads the data for the custom listview including the item that you just added so the listview is up to date. That is it with adding custom dialog with Xamarin Android.

Happy coding

About the Author valokafor

I am a Software Engineer with expertise in Android Development. I am available for Android development projects.

follow me on:

Leave a Comment: