September 20, 2015

Xamarin.iOS Location Autocomplete by using Google Place API

In brief: 
Making use of Google place API to provide the Location auto-complete option with text field in Xamarin iOS.


In my previous post shared my thought about How to apply Animation and Gesture recognition in Xamarin android,How to draw route between two geo-location in a google map xamrin iOSBest Practice and issues with ListView in Android Xamarin
How to use Google Place API with Autocomplete in Xamarin AndroidIntegrating Login by Google Account in Xamarin.Android,How to avoid ImageBitmap OutOfMemoryException and Rounded corner Image in android Xamarin,Drawing path between two location in xamarin android.

In detail:
In this post location autocomplete option for iOS application is implemented by using the Google place api REST webservice.
Whenever user enter the character,matching location with that entered character will be fetched from the Google server and binding back in UI .
As iOS doesn't support autocomplete textview like android,here made use of the regular text field with Table view to show the autocomplete text option.

In Steps:
1.) Login to Google developer console and enable the Google place API.
Follow this my previous post step 1.) to get the Google place API Browser key.

2.) Create new project and add  Storyboard with text field  and table view as follows,

3.)Code in viewController

3.1) onEditingChanged event of  text field make a REST request to Google server along with the input character and apikey generated in the above step. As shown below,
public static string strPlacesAutofillUrl="https://maps.googleapis.com/maps/api/place/autocomplete/json?input=inputChar&key=apiKey";
[Find details about  REST request and process the JSON response]

3.2)Parse the downloaded JSON string to google LocationPredictionClass.

3.3)Pass the "Prediction" class attribute "description" to tablesource class.

3.4)Bind the tablesource  class instance to table view to display the autocomplete option.

This complete above step 3. is shown in the following code.
LocationViewController.cs
//LocationViewController.cs
//---------------------------------------
using System;
using Foundation;
using UIKit;
using CoreGraphics;
using System.Text; 

namespace DataManip
{
 public partial class LocationViewController : UIViewController
 {
  public string strSampleString { get;set;} 
  LocationPredictionClass objAutoCompleteLocationClass;
  string strAutoCompleteQuery;
  LocationAutoCompleteTableSource objLocationAutoCompleteTableSource;
  public LocationViewController (IntPtr handle) : base (handle)
  {
   
  }
  public override void ViewDidLoad ()
  {
   base.ViewDidLoad ();  
   FnInitializeView();
   FnClickEventInit ();
  }

  void FnInitializeView()
  {  
   tableViewLocationAutoComplete.Hidden = true; 
   txtLocation.ShouldReturn  += (textField) => textField.ResignFirstResponder();  

   StringBuilder builderLocationAutoComplete = new StringBuilder (Constants.strPlacesAutofillUrl);
   builderLocationAutoComplete.Append ("?input={0}").Append("&key=").Append(Constants.strGooglePlaceAPILey);
   strAutoCompleteQuery = builderLocationAutoComplete.ToString ();
   builderLocationAutoComplete.Clear ();
   builderLocationAutoComplete = null;

  }
  void FnClickEventInit()
  {
   txtLocation.EditingChanged+=async delegate(object sender , EventArgs e )
   {
    if(string.IsNullOrWhiteSpace(txtLocation.Text))
    {
     tableViewLocationAutoComplete.Hidden =true;
    }
    else
    {
     if(txtLocation.Text.Length >3)
     { 

      //Autofill
      string strFullURL = string.Format (strAutoCompleteQuery,txtLocation.Text);
      objAutoCompleteLocationClass= await RestRequestClass.LocationAutoComplete (strFullURL);
 

      if(objAutoCompleteLocationClass!=null && objAutoCompleteLocationClass.status=="OK")
      {
       if ( objAutoCompleteLocationClass.predictions.Count > 0 )
       {
        if(objLocationAutoCompleteTableSource!=null)
        {
         objLocationAutoCompleteTableSource.LocationRowSelectedEventAction -= LocationSelectedFromAutoFill;
         objLocationAutoCompleteTableSource=null;
        }

        tableViewLocationAutoComplete.Hidden = false;
        objLocationAutoCompleteTableSource = new LocationAutoCompleteTableSource ( objAutoCompleteLocationClass.predictions );
        objLocationAutoCompleteTableSource.LocationRowSelectedEventAction += LocationSelectedFromAutoFill;
        tableViewLocationAutoComplete.Source = objLocationAutoCompleteTableSource;
        tableViewLocationAutoComplete.ReloadData ();
       }
       else
        tableViewLocationAutoComplete.Hidden = true;
      }
      else
      {
       tableViewLocationAutoComplete.Hidden=true;
      } 
     }
    }
   };
  
   btnBack.TouchUpInside+= delegate(object sender , EventArgs e )
   {
    DismissViewController(true,null);
   };
  }

  void LocationSelectedFromAutoFill(Prediction objPrediction)
  {
   Console.WriteLine ( objPrediction.description );
   txtLocation.ResignFirstResponder ();
  }
 }
}


Rerequestclass.cs
//Rerequestclass.cs
//--------------------------------------
using System;
using System.Threading.Tasks;
using System.Net;
using Newtonsoft.Json;

namespace DataManip
{
 public class RestRequestClass
 {
  static async Task<string> CallService(string strURL)
  {
   WebClient client = new WebClient ();
   string strResult;
   try
   {
    strResult=await client.DownloadStringTaskAsync (new Uri(strURL));
   }
   catch
   {
    strResult="Exception";
   }
   finally
   {
    client.Dispose ();
    client = null;
   }
   return strResult;
  }

  internal static async Task<LocationPredictionClass> LocationAutoComplete(string strFullURL)
  {
   LocationPredictionClass objLocationPredictClass = null;
   string strResult = await CallService (strFullURL);
   if(strResult!="Exception")
   {
    objLocationPredictClass= JsonConvert.DeserializeObject<LocationPredictionClass> (strResult);
   }
   return objLocationPredictClass;
  }

 }
}
LocationPredictionClass.cs
//LocationPredictionClass.cs
//------------------------
using System;
using System.Collections.Generic;

namespace DataManip
{
  
  public class MatchedSubstring
  {
   public int length { get; set; }
   public int offset { get; set; }
  }

  public class Term
  {
   public int offset { get; set; }
   public string value { get; set; }
  }

  public class Prediction
  {
   public string description { get; set; }
   public string id { get; set; }
   public List<MatchedSubstring> matched_substrings { get; set; }
   public string place_id { get; set; }
   public string reference { get; set; }
   public List<Term> terms { get; set; }
   public List<string> types { get; set; }
  }

     public class LocationPredictionClass
  {
   public List<Prediction> predictions { get; set; }
   public string status { get; set; }
  }

  
}
Constants.cs
Constants.cs
//-----------------
using System;

namespace DataManip
{
 public class Constants
 {  
  public static string strGooglePlaceAPILey="AIzaSyBK9hN_V************VI2_cG7QHZU8"; 
  public static string strPlacesAutofillUrl="https://maps.googleapis.com/maps/api/place/autocomplete/json";
 }
}
LocationAutoCompleteTableSource.cs
//LocationAutoCompleteTableSource.cs
//---------------------
using System;
using UIKit;
using System.Collections.Generic;

namespace DataManip
{
 public class LocationAutoCompleteTableSource : UITableViewSource
 {
  const string strCellIdentifier="Cell";
  readonly List<Prediction> lstLocations; 
  internal event Action<Prediction> LocationRowSelectedEventAction; 
 
  public LocationAutoCompleteTableSource(List<Prediction> arrItems)
  { 
   lstLocations=arrItems; 
  }

  public override nint RowsInSection (UITableView tableview, nint section)
  {
   return lstLocations.Count;

  }

  public override UIView GetViewForFooter (UITableView tableView, nint section)
  {
   return new UIView();
  }

  public override UITableViewCell GetCell (UITableView tableView, Foundation.NSIndexPath indexPath)
  {
   UITableViewCell cell = tableView.DequeueReusableCell ( strCellIdentifier ) ?? new UITableViewCell ( UITableViewCellStyle.Default , strCellIdentifier );
   cell.TextLabel.Text = lstLocations [indexPath.Row].description;
   cell.TextLabel.Font = UIFont.SystemFontOfSize (12);
   return cell;
  }
  public override void RowSelected (UITableView tableView, Foundation.NSIndexPath indexPath)
  {
   if ( LocationRowSelectedEventAction != null )
   {
    LocationRowSelectedEventAction ( lstLocations[indexPath.Row] );
   }
   tableView.DeselectRow ( indexPath , true );
  }
 }
}

Usage quota:

->As per the Google developer usage document, Place API Web Service enforces a default access limit of 1,000 requests per 24 hour period per app.
->This free usage can be expandable up to 1,50,000 requests per 24 hour period per app.This requires the verifying your identity by enabling the billing in google developer console using the credit card. Card will not be charged and used only for identity verification.
-> If you expect to exceed the above mentioned limit, need to Purchase a Google Maps API for Work license.
[Note: above mentioned usage limit is based on Google place API usage document as per the below date,check the usage limit for recent updates if any]

Final touch:
Location auto-complete option using Google place API is very much useful and user friendly feature whenever user need to enter the location  in an app. Because google place api has the very accurate and huge location option.

Thanks for your time here and visit again.

Happiee coding :)