Create a Place Locator using Google Places API with ADF Mobile

For this tip I decided to extend the example given in this amazing post in Andrejus Blog about Geo Location using ADF Mobile and create a place locator using Google Places API , using this integration you can add new features to your  applications to show specific Google Maps search into them, like restaurants for example.

Before starting make sure that you:

  • Have your own browser API created from Google’s API console as described here.
  • Already enabled the Places API service from your Google Console:
    Screen Shot 2014-05-05 at 5.07.05 PM

Hands On!

    1. Generate a new ADF Mobile Application.
    2. Create a new URL Connection in the Application Resources section, the correct URL endpoint should be https://maps.googleapis.com/maps/api, this according to the nearby search method documentation of Google Places API:
      Screen Shot 2014-05-05 at 5.42.29 PM
    3. As I wrote before, this is an extension of an existing post from Andrejus blog, so we will reuse some of the logic found there… so, first of all, following the same approach as my previous post , in order to make successful REST service method call,  lets create 2 Java Classes in the View Controller Project, 1 to handle the JSON response retrieved by the API:
      import oracle.adfmf.json.JSONArray;
      
      /**
       * This class is used to handle the response from "Place Search" method retrieved by Google API.
       * @version 1.0
       */
      public class GooglePlacesResponse {
      
          /** This variable will hold the results array retrieved from the service call.**/
          private JSONArray results;
      
          /**
           * Class Constructor.
           */
          public GooglePlacesResponse() {
              super();
          }
      
          //Getters and Setters
      

      (Notice that the “results” variable matches the exact JSON response retrieved by the Service method)

      The second java class is to be used in the .amx Page to show on map the points retrieved from the method:

      /**
       * This class is used to show a point on the map retrieved by "Place Search method.
       * @version 1.0
       */
      public class GooglePlacesRecord {
      
       /** Latitude of the point. **/
       private double latitude;
      
       /** Longitude of the point. **/
       private double longitude;
      
       /**Icon of the point on the map. **/
       private String icon;
      
       /**
       * Class Constructor.
       */
       public GooglePlacesRecord() {
       super();
       }
      
       //Getters and Setters
      

      This class contains the necessary information to display a point on the map.

    4. Create a new feature in the adfmf-feature.xml file and create a new TaskFlow
      :Screen Shot 2014-05-10 at 9.23.07 PM
    5. Add 2 views into the task flow and connect them:
      Screen Shot 2014-05-10 at 9.26.20 PM
    6. The start location monitor view is part of Andrejus’ blog (as previously mentioned), to handle the start of the Geo Location in the device:
      Screen Shot 2014-05-12 at 2.16.49 PM
      Notice that, in order to handle all the operations of this view, I’ve created a special Java Class that holds the XY coordinates that were retrieved by the startLocationMonitor method and that are going to be passed as parameters of the nearby search operation.

      /**
       *  This class will handle everything related to the device's locaion.
       *  @version 1.0
       */
      public class LocationBean {
      
          /** The current latitude of the device retrieved by the startLocationMonitor method. **/
          private double latitude = 0;
      
          /** The current latitude of the device retrieved by the startLocationMonitor method. **/
          private double longitude = 0;
      
          /** Used when subscribing to periodic location updates. **/
          private String watchId = "";
      
          /** The zoom level of the map. **/
          private int zoomLevel = 0;
      
          /** The center X to point the map. **/
          private double centerX = 0;
      
          /** The center Y to point the map. **/
          private double centerY = 0;
      
          /** Indicates if the location monitor is running **/
          private boolean started = false;
      
          /** Transient property to fire changes on the ui.*/
          private transient PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this);
      
          /**
           * Class constructor.
           */
          public LocationBean() {
              super();
          }
      
          /**
           * This method is invoked by the startLocationMonitor method in response to a location update.
           * @param currentLocation the current location changed
           */
          public void locationUpdated(Location currentLocation) {
              this.setLatitude(currentLocation.getLatitude());
              this.setLongitude(currentLocation.getLongitude());
              this.setWatchId(currentLocation.getWatchId());
          }
      
          /**
           * This method is used to refresh the XY position and zoom of the map.
           * @param actionEvent
           */
          public void stopLocationMonitor(ActionEvent actionEvent) {
              if (getWatchId().length() > 0) {
                  DeviceManagerFactory.getDeviceManager().clearWatchPosition(getWatchId());
                  setWatchId("");
                  this.setZoomLevel(14);
                  this.setCenterX(this.getLongitude());
                  this.setCenterY(this.getLatitude());
              }
          }
      
          //Getters and Setters
      
    7. Create a Java Class that is going to be exposed as Data Control, this class will include the method to perform the REST Service call to retrieve the nearbySearch method results:
      /**
       *  This bean will be exposed as Data Control to perform method calls to Google Places API.
       *  @version 1.0
       */
      public class GooglePlacesOperations {
      
          /** Variable to get the JSON response String. **/
          private String jsonResponse = null;
      
          /** Array of points to draw in the map **/
          private GooglePlacesRecord[] points;
      
          /** used for the encodeURIComponent function */
          private static final BitSet dontNeedEncoding;
      
          /**
           * Constructor of the class.
           */
          public GooglePlacesOperations() {
              super();
          }
      
          /**
           * Method exposed in Data Control to perform a REST call to Google Places API.
           * @param search the place to search for
           * @return an array of points to draw in the map
           */
          public GooglePlacesRecord[] getPlacesNearMe(String search) {
              try {
      
                  //Validates the searchText
                  if (search == null || search.equals("")) {
                      return null;
                  }
      
                  //Encode the search String to UTF-8
                  String value = encodeURIComponent(search);
                  RestServiceAdapter restServiceAdapter = Model.createRestServiceAdapter();
                  // Clear any previously request
                  restServiceAdapter.clearRequestProperties();
                  // Set the connection to search in connections.xml file
                  restServiceAdapter.setConnectionName("GooglePlacesConnection");
                  // Set the Request type
                  restServiceAdapter.setRequestType(RestServiceAdapter.REQUEST_TYPE_GET);
                  //Specify the content type
                  restServiceAdapter.addRequestProperty("Content-Type", "application/json");
                  // Specify the number of retries
                  restServiceAdapter.setRetryLimit(0);
                  // Set the URI which is defined after the endpoint in the connections.xml.
                  // The request is the endpoint + the URI being set
                  restServiceAdapter.setRequestURI("/place/nearbysearch/json?location=" + this.getLatitude() + "," +
                                                   this.getLongitude() + "&type=food&radius=2000&name=" + value +
                                                   "&sensor=false&key=yourKeyHere");
                  this.setJsonResponse(restServiceAdapter.send(""));
                  JSONBeanSerializationHelper helper = new JSONBeanSerializationHelper();
                  //Parse the Response from Google
                  GooglePlacesResponse googleResponse =
                      (GooglePlacesResponse)helper.fromJSON(GooglePlacesResponse.class, this.getJsonResponse());
                  if (googleResponse != null && googleResponse.getResults() != null) {
                      this.points = new GooglePlacesRecord[googleResponse.getResults().length()];
                      //Map the JSON response to a point to draw in the map
                      for (int i = 0; i < googleResponse.getResults().length(); i++) {
                          GooglePlacesRecord record = new GooglePlacesRecord();
                          fillGeoJSONRecord(record, googleResponse.getResults().getJSONObject(i));
                          this.points[i] = record;
                      }
                  }
              } catch (Exception e) {
                  throw new AdfException(e);
              }
      
              return this.points;
          }
      
          /**
           * Getter for latitude
           * @return the value for the latitude retrieved from the startLocationMonitor method
           */
          private double getLatitude() {
              ValueExpression veLatitude =
                  AdfmfJavaUtilities.getValueExpression("#{pageFlowScope.LocationBean.latitude}", Double.class);
              return ((Double)veLatitude.getValue(AdfmfJavaUtilities.getAdfELContext())).doubleValue();
          }
      
          /**
           * Getter for longitude
           * @return the value for the longitude retrieved from the startLocationMonitor method
           */
          private double getLongitude() {
      
              ValueExpression veLongitude =
                  AdfmfJavaUtilities.getValueExpression("#{pageFlowScope.LocationBean.longitude}", Double.class);
              return ((Double)veLongitude.getValue(AdfmfJavaUtilities.getAdfELContext())).doubleValue();
      
          }
      
          /**
           * Method that maps the JSON object retrieved to a record to draw in the map.
           * @param record the record to map
           * @param googleResponse the source object containing the information to map
           */
          private void fillGeoJSONRecord(GooglePlacesRecord record, JSONObject googleResponse) {
              try {
                  record.setIcon(googleResponse.getString("icon"));
                  JSONObject geometry = googleResponse.getJSONObject("geometry");
                  JSONObject location = geometry.getJSONObject("location");
                  record.setLatitude(location.getDouble("lat"));
                  record.setLongitude(location.getDouble("lng"));
              } catch (Exception e) {
                  throw new AdfException(e);
              }
          }
      
          /**
           * Escapes all characters except the following: alphabetic, decimal digits, - _ . ! ~ * ' ( )
           * @param input A component of a URI
           * @return the escaped URI component
           */
          private static String encodeURIComponent(String input) {
              if (input == null) {
                  return input;
              }
              StringBuffer filtered = new StringBuffer(input.length());
              char c;
              for (int i = 0; i < input.length(); ++i) {
                  c = input.charAt(i);
                  if (dontNeedEncoding.get(c)) {
                      filtered.append(c);
                  } else {
                      final byte[] b = charToBytesUTF(c);
                      for (int j = 0; j < b.length; ++j) {
                          filtered.append('%');
                          filtered.append("0123456789ABCDEF".charAt(b[j] >> 4 & 0xF));
                          filtered.append("0123456789ABCDEF".charAt(b[j] & 0xF));
                      }
                  }
              }
              return filtered.toString();
          }
      
          /**
           * Method that retrievesa UTF-8 byte array representation of a char.
           * @param c the char to evaluare
           * @return a UTF-8 byte array representation of the char param
           */
          private static byte[] charToBytesUTF(char c) {
              try {
                  return new String(new char[] { c }).getBytes("UTF-8");
              } catch (UnsupportedEncodingException e) {
                  return new byte[] { (byte)c };
              }
          }
      
          //Static content
          static {
              dontNeedEncoding = new BitSet(256);
      
              // a-z
              for (int i = 97; i <= 122; ++i) {
                  dontNeedEncoding.set(i);
              }
              // A-Z
              for (int i = 65; i <= 90; ++i) {
                  dontNeedEncoding.set(i);
              }
              // 0-9
              for (int i = 48; i <= 57; ++i) {
                  dontNeedEncoding.set(i);
              }
      
              // '()*
              for (int i = 39; i <= 42; ++i) {
                  dontNeedEncoding.set(i);
              }
              dontNeedEncoding.set(33); // !
              dontNeedEncoding.set(45); // -
              dontNeedEncoding.set(46); // .
              dontNeedEncoding.set(95); // _
              dontNeedEncoding.set(126); // ~
          }
      
      

      For the previous code, notice that as part of the parameters of the method call I’ve specified the type to “food” and a radius of 2000 (meters), with this you restrict the results retrieved by the API to meet your specific localisation requirements.

    8. Expose this class as Data Control and Add the method call as parameter form in the second .amx Page
      Screen Shot 2014-05-12 at 2.27.17 PM
    9. Add a new geographic map into the page and from the Data Control just drag and drop the result of the “getPlacesNearMe” method into the Map component. Select the Data Type as coordinates and establish the XY attributes as requested:
      Screen Shot 2014-05-12 at 2.30.48 PM
    10. Finally you add the “source” property of the marker component to be set from the “icon” property that we populated from the API call:Screen Shot 2014-05-12 at 2.35.41 PM

 

Now you just deploy and start searching for places. You can download the sample project to look deeper into the location monitor page and Google Places API call.

Screen Shot 2014-05-12 at 2.41.21 PM
Please comment if this tip was helpful and enjoy!

About these ads

Tags: , , ,

About Christian Silva

I'm an IT specialist, husband and friend who likes to keep learning about new technologies. As an Oracle ADF Implementation Specialist I've decided to create this blog to share my knowledge in ADF Mobile, ADF and any other topic I believe I can contribute with.

2 responses to “Create a Place Locator using Google Places API with ADF Mobile”

  1. mrijad says :

    Can you post or send me whole project “Create a Place Locator using Google Places API with ADF Mobile”??

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: