Monday, April 25, 2016

MVC Repository Pattern (ASP.NET)

Introduction

This article will guide you through creating a small application using generic repository pattern in MVC framework.  This article is basically targeted for beginner to intermediate level programmer so that they could at least understand how to develop asp.net MVC app.  After reading this article you will be in position to understand the followings:
  • Basic concept of performing select, insert, update, and delete operation with the use of MVC repository
  • How to open bootstrap model popup window and pass value to the model popup using jQuery
  • Upload the images in desired storage location from the model window and display images in the model window with the help of jQuery ajax call and generic handler
For the practical application, I am creating a simple application of Employee repository which contains basic employee information along with their documents. All documents will be stored in a folder, storage location of which is specified in the appsetting of web.config file. Following screenshot shows how the employee image upload and display window looks like:


Background

Repositories are independent layer between data and its business logic of an application which acts as a bridge between these two components. It communicates between data access layer and the business logic layer of an application and hence keeps business logic and data access layers loosely coupled or isolated. A repository maintains and keeps track of data in its entities and data source. It requests data from database, maintains the relationship of retrieved data with its entities, and also updates the data source for any change of data in entities.

The application of repository helps to decrease repetition of code in an application. Once a repository is created, it can be used as much times as you need within your application. Since it separates business logic and data access, unit testing will be more easier and independent

Generic interface for custom repository

ICustomRepository is an interface which defines requirements that any classes which uses this interface must implement. Since we are performing CRUD operation, my ICustomRepository interface contains following declarations:
    public interface ICustomRepository<T> where T : class
    {
        IEnumerable<T> GetAllData();
        T SelectDataById(object id);
        void InsertRecord(T objRecord);
        void Update(T objRecord);
        void DeleteRecord(object id);
        void SaveRecord();
    }

MyCustomRepository Class

MyCustomRepository is repository class which is derived from the generic repository interface ICustomRepository. The definition of each interface declaration is implemented in this class. Following code demonstrate how the MyCustomRepository is implemented?
public class MyCustomRepository<T> : ICustomRepository<T> where T : class
{
    private EmployeeDbContext db = null;
    private IDbSet<T> dbEntity = null;

    public MyCustomRepository()
    {
        this.db = new EmployeeDbContext();
        dbEntity = db.Set<T>();
    }

    public MyCustomRepository(EmployeeDbContext _db)
    {
        this.db = _db;
        dbEntity = db.Set<T>();
    }

    public IEnumerable<T> GetAllData()
    {
        return  dbEntity.ToList();
    }

    public T SelectDataById(object id)
    {
        return dbEntity.Find(id);
    }

    public void InsertRecord(T objRecord)
    {
        dbEntity.Add(objRecord);
    }

    public void Update(T objRecord)
    {
        dbEntity.Attach(objRecord);
        db.Entry(objRecord).State = System.Data.Entity.EntityState.Modified;
    }

    public void DeleteRecord(object id)
    {
        T currentRecord = dbEntity.Find(id);
        dbEntity.Remove(currentRecord);
    }

    public void SaveRecord()
    {
        db.SaveChanges();
    }
}

Employee Model

The model employee contains fields to explain attributes of a employee such as EmployeeId, Employee Name, Date of Join, Current Salary amount, Contact number, and Email. These atrributes are mentioned in properties and specification and validation of each entities are mentioned with data annotation.
public class Employee
{
    [Key]
    public int EmployeeId { get; set; }
    [Required,MaxLength(70)]
    [Display(Name ="Employee Name")]
    public string EmployeeName { get; set; }
    [Required]
    [DataType(DataType.Date)]
    [Display(Name = "Join Date")]
    [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
    public DateTime? JoinDate { get; set; }
    [Required]
    [DisplayFormat(DataFormatString ="{0:N0}")]
    public decimal Salary { get; set; }
    [Required,MaxLength(30)]
    public string MobileNo { get; set; }
    [MaxLength(60)]
    [EmailAddress(ErrorMessage = "Invalid Email Address")]
    public string Email { get; set; }
}

Employee List

Following is the screenshot of page containing Employee List. It contains the necessary buttons and input to perform CRUD operation over employees. It also searches employees whose name matches with the name entered in search textbox. A bootstrap modal box will be displayed to show and upload documents of selected employee. A third party library PadgedList is used for paging.


Following code block reveals designer code of the above shown employee list view. When user clicks on the button "Documents" a bootstrap model window will be displayed. The model window will have a readonly textbox to show "EmployeeId" whose documents are supposed to be uploaded, a file browse button, upload button, and a close button. If the employee already have documents uploaded, it will be loaded on the model window when the model opens.
@model IEnumerable<RepositoryApp.Models.Employee>
@using PagedList.Mvc;
<link href="~/Content/PagedList.css" rel="stylesheet" type="text/css" />
<script type="text/javascript" src="~/Scripts/jquery-1.10.2.min.js"></script>

@{
    ViewBag.Title = "Employee List";
    var pagedlist = (PagedList.IPagedList)Model;
}

<div class="repository-app panel panel-primary">
    <div class="panel-heading"><strong> Employee List</strong></div>
    <div class="panel-body">
        <p>
            @Html.ActionLink("Add New Employee", "Create", "", htmlAttributes: new { @class = "btn btn-success" })
        </p>

        @using (Html.BeginForm("Index", "Employee", FormMethod.Post))
            {
            <p>
                Employee Name: @Html.TextBox("SearchString", ViewBag.CurrentFilter as string)
                <input type="submit" value="Search" />
            </p>
        }

        <table class="table table-striped">
            <tr>
                <th>@Html.DisplayNameFor(model => model.EmployeeName)</th>
                <th>@Html.DisplayNameFor(model => model.MobileNo)</th>
                <th>@Html.DisplayNameFor(model => model.JoinDate)</th>
                <th>@Html.DisplayNameFor(model => model.Salary)</th>
                <th>@Html.DisplayNameFor(model => model.Email)</th>
                <th></th>

            </tr>

          
            @foreach (var item in Model)
            {
                <tr>
                    <td>@Html.DisplayFor(mitem => item.EmployeeName)</td>
                    <td>@Html.DisplayFor(mitem => item.MobileNo)</td>
                    <td>@Html.DisplayFor(mitem => item.JoinDate)</td>
                    <td>@Html.DisplayFor(mitem => item.Salary)</td>
                    <td>@Html.DisplayFor(mitem => item.Email)</td>
                    <td>
                        @Html.ActionLink("Edit", "Edit", new { id = item.EmployeeId }, new { @class = "btn btn-primary", @style = "color:white" })
                        @Html.ActionLink("Details", "Details", new { id = item.EmployeeId }, new { @class = "btn btn-success", @style = "color:white" })
                        <a data-toggle="modal" data-id="@item.EmployeeId" title="Documents" class="open-AddEmpDocs btn btn-info" href="#addEmpDocs">
                            Documents
                        </a>

                        @Html.ActionLink("Delete", "Delete", new { id = item.EmployeeId }, new { @class = "btn btn-danger", @style = "color:white" })
                </tr>
            }

        </table>
    </div>

    <div class="panel-footer">
        Page @(pagedlist.PageCount < pagedlist.PageNumber ? 0 : pagedlist.PageNumber) of @pagedlist.PageCount

        @Html.PagedListPager(pagedlist, page => Url.Action("Index",
                new { page, sortOrder = ViewBag.CurrentSort, currentFilter = ViewBag.CurrentFilter }))

    </div>

</div>

@using (Html.BeginForm("UploadDocuments", "Employee", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
    @Html.AntiForgeryToken()
    <!-- Modal -->
    <div class="modal fade" id="addEmpDocs" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
        <div class="modal-dialog">
            <div class="modal-content">
                <div class="modal-header">
                    <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
                    <h4 class="modal-title">Documents</h4>
                </div>
                <div class="modal-body">
                    <p>
                        Employee Id:<input type="text" id="empidspan" name="empidtoupload" class="form-control" readonly="readonly" />
                    </p>
                    <p>
                        <label class="control-label">Select Documents</label>
                        <input name="empdocs" type="file" class="form-control">
                    </p>
                    <div id="divdocumentcontain">
                    </div>
                </div>
                <div class="modal-footer">

                    <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
                    <input type="submit" value="Upload" class="btn btn-primary"  />
                </div>
            </div><!-- /.modal-content -->
        </div><!-- /.modal-dialog -->
    </div><!-- /.modal -->
}

<script>
    $(document).on("click", ".open-AddEmpDocs", function () {
        var myBookId = $(this).data('id');
        $(".modal-body #empidspan").val(myBookId);
        $('#addEmpDocs').modal('show');
       
        $.ajax({
            type: 'GET',
            dataType: 'html',
            url: '/Employee/EmployeesDocs',
            data: { id: myBookId },
            success: function (response) {
                $(".modal-body #divdocumentcontain").html(response);
            },
            error: function (XMLHttpRequest, textStatus, errorThrown) {
                //alert(textStatus);
            }
        });
    });
</script>

Following code block contains complete code of the controller employee. The repository class MyCustomRepository is instantiated  in the constructor of Employee controller. The Index action method renders the employee List view according to the parameters of this method. The default number of rows for each page is 4 but it can be changed by changing the value of variable pageSize. Records in employee list can be filtered according to searchString value which is passed from search textbox in employee list view.
    public class EmployeeController : Controller
    {
        private ICustomRepository<Employee> empRepository = null;
        string docRootPath = System.Configuration.ConfigurationManager.AppSettings["DocumentRootPath"].ToString();
        public EmployeeController()
        {
            this.empRepository = new MyCustomRepository<Employee>();
        }

        // GET: Employee
        public ActionResult Index(string thisFilter, string searchString, int? page)
        {
  
            if (searchString != null)
            {
                page = 1;
            }
            else
            {
                searchString = thisFilter;
            }

            ViewBag.CurrentFilter = searchString;
            var employees = from emp in empRepository.GetAllData()
                            select emp;
            if (!String.IsNullOrEmpty(searchString))
            {
                employees = employees.Where(emp => emp.EmployeeName.ToUpper().Contains(searchString.ToUpper()));
            }

            int pageSize = 4;
            int pageNumber = (page ?? 1);
            return View(employees.ToPagedList(pageNumber, pageSize));

           // return View(employees);
        }

        public ActionResult Create()
        {
            return View(new Models.Employee());
        }

        [HttpPost]
        public ActionResult Create(Employee emp)
        {
            try
            {
                if (ModelState.IsValid)
                {
                    empRepository.InsertRecord(emp);
                    empRepository.SaveRecord();
                    return RedirectToAction("Index");
                }
            }
            catch (DataException)
            {
                ModelState.AddModelError("", "Unable to save record.");
            }
            return View(emp);
        }

        public ActionResult Edit(int id)
        {
            return View(empRepository.SelectDataById(id));
        }

        [HttpPost]
        public ActionResult Edit(Employee emp)
        {
            try
            {
                if (ModelState.IsValid)
                {
                    empRepository.Update(emp);
                    empRepository.SaveRecord();
                    return RedirectToAction("Index");
                }
            }
            catch (DataException)
            {

                ModelState.AddModelError("", "Unable to edit employee record.");
            }

            return View(emp);
        }

        public ActionResult Details(int id)
        {
            return View(empRepository.SelectDataById(id));
        }

        public ActionResult Delete(int id)
        {
            return View(empRepository.SelectDataById(id));
        }

        [HttpPost,ActionName("Delete")]
        [ValidateAntiForgeryToken]
        public ActionResult DeleteEmployee(int id)
        {
            try
            {
                if (ModelState.IsValid)
                {
                    empRepository.DeleteRecord(id);
                    empRepository.SaveRecord();
                }
            }
            catch (DataException)
            {
                ModelState.AddModelError("", "Unable to delete the record.");
            }
            return RedirectToAction("Index");

        }

        public ActionResult UploadDocuments()
        {
            return View("Index");
        }


        [HttpPost]
        public ActionResult UploadDocuments(string empidtoupload, HttpPostedFileBase empdocs)
        {
            try
            {
                if (empdocs != null && empdocs.ContentLength > 0)
                {
                    var rootPath = docRootPath + empidtoupload + "/";

                    if (!System.IO.Directory.Exists(rootPath))
                    {
                        Directory.CreateDirectory(rootPath);
                    }

                    var fileName = Path.GetFileName(empdocs.FileName);
                    var path = Path.Combine(rootPath, fileName);
                    empdocs.SaveAs(path);
                }
            }
            catch (IOException ex)
            {
                throw new Exception(ex.Message);
            }

            return RedirectToAction("Index");

        }

        public string EmployeesDocs(string id)
        {

            var rootPath = docRootPath + id + "/";
            List<string> lstFiles = new List<string>();

            if (System.IO.Directory.Exists(rootPath))
            {
                DirectoryInfo di = new DirectoryInfo(rootPath);
                foreach (FileInfo fi in di.GetFiles())
                {
                    
                    lstFiles.Add(id+"/"+ fi.Name);
                }
            }

            StringBuilder sb = new StringBuilder();
            sb.Append("<div>");
            foreach (string s in lstFiles)
            {
                var path = Path.Combine(rootPath, s.ToString());
                sb.AppendFormat("  <img src='{0}' width='500px' height='300px' alt='{1}'></img><br/>", "../Handallers/PhotoHandler.ashx?f=" + s.ToString(), id);
            }
            sb.Append("</div>");

            return sb.ToString();
        }

    }

If you click on "Document" button in employee list page, a model window similar to shown following opens. It contains EmployeeId and list of already uploaded images if any. More than one images can be added for the selected employee one by one. The default path to store uploaded documents is mentioned in web config file in the section appsetting. You can change the default
document upload path to your convenient location.



PhotoHandaler to show image from local source

The generic handler handles the display of images stored as a local resource. It reads image from file and returns to the calling member.
public class PhotoHandler : IHttpHandler
{
    string docRootPath = System.Configuration.ConfigurationManager.AppSettings["DocumentRootPath"].ToString();
    public void ProcessRequest(HttpContext context)
    {
        string f = context.Request.QueryString.Get("f");
        f = docRootPath + f;
        Image image = Image.FromFile(f);
        context.Response.Clear();
        context.Response.ClearHeaders();
        image.Save(context.Response.OutputStream, ImageFormat.Jpeg);
        context.Response.ContentType = "image/jpeg";
        HttpContext.Current.ApplicationInstance.CompleteRequest();
    }

    public bool IsReusable
    {
        get
        {
            return false;
        }
    }
}


Through this artical, I tried to implment asp.net mvc generic repository pattern with example. Moreover, tried to apply bootstrap styles and used jquery ajax to load imaged from computer location. 

Please feel free to comment and feedback. If you like visit my youtube channel https://www.youtube.com/channel/UCYESe2tTh9G6temYeaPNwwA

Tuesday, April 5, 2016

Xmarin Android Address Book

Xamarin Android App Using SQLite and Custom ListView

Sharad Chandra Pyakurel

This tutorial will explain how to develop android app with Xamarin tool provided by visual studio. This post will walk you through creating a small but complete android app. After reading this tutorial you will learn:
  • How to create android project in visual studio and setting up JDK and Android SDK location in visual studio IDE. How to create style and apply it on our layout components
  • How to create SQLite database and perform CRUD operation
  • How to create custom ListView and some operation with it
  • How to pass values between activities using intents
For your good understanding, I am creating app for Address Book which will have functionality of displaying contact lists, search contacts according to contact name, create new contacts, edit existing contact information, and delete contacts. Following snapshots shows how the app we are creating will look like.





Setup JDK and Android SDK Location

Before creating your first app, firstly you just need to make sure that Android Setting of your Xamarin is correct or not. You can check it from Tools->Options->Xamarin->Android Setting on Microsoft Visual Studio start page. Moreover, you can change the location of JDK and Android SDK if necessary.



Now create a new Xamarin project, you can create blank project by choosing “Blank App” option on New Project window. A default project containing Assets and Resources folder is ready. Moreover, initially you can change basic application properties such as “Android Version to Compile”, “Minimum android target version”, “and Default target version” just by right clicking project under solution explorer and properties.

SQLite Database for Address book

Add a new class AddressBookDbSelper.cs and extend it from SQLiteOpenHelper base class. In this class I would write code to create a table AddressBook on OnCreate method then write some methods to select, insert, update, and delete contacts.
  • GetAllContacts(): Retrive all contacts available in the database.
  • GetContactsBySearchName(string nameToSearch): Read contacts according to passed search parameter nameToSearch.  
  • AddNewContact(AddressBook contactinfo): Insert new contact information.
  • UpdateContact(AddressBook contitem): Update existing contact based on Id property of the class AddressBook.
  • DeleteContact(string  contactId): Delete existing contact according to contact Id.

  public class AddressBookDbHelper: SQLiteOpenHelper
    {
        private const string APP_DATABASENAME = "Student.db3";
        private const int APP_DATABASE_VERSION = 1;

        public AddressBookDbHelper(Context ctx):
            base(ctx, APP_DATABASENAME, null, APP_DATABASE_VERSION)
        {

        }

        public override void OnCreate(SQLiteDatabase db)
        {
            db.ExecSQL(@"CREATE TABLE IF NOT EXISTS AddressBook(
                            Id INTEGER PRIMARY KEY AUTOINCREMENT,
                            FullName TEXT NOT NULL,
                            Mobile  TEXT NOT NULL,
                            Email   TEXT NULL,
                            Details TEXT)");

db.ExecSQL("Insert into AddressBook(FullName,Mobile,Email,Details)VALUES('Ram','9851162459','sharad.pyakurel@gmail.com','this is details')");

        }

        public override void OnUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)
        {
            db.ExecSQL("DROP TABLE IF EXISTS AddressBook");
            OnCreate(db);
        }

        //Retrive All Contact Details
        public IList<AddressBook> GetAllContacts()
        { 
            SQLiteDatabase db = this.ReadableDatabase;

      ICursor c =  db.Query("AddressBook", new string[] { "Id", "FullName", "Mobile", "Email", "Details" }, null, null, null, null, null);

            var contacts = new List<AddressBook>();

            while (c.MoveToNext())
            {
                contacts.Add(new AddressBook
                        {
                            Id = c.GetInt(0),
                            FullName = c.GetString(1),
                            Mobile = c.GetString(2),
                            Email = c.GetString(3),
                            Details = c.GetString(4) });
            }

            c.Close();
            db.Close();

            return contacts;
        }


        //Retrive All Contact Details
        public IList<AddressBook> GetContactsBySearchName(string nameToSearch)
        { 
            SQLiteDatabase db = this.ReadableDatabase;

            ICursor c = db.Query("AddressBook", new string[] { "Id", "FullName", "Mobile", "Email", "Details" }, "upper(FullName) LIKE ?", new string[] {"%"+ nameToSearch.ToUpper() +"%"}, null, null, null, null);

            var contacts = new List<AddressBook>();

            while (c.MoveToNext())
            {
                contacts.Add(new AddressBook
                {
                    Id = c.GetInt(0),
                    FullName = c.GetString(1),
                    Mobile = c.GetString(2),
                    Email = c.GetString(3),
                    Details = c.GetString(4)
                });
            }

            c.Close();
            db.Close();

            return contacts;
        }

        //Add New Contact
        public void AddNewContact(AddressBook contactinfo)
        {
            SQLiteDatabase db = this.WritableDatabase;
            ContentValues vals = new ContentValues();
            vals.Put("FullName", contactinfo.FullName);
            vals.Put("Mobile", contactinfo.Mobile);
            vals.Put("Email", contactinfo.Email);
            vals.Put("Details", contactinfo.Details);
            db.Insert("AddressBook", null, vals);
        }

        //Get contact details by contact Id
        public ICursor getContactById(int id)
        {
            SQLiteDatabase db = this.ReadableDatabase;
            ICursor res = db.RawQuery("select * from AddressBook where Id=" + id + "", null);
            return res;
        }

        //Update Existing contact
        public void UpdateContact(AddressBook contitem)
        {
            if (contitem == null)
            {
                return;
            }

            //Obtain writable database
            SQLiteDatabase db = this.WritableDatabase;

            //Prepare content values
            ContentValues vals = new ContentValues();
            vals.Put("FullName", contitem.FullName);
            vals.Put("Mobile", contitem.Mobile);
            vals.Put("Email", contitem.Email);
            vals.Put("Details", contitem.Details);


            ICursor cursor = db.Query("AddressBook",
                    new String[] {"Id", "FullName", "Mobile", "Email", "Details" }, "Id=?", new string[] { contitem.Id.ToString() }, null, null, null, null);

            if (cursor != null)
            {
                if (cursor.MoveToFirst())
                {
                    // update the row
                    db.Update("AddressBook", vals, "Id=?", new String[] { cursor.GetString(0) });
                }

                cursor.Close();
            } 
        }


        //Delete Existing contact
        public void DeleteContact(string  contactId)
        {
            if (contactId == null)
            {
                return;
            }

            //Obtain writable database
            SQLiteDatabase db = this.WritableDatabase;

            ICursor cursor = db.Query("AddressBook",
                    new String[] { "Id", "FullName", "Mobile", "Email", "Details" }, "Id=?", new string[] { contactId }, null, null, null, null);

            if (cursor != null)
            {
                if (cursor.MoveToFirst())
                {
                    // update the row
                    db.Delete("AddressBook", "Id=?", new String[] { cursor.GetString(0) });
                }

                cursor.Close();
            } 
        }      
    }




The class AddressBook contains following properties which has matched with the columns of the table AddressBook



   public class AddressBook
    {
        public int Id { get; set; }
        public string FullName { get; set; }
        public string Mobile { get; set; }
        public string Email { get; set; }
        public string Details { get; set; }

        public static explicit operator AddressBook(Java.Lang.Object v)
        {
            throw new NotImplementedException();
        }
   }

Creating ListView and custom base adapter for ListView

The contact list will be populated in the list view. At the top of ListView two buttons for “Add new contact” and “Search”, and a EditText to input search parameters has added. descendantFocusability="beforeDescendants ensures This view will get focus before any of its descendants.

 <?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:descendantFocusability="beforeDescendants"
    android:focusableInTouchMode="true"
    android:paddingBottom="1dp"
    android:paddingLeft="1dp"
    android:paddingRight="1dp"
    android:paddingTop="0dp">
    <TableLayout
        android:id="@+id/tableLayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <TableRow>
            <Button
                android:id="@+id/contactList_btnAdd"
                style="@style/button_style"
                android:layout_weight="1"
                android:text="Add New Contact" />
        </TableRow>
        <TableRow>
            <EditText
                android:id="@+id/contactList_txtSearch"
                style="@style/input_style"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:hint="Search" />
            <Button
                android:id="@+id/contactList_btnSearch"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:background="#0f87ff"
                android:layout_marginRight="1dp"
                android:paddingTop="9dp"
                android:paddingBottom="9dp"
                android:text="Search"
                android:textAlignment="center"
                android:textAllCaps="false"
                android:minHeight="0dp"
                android:textColor="#fff"
                android:textSize="15sp" />
        </TableRow>
    </TableLayout>
    <ListView
        android:id="@+id/contactList_listView"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_below="@+id/tableLayout"
        android:layout_marginTop="0dp"
        android:layout_weight="1"
        android:clickable="true"
        android:divider="#b5b5b5"
        android:dividerHeight="1dp"
        android:listSelector="@drawable/list_selector" />
</RelativeLayout>

This id will be used to initialize this from activity cs file. We need to create a separate xml file for items in ListView row. The list row layout will define the layout for list row item. This layout will be used by the custom list adapter.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@drawable/list_selector"
    android:orientation="vertical"
    android:padding="2dp">

    <TableLayout
        android:layout_width="fill_parent"
        android:padding="3dp"
        android:layout_height="wrap_content">

        <TableRow
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"> 
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginRight="2dp"
                android:text="Name: "
                android:textColor="#000" /> 
            <TextView
                android:id="@+id/lr_fullName"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginRight="10dp"
                android:layout_weight="1"
                android:text="Full Name"
                android:textColor="#0000dd"
                android:textSize="15dp"
                android:textStyle="bold" />
        </TableRow>


        <TableRow
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"> 
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginRight="2dp"
                android:text="Mobile: "
                android:textColor="#000" /> 
            <TextView
                android:id="@+id/lr_mobile"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginRight="10dp"
                android:layout_weight="1"
                android:text="Mobile"
                android:textColor="#0000dd"
                android:textSize="15dip"
                android:textStyle="bold" /> 
        </TableRow>

        <TableRow
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"> 
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginRight="2dp"
                android:text="Email: "
                android:textColor="#000" /> 
            <TextView
                android:id="@+id/lr_email"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginRight="10dp"
                android:layout_weight="1"
                android:text="Email"
                android:textColor="#0000dd"
                android:textSize="15dp" />
        </TableRow>


        <TableRow
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"> 
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginRight="2dp"
                android:text="Description: "
                android:textColor="#000" /> 
            <TextView
                android:id="@+id/lr_descriptin"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginRight="10dp"
                android:layout_weight="1"
                android:text="description"
                android:textColor="#0000dd"
                android:textSize="15dp" />
        </TableRow>

    </TableLayout>

  <ImageView
    android:id="@+id/lr_deleteBtn"
    android:layout_width="50dip"
    android:layout_height="100dip"
    android:src="@drawable/delete"
    android:layout_alignParentRight="true"
    android:clickable="true" /> 

</RelativeLayout>

Since we are creating a custom ListView, we now need to create a custom adapter. This custom adapter will extend a generic base adapter AddressBook. This adapter’s constructor accepts an Activty as context and a List of type AddressBook. An adapter takes list of data objects as input, and each object in the list is mapped to a list row. Adapter also inflates the layout to be rendered for each row items. It is the most important and tricky part on creating a custom ListView.

    [Activity(Label = "ContactListBaseAdapter")]
    public partial class ContactListBaseAdapter : BaseAdapter<AddressBook>
    {
        IList<AddressBook> contactListArrayList;
        private LayoutInflater mInflater;
        private Context activity;
        public ContactListBaseAdapter(Context context,
                                                IList<AddressBook> results)
        {
            this.activity = context;
            contactListArrayList = results;
            mInflater = (LayoutInflater)activity.GetSystemService(Context.LayoutInflaterService);
        }

        public override int Count
        {
            get { return contactListArrayList.Count; }
        }

        public override long GetItemId(int position)
        {
            return position;
        }

        public override AddressBook this[int position]
        {
            get { return contactListArrayList[position]; }
        }

        public override View GetView(int position, View convertView, ViewGroup parent)
        {
            ImageView btnDelete;
            ContactsViewHolder holder = null;
            if (convertView == null)
            {
                convertView = mInflater.Inflate(Resource.Layout.list_row_contact_list, null);
                holder = new ContactsViewHolder();

                holder.txtFullName = convertView.FindViewById<TextView>(Resource.Id.lr_fullName);
                holder.txtMobile = convertView.FindViewById<TextView>(Resource.Id.lr_mobile);
                holder.txtEmail = convertView.FindViewById<TextView>(Resource.Id.lr_email);
                holder.txtDescription = convertView.FindViewById<TextView>(Resource.Id.lr_descriptin);
                btnDelete = convertView.FindViewById<ImageView>(Resource.Id.lr_deleteBtn);


                btnDelete.Click += (object sender, EventArgs e) =>
                { 
                    AlertDialog.Builder builder = new AlertDialog.Builder(activity);
                    AlertDialog confirm = builder.Create();
                    confirm.SetTitle("Confirm Delete");
                    confirm.SetMessage("Are you sure delete?");
                    confirm.SetButton("OK", (s, ev) =>
                    {
                        var poldel = (int)((sender as ImageView).Tag);

                        string id = contactListArrayList[poldel].Id.ToString();
                        string fname = contactListArrayList[poldel].FullName;

                        contactListArrayList.RemoveAt(poldel);

                        DeleteSelectedContact(id);
                        NotifyDataSetChanged();

                        Toast.MakeText(activity, "Contact Deeletd Successfully", ToastLength.Short).Show();
                    });
                    confirm.SetButton2("Cancel", (s, ev) =>
                    {

                    });

                    confirm.Show();
                };

                convertView.Tag = holder;
                btnDelete.Tag = position;
            }
            else
            {
                btnDelete = convertView.FindViewById<ImageView>(Resource.Id.lr_deleteBtn);
                holder = convertView.Tag as ContactsViewHolder;
                btnDelete.Tag = position;
            }

            holder.txtFullName.Text = contactListArrayList[position].FullName.ToString();
            holder.txtMobile.Text = contactListArrayList[position].Mobile;
            holder.txtEmail.Text = contactListArrayList[position].Email;
            holder.txtDescription.Text = contactListArrayList[position].Details;

            if (position % 2 == 0)
            {
                convertView.SetBackgroundResource(Resource.Drawable.list_selector);
            }
            else
            {
                convertView.SetBackgroundResource(Resource.Drawable.list_selector_alternate);
            } 
            return convertView;
        }

        public IList<AddressBook> GetAllData()
        {
            return contactListArrayList;
        }

        public class ContactsViewHolder : Java.Lang.Object
        {
            public TextView txtFullName { get; set; }
            public TextView txtMobile { get; set; }
            public TextView txtEmail { get; set; }
            public TextView txtDescription { get; set; }
        }

        private void DeleteSelectedContact(string contactId)
        {
            AddressBookDbHelper _db = new AddressBookDbHelper(activity);
            _db.DeleteContact(contactId);
        } 
    }


The ContactViewHolder enables you to access each list item view without the need for the look up. It represents items in the list row. You must override the Count, GetItemId and GetView method in your BaseAdapter class implementation. The Count method indicates the total number of rows in the list, GetItemId represent a unique id for reach item in the list. GetView inflates layout for each list row and render on the screen. To implement delete functionality on list view row, code to manage click event of delete button is placed in GetView method. The tag of the delete button will determine the position of row to be deleted. The position of row is divided by 2 to find alternate row in the ListView and apply style accordingly.

Setting custom Adapter to ListView

Once our custom adapter is ready, we need to instantiate this adapter to set to ListView.

    [Activity(Label = "Contact List", MainLauncher = true, Icon = "@drawable/icon")]
    public class MainActivity : Activity
    {
        Button btnAdd, btnSearch;
        EditText txtSearch;
        ListView lv;
        IList<AddressBook> listItsms = null;
        protected override void OnCreate(Bundle bundle)
        {
            base.OnCreate(bundle);

            // Set our view from the "main" layout resource
            SetContentView(Resource.Layout.Main);

            btnAdd = FindViewById<Button>(Resource.Id.contactList_btnAdd);
            btnSearch = FindViewById<Button>(Resource.Id.contactList_btnSearch);
            txtSearch = FindViewById<EditText>(Resource.Id.contactList_txtSearch);
            lv = FindViewById<ListView>(Resource.Id.contactList_listView);

            btnAdd.Click += delegate
            {
                var activityAddEdit = new Intent(this, typeof(AddEditAddressBookActivity));
                StartActivity(activityAddEdit);

            };

            btnSearch.Click += delegate
              {
                  LoadContactsInList();
              };

            LoadContactsInList(); 
        }

        private void LoadContactsInList()
        {
            AddressBookDbHelper dbVals = new AddressBookDbHelper(this);
            if (txtSearch.Text.Trim().Length < 1)
            {
                listItsms = dbVals.GetAllContacts();
            }
            else {

                listItsms = dbVals.GetContactsBySearchName(txtSearch.Text.Trim());
            } 

            lv.Adapter = new ContactListBaseAdapter(this, listItsms);

            lv.ItemLongClick += lv_ItemLongClick;
        }

        private void lv_ItemLongClick(object sender, AdapterView.ItemLongClickEventArgs e)
        {
            AddressBook o = listItsms[e.Position];

            //  Toast.MakeText(this, o.Id.ToString(), ToastLength.Long).Show();

            var activityAddEdit = new Intent(this, typeof(AddEditAddressBookActivity));
            activityAddEdit.PutExtra("ContactId", o.Id.ToString());
            activityAddEdit.PutExtra("ContactName", o.FullName);
            StartActivity(activityAddEdit);
        } 
    }




We have created lv_ItemLongClick call back to handle ListView long press event. The selected contact will open in new window for edit with existing information filled in when we long press the item in ListView. The value indicating edit mode is passed using intent PutExtra method.


Add Or Edit contacts

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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">
    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:paddingBottom="@dimen/activity_vertical_margin"
        android:paddingLeft="@dimen/activity_horizontal_margin"
        android:paddingRight="@dimen/activity_horizontal_margin"
        android:paddingTop="@dimen/activity_vertical_margin"
        android:scrollbars="none">
        <TableLayout
            android:layout_width="fill_parent"
            android:layout_height="wrap_content">
            <TableRow
                android:layout_width="fill_parent"
                android:layout_height="wrap_content">
                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="Contact Id " />
                <EditText
                    android:id="@+id/addEdit_ContactId"
                    style="@style/input_style"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:editable="false"
                    android:hint="Contact Id" />
            </TableRow>
            <TableRow
                android:layout_width="match_parent"
                android:layout_height="wrap_content">
                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="Full Name " />
                <EditText
                    android:id="@+id/addEdit_FullName"
                    style="@style/input_style"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_below="@+id/addEdit_ContactId"
                    android:layout_weight="1"
                    android:hint="Full Name" />
            </TableRow>
            <TableRow
                android:layout_width="match_parent"
                android:layout_height="wrap_content">
                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="Mobile" />
                <EditText
                    android:id="@+id/addEdit_Mobile"
                    style="@style/input_style"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_below="@+id/addEdit_FullName"
                    android:layout_weight="1"
                    android:hint="Mobile" />
            </TableRow>
            <TableRow
                android:layout_width="match_parent"
                android:layout_height="wrap_content">
                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="Email" />
                <EditText
                    android:id="@+id/addEdit_Email"
                    style="@style/input_style"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_below="@+id/addEdit_Mobile"
                    android:layout_weight="1"
                    android:hint="Email" />
            </TableRow>
            <TableRow
                android:layout_width="match_parent"
                android:layout_height="wrap_content">
                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="Description" />
                <EditText
                    android:id="@+id/addEdit_Description"
                    style="@style/input_style"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_below="@+id/addEdit_Email"
                    android:layout_weight="1"
                    android:gravity="top|left"
                    android:hint="Description"
                    android:inputType="textMultiLine"
                    android:lines="4"
                    android:scrollHorizontally="false"
                    android:scrollbars="vertical" />
            </TableRow>
            <TableRow
                android:layout_width="match_parent"
                android:layout_height="wrap_content">
                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text=" " />
                <Button
                    android:id="@+id/addEdit_btnSave"
                    style="@style/button_style"
                    android:layout_weight="1"
                    android:text=" Save " />
            </TableRow>
        </TableLayout>
    </ScrollView>
</RelativeLayout>




The xml code for style.xml which is used to apply style to controls is following:

<resources>
  <style name="input_style">
    <item name="android:background">@drawable/input_shape</item>
    <item name="android:layout_marginTop">1dp</item>
    <item name="android:layout_marginRight">10dp</item>
    <item name="android:layout_marginBottom">7dp</item>
    <item name="android:paddingLeft">8dp</item>
    <item name="android:paddingTop">9dp</item>
    <item name="android:paddingBottom">9dp</item>
    <item name="android:textSize">16sp</item>
    <item name="android:textColor">#000</item>
  </style>

  <style name="button_style">
    <item name="android:background">@drawable/button_shape</item>
    <item name="android:layout_width">match_parent</item>
    <item name="android:layout_height">wrap_content</item>
    <item name="android:layout_margin">1dp</item>
    <item name="android:paddingLeft">5dp</item>
    <item name="android:paddingRight">5dp</item>
    <item name="android:paddingTop">9dp</item>
    <item name="android:paddingBottom">9dp</item>
    <item name="android:textSize">16sp</item>
    <item name="android:textColor">#fff</item>
    <item name="android:textAllCaps">false</item>
    <item name="android:minHeight">35dp</item>
  </style>
</resources>



The value received by Intent.GetStringExtra("ContactId") ?? string.Empty will determine the save mode of the contact. If we get positive Intent.GetStringExtra value then existing contact information of the particular Id will be loaded by LoadDataForEdit(string contactId) method thereby indicating edit mode. If the value is empty the activity will open in new entry mode.



    [Activity(Label = "Add/Edit Contacts")]
    public class AddEditAddressBookActivity : Activity
    {
        EditText txtId, txtFullName, txtMobile, txtEmail, txtDescription;
        Button btnSave;
        protected override void OnCreate(Bundle savedInstanceState)
        {
            base.OnCreate(savedInstanceState);

            SetContentView(Resource.Layout.AddEditContacts);

            txtId = FindViewById<EditText>(Resource.Id.addEdit_ContactId);
            txtFullName = FindViewById<EditText>(Resource.Id.addEdit_FullName);
            txtMobile = FindViewById<EditText>(Resource.Id.addEdit_Mobile);
            txtEmail = FindViewById<EditText>(Resource.Id.addEdit_Email);
            txtDescription = FindViewById<EditText>(Resource.Id.addEdit_Description);
            btnSave = FindViewById<Button>(Resource.Id.addEdit_btnSave);

            btnSave.Click += buttonSave_Click;

            string editId = Intent.GetStringExtra("ContactId") ?? string.Empty;

            if (editId.Trim().Length > 0)
            {
                txtId.Text = editId;
                LoadDataForEdit(editId);
            }
        }

        private void LoadDataForEdit(string contactId)
        {
            AddressBookDbHelper db = new AddressBookDbHelper(this);
            ICursor cData = db.getContactById(int.Parse(contactId));
            if (cData.MoveToFirst())
            {
                txtFullName.Text = cData.GetString(cData.GetColumnIndex("FullName"));
                txtMobile.Text = cData.GetString(cData.GetColumnIndex("Mobile"));
                txtEmail.Text = cData.GetString(cData.GetColumnIndex("Email"));
                txtDescription.Text = cData.GetString(cData.GetColumnIndex("Details"));
            }
        }

        void buttonSave_Click(object sender, EventArgs e)
        {
            AddressBookDbHelper db = new AddressBookDbHelper(this);
            if (txtFullName.Text.Trim().Length < 1)
            {
                Toast.MakeText(this, "Enter Full Name.", ToastLength.Short).Show();
                return;
            }

            if (txtMobile.Text.Trim().Length < 1)
            {
                Toast.MakeText(this, "Enter Mobile Number.", ToastLength.Short).Show();
                return;
            }

            if (txtEmail.Text.Trim().Length > 0)
            {
                string EmailPattern = @"\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*";
                if (!Regex.IsMatch(txtEmail.Text, EmailPattern, RegexOptions.IgnoreCase))
                {
                    Toast.MakeText(this, "Invalid email address.", ToastLength.Short).Show();
                    return;
                }
            }

            AddressBook ab = new AddressBook();

            if (txtId.Text.Trim().Length > 0)
            {
                ab.Id = int.Parse(txtId.Text);
            }
            ab.FullName = txtFullName.Text;
            ab.Mobile = txtMobile.Text;
            ab.Email = txtEmail.Text;
            ab.Details = txtDescription.Text;

            try
            { 
                if (txtId.Text.Trim().Length > 0)
                {
                    db.UpdateContact(ab);
                    Toast.MakeText(this, "Contact Updated Successfully.", ToastLength.Short).Show();
                }
                else
                {
                    db.AddNewContact(ab);
                    Toast.MakeText(this, "New Contact Created Successfully.", ToastLength.Short).Show();
                }

                Finish();

                //Go to main activity after save/edit
                var mainActivity = new Intent(this, typeof(MainActivity));
                StartActivity(mainActivity);

            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message);
            }         
        }
    }





Thank you very much for reading this tutorial.