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!

Consume Twitter REST API v1.1 with ADF Mobile

The new version of Twitter REST API has been out for a while now, and one of the biggest changes that needs to be implemented now in order to consume it, is that for GET methods such as search / tweets, all the requests need to be sent with OAuth signature as part of the Authorisation header, so I’ll show a little work around on how to perform these requests by creating an ADF Mobile application that calls the GET search/tweets method with a OAuth 1.0a HMAC-SHA1 signature as part of the Authorisation header…

Before starting make sure that you:

  • Have your own Twitter account (pretty obvious I know).
  • Have created your Twitter app from https://apps.twitter.com
  • Already generated the API Keys for your Twitter app.

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://api.twitter.com.  We will call this connection from our java class later:
      Screen Shot 2014-04-11 at 6.52.41 PM
      Don´t worry about the not accessible message, that´s why we are generating our OAuth signature!
    3. Considering the JSON expected results explained in the documentation, on the ViewController project create a new Java Class to handle the response:
      import oracle.adfmf.json.JSONArray;
      
      /**
       * This class is used to hanlde the response from Twitter's search method.
       * @version 1.0
       */
      public class TwitterResponse {
      
          /** JSON Array to handle error retrieved by search method. **/
          private JSONArray errors;
      
          /** JSON Array to handle the response. **/
          private JSONArray statuses;
      
          //Getters and Setters
      }
      

      Notice that the variables names in this Java Class must match the exact JSON response objects in order to be parsed correctly.

    4. Now we need to create another Java Class to be used in the .amx Page to show the information retrieved from the method:
      /**
       * This class is used to show a Twitter search record.
       * @version 1.0
       */
      public class TwitterSearchRecord {
      
          /** The name of the user who created the tweet. **/
          private String userName;
      
          /** The Id of the user who created tweet. **/
          private String userId;
      
          /** The tweet message. **/
          private String message;
      
          /** The user's profile photo URL. **/
          private String profilePhoto;
      
          // Getters and Setters
      }
      
    5. Create a new feature in the adfmf-feature.xml file and specify the content as AMX Page.
      Screen Shot 2014-04-11 at 9.07.34 PM
    6. In order to perform a successful call we need to create our OAuth signature as described here. A couple of important things need to be considered:
      1. We need to generate the oauth_nonce:  a Base 64 encoding unique token of random data which needs to be created on every request.
      2. We need to create the signature using HMAC-SHA1 method.

      To accomplish this, I created a JavaScript resource which exposes 2 functions, one to retrieve the nonce, and another one to retrieve the signature and save it as functions.js:

      // function to get the oauth_nonce
      getOauthNonce = function(){
         var chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz";
         var result = "";
         for (var i = 0; i < 6; ++i) {
                 var rnum = Math.floor(Math.random() * chars.length);
                  result += chars.substring(rnum, rnum+1);
          }
          return result;
      }
      
      //Function to generate the signature
      getSignature = function(){
          try{
               var args = arguments;
               //Call the Crypto API and create the HMAC String using
               //the signature baseString and the both the consumer and application secrets
               var hmacString = Crypto.HMAC(Crypto.SHA1, args[0], args[1], { asString:true });
               //encode the String to base64
               var signature = encodeBase64(hmacString);
               return signature;
          }catch(e){
              alert(e);
          }
      };
      

      As you can see, to generate the HMAC SHA1 String we use Crypto library from Google (I suggest to create a separate JS resource to include it as part of the feature content),  you can also refer to the getOauthNonce method from here.  For the encodeBase64 method I’m using a nice conversion function to ensure cross browser compatibility.

    7. Don’t forget to Include the reference to these JavaScript files in the  adfmf-feature.xml file.

Putting all Together!

  1. Create a Java class and include the following methods to perform the call (this Java class is going to be exposed as Data Control later) ,
    public class TwitterSearchOperations {
        /** This variable holds the rest request value **/
        protected String jsonResponse;
    
        /** The records to be retrieved in the amx page **/
        protected static TwitterSearchRecord[] searchRecords;
    
        /** used for the encodeURIComponent function */
        private static final BitSet dontNeedEncoding;
    
        /**
         * Constructor of the class.
         */
        public TwitterSearchOperations() {
            super();
        }
    
        /**
         * This method return a list of tweets based on a search text from the user
         * @param searchText the criteria for searchinf tweets
         * @return a list of tweets meeting the search criteria
         */
        public TwitterSearchRecord[] searchTweets(String searchText) {
            //Validates the searchText
            if(searchText == null || searchText.equals("")){
                return null;
            }
    
            try {
                //Create a new instance of the Service Adapter
                RestServiceAdapter restServiceAdapter = Model.createRestServiceAdapter();
                // Clear any previously request
                restServiceAdapter.clearRequestProperties();
                // Set the connection to search in connections.xml file
                restServiceAdapter.setConnectionName("TwitterConnection");
                //Encode the search String to UTF-8
                String value = encodeURIComponent(searchText);
                // Set the Request type
                restServiceAdapter.setRequestType(RestServiceAdapter.REQUEST_TYPE_GET);
                //Get the header value for Authorization
                String authorizationString = getAuthorizationString(value);
                //Specify the content type
                restServiceAdapter.addRequestProperty("Content-Type", "application/json");
                restServiceAdapter.addRequestProperty("X-HostCommonName", "api.twitter.com");
                restServiceAdapter.addRequestProperty("Host", "api.twitter.com");
                restServiceAdapter.addRequestProperty("X-Target-URI", "https://api.twitter.com");
                restServiceAdapter.addRequestProperty("Connection", "Keep-Alive");
                restServiceAdapter.addRequestProperty("Authorization", authorizationString);
                // 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("/1.1/search/tweets.json?q=" + value);
                this.setJsonResponse(restServiceAdapter.send(""));
                JSONBeanSerializationHelper helper = new JSONBeanSerializationHelper();
                //Parse the JSON Response
                TwitterResponse twitterBean =
                    (TwitterResponse)helper.fromJSON(TwitterResponse.class, this.getJsonResponse());
                //Validate the results
                if (twitterBean != null && twitterBean.getStatuses() != null) {
                    this.searchRecords = new TwitterSearchRecord[twitterBean.getStatuses().length()];
                    for (int i = 0; i < twitterBean.getStatuses().length(); i++) {
                        TwitterSearchRecord record = new TwitterSearchRecord();
                        JSONObject properties = (JSONObject)twitterBean.getStatuses().get(i);
                        //Map the JSON Response records to Search records object
                        fillTwitterRecord(properties, record);
                        this.searchRecords[i] = record;
                    }
                }
            } catch (Exception e) {
                throw new AdfException(e);
            }
            return this.searchRecords;
        }
    
        //private methods
    
        /**
         * This method constructs the authorization String to be added as the Authorization property value.
         * @param searchText the query search performed by the user
         * @return authorizationString a String value containing the authorization key
         */
        private String getAuthorizationString(String searchText) {
            String authorizationString = null;
            try {
                //Generate the oauthTimeStamp
                String oauthTimestamp = String.valueOf(System.currentTimeMillis() / 1000);
                //Retrieve the oauth_nonce from JS method
                String oauthNonce =
                    (String)AdfmfContainerUtilities.invokeContainerJavaScriptFunction("SearchFeature", "getOauthNonce",
                                                                                      new Object[] { });
                //Encode the application Secret
                String applicationSecret = URLEncoder.encode("yourAccessTokenSecret)", "UTF-8");
                //Encode the consumer secret
                String consumerSecret = URLEncoder.encode("yourConsumerSecret", "UTF-8");
                String consumerKey = "youConsumerKey";
                String oauthToken = "yourOauthToken(Access Token)";
                //Create the signatureBase String
                String signatureBaseString =
                    getSignatureBaseString(consumerKey, oauthNonce, oauthTimestamp, oauthToken, searchText);
                //Get the signature from JS method
                String signature =
                    (String)AdfmfContainerUtilities.invokeContainerJavaScriptFunction("SearchFeature", "getSignature",
                                                                                      new Object[] { signatureBaseString,
                                                                                                     consumerSecret + "&" +
                                                                                                     applicationSecret });
                //Construct the header value
                authorizationString =
                        "OAuth oauth_consumer_key=\"" + consumerKey + "\"," + "oauth_nonce=\"" + oauthNonce + "\", " +
                        "oauth_signature=\"" + URLEncoder.encode(signature, "UTF-8") + "\"," +
                        "oauth_signature_method=\"HMAC-SHA1\", " + "oauth_timestamp=\"" + oauthTimestamp + "\", " +
                        "oauth_version=\"1.0\"," + "oauth_token=\"" + URLEncoder.encode(oauthToken, "UTF-8") + "\"";
    
            } catch (UnsupportedEncodingException e) {
                throw new AdfException(e);
            }
            return authorizationString;
        }
    
        /**
         * This method retrieves the correct signatureBase String.
         * @param consumerKey
         * @param oauthNonce
         * @param oauthTimestamp
         * @param oauthToken
         * @param searchValue the query search performed by the user
         * @return signatureBaseString for signature
         */
        private String getSignatureBaseString(String consumerKey, String oauthNonce, String oauthTimestamp,
                                              String oauthToken, String searchValue) {
            String signatureBaseString = null;
            try {
                signatureBaseString =
                        "GET" + "&" + URLEncoder.encode("https://api.twitter.com/1.1/search/tweets.json", "UTF-8") + "&" +
                        URLEncoder.encode("oauth_consumer_key=" + consumerKey, "UTF-8") +
                        URLEncoder.encode("&" + "oauth_nonce=" + oauthNonce, "UTF-8") +
                        URLEncoder.encode("&" + "oauth_signature_method=" + "HMAC-SHA1", "UTF-8") +
                        URLEncoder.encode("&" + "oauth_timestamp=" + oauthTimestamp, "UTF-8") +
                        URLEncoder.encode("&" + "oauth_token=" + oauthToken, "UTF-8") +
                        URLEncoder.encode("&" + "oauth_version=" + "1.0", "UTF-8") +
                        URLEncoder.encode("&q=" + searchValue, "UTF-8");
            } catch (UnsupportedEncodingException e) {
                throw new AdfException(e);
            }
            return signatureBaseString;
    
        }
    
        /**
         * This method maps the properties of the JSON response to a record to show in the application.
         * @param properties JSON object with the properties needed to display
         * @param record the object on which all the properties are going to be filled
         */
        private void fillTwitterRecord(JSONObject properties, TwitterSearchRecord record) {
            try {
                record.setMessage(properties.getString("text"));
                JSONObject user = properties.getJSONObject("user");
                record.setProfilePhoto(user.getString("profile_image_url"));
                record.setUserName(user.getString("name"));
                record.setUserId(user.getString("screen_name"));
            } catch (JSONException 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();
        }
        //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); // ~
        }
    
        //Getters and Setters
    
    
  2. Expose this class as DataControl (right click on the Java file from the Application Navigator and select “Create Data Control”).
  3. Now just drag and drop the created method as a Parameter Form and the Result as List View into your amx page:Screen Shot 2014-04-12 at 8.38.59 PM
  4. Deploy your application to your simulator and start searching for tweets!:
    Screen Shot 2014-04-12 at 8.46.18 PM

You can download the sample project to see with more detail the solution using Crypto.js and the source Java Code. Remember that for this use case, you’re using your consumer Key and application Secret so every search performed to Twitter will be on your behalf.

Please comment if this tip was helpful and enjoy!

 

Fix for “JDeveloper Hangs on Windows 8 when viewing .amx page on preview mode”

One of the main reasons of creating this blog, is to share some tips & tricks that I couldn’t find on Google at first glance when I have a situation to solve. So my first post is about fixing a  problem on Jdeveloper when you try to preview an amx page while developing an ADF Mobile application on Windows 8 64 bits.

If you:

  1. Perform the installation of Jdeveloper 11.2.4 on your Windows 8 64 bit machine using the default settings, and with default settings I mean just click next.. next.. next until the installation of JDeveloper is completed.
  2. Download the latest ADF Mobile extension.
  3. Start developing your first ADF Mobile application and you want to preview your work.

Then your JDeveloper may hang like this (sometimes showing an alert saying “Unexpected Error”) :

Capture

This of course is a JDK error, and since we kept the default JDK that comes with JDeveloper when we downloaded the ADF Mobile extension, this error will not be resolved even if we change our Java Home to point to a proper 64 bit JDK 6 version. So, how to fix this? Unfortunately, since we’ve already downloaded the latest version of ADF Mobile extension we will have to uninstall JDeveloper (this because uninstalling a JDeveloper extension can be really tricky and it’s best if you start clean).

You can perform the following steps to accomplish this:

  1. Uninstall Jdeveloper 11.1.2.4  and make sure that you delete all files and folders under this Middleware home installation.
  2. Download and install  JDK 1.6.0_45 64 bits from here
  3. Reinstall Jdeveloper with default settings (don’t worry we will change this later ).
  4. Now, Before you open Jdeveloper after install, go to your <Middleware_Home>\jdeveloper\jdev\bin folder where we just installed JDeveloper.
  5. Open the jdev.conf file using notepad and comment the line “SetJavaHome C:\Oracle\Mobile1\jdk160_24”Screen Shot 2014-03-23 at 6.04.03 PM
  6. Start Jdeveloper, and when prompted about JDK home, point to the JDK 1.6.0_45 that we just installed in previous steps.                                                                                                                       Screen Shot 2014-03-23 at 6.05.34 PM
  7. Download the latest ADF Mobile extension.

After these steps are completed, when you test an amx page on preview mode you should  see the page correctly:

Screen Shot 2014-03-23 at 6.11.54 PM

Please comment if this was helpful for you!

Follow

Get every new post delivered to your Inbox.