Sunday, March 31, 2013

Handling the Device Back button in ADF Mobile Application

While developing an ADF Mobile application for Android/IOS, you may require to capture the device back button on user click. Here we will add little logic to exit the application once the user clicks on device back button.

We can directly use the phone gap backbutton api to override the default back button behaviour, you can register an event listener for the 'backbutton' event. It is no longer necessary to call any other method to over ride the back button behaviour. Now, you only need to register an event listener for 'backbutton'.

Application screen looks like below when it is deployed and run on the Android Device/Emulator. Click on the device back button will launch the confirm dialog, user can click OK to exit the application.


You can download the sample workspace from here. I have checked in Android Device/Emulator and it's working fine, not sure in IOS device.

Implementation Steps

Create an ADF Mobile Application, the application consists of two projects. Application Controller project of Application LifeCycle, Listeners, Device Features DataControl and ViewController project contains mobile features content like AMX Files, Task Flows and DataControl.

Right click on the ViewController project and in New Gallery window, select Web Tier-> HTML -> JavaScript File and name as "index.js". Next locate and expand the Application Sources folder, then expand the META-INF folder. You will see the adfmf-feature.xml file, click on the adfmf-feature.xml file to launch the Feature editor. Add a new feature by clicking the green plus sign on the Features table near top of the editor this will launch the new Create ADF Mobile Feature dialog, leave the default values and click ok.

In the Features table, select the newly created feature "feature1". Under the Features table, click the Content tab, and locate the Content table. Notice that the content item feature1.1 is created by default. Next add a new file by clicking the green plus sign and select ADF Mobile AMX Page, this will launch the new Create ADF Mobile AMX Page dialog, modify the File Name as index.amx.

Next in Includes section, click the green plus sign and Insert Include the javascript file as shown below.

Open the index.js file and below code catches the event that fires when the user presses the device back button.
//This is an event that fires when the user presses the device back button
document.addEventListener("deviceready", onDeviceReady, false);

function onDeviceReady() {
    document.addEventListener("backbutton", backKeyDown, true);
}

function backKeyDown() {
    var cFirm = confirm("Are you sure you want to exit the application?");
    if (cFirm == true) {
        //Code to exit the application
        navigator.app.exitApp();
    }
}

Tuesday, March 26, 2013

ADF Mobile : Display Custom Springboard layout with 3 * 3 matrix

Scenario here is to display 3 * 3 matrix dynamic table in custom springboard amx page using ApplicationFeatures data control with Features data collection.

Using amx:tableLayout only we might not be able to get the required structure. We need to use amx:panelGroupLayout with inlineStyle to achieve the above scenario. Credit goes to Matt Cooper, I have extracted the idea based on one of his forum reply. Here is the code below.
<amx:tableLayout id="tl1" shortDesc="features-table" inlineStyle="width:100%;">
  <amx:rowLayout id="rl1">
 <amx:cellFormat id="cf1" shortDesc="features-column" inlineStyle="width:100%;">
   <amx:panelGroupLayout id="pgl1" layout="wrap">
  <amx:iterator var="row" value="#{bindings.features.collectionModel}" id="i1">
    <amx:panelGroupLayout id="plam2" inlineStyle="width:33%;display:inline-block;" halign="center"
        valign="middle">
   <amx:tableLayout id="tl2">
     <amx:rowLayout id="rl2">
    <amx:cellFormat id="cf2" halign="center" valign="middle">
      <amx:commandLink id="cl1" actionListener="#{bindings.gotoFeature.execute}">
     <amx:image id="i2" source="/images/#{row.name}-springboard.png"
          inlineStyle="width:36px;height:36px"/>
     <amx:setPropertyListener from="#{row.id}" to="#{pageFlowScope.FeatureId}" type="action"/>
      </amx:commandLink>
    </amx:cellFormat>
     </amx:rowLayout>
     <amx:rowLayout id="rl3">
    <amx:cellFormat id="cf3" halign="center" valign="middle">
      <amx:commandLink id="cl2" actionListener="#{bindings.gotoFeature.execute}">
     <amx:outputText value="#{row.name}" id="ot2"/>
     <amx:setPropertyListener from="#{row.id}" to="#{pageFlowScope.FeatureId}" type="action"/>
      </amx:commandLink>
    </amx:cellFormat>
     </amx:rowLayout>
   </amx:tableLayout>
   <amx:spacer id="s2" height="25"/>
    </amx:panelGroupLayout>
  </amx:iterator>
   </amx:panelGroupLayout>
 </amx:cellFormat>
  </amx:rowLayout>
</amx:tableLayout>
Notice: - In panelGroupLayout, I have set the width to 33%, since I need 3 columns per row.  Adjust the width as desired to fit the number of features Items per row based on our requirements.

Application screen looks like below when it deployed and run on Android Device/Emulator. Click on any image/link will take you to the respective feature tab.

Monday, March 11, 2013

Get ADF Mobile Form values in managed bean using Accessor Iterator

Scenario is how to get the ADF Mobile form values in managed bean programmatically. Accessor Iterator can be used with AdfmfJavaUtilities.getValueExpression() method to get values.  getValueExpression method is for expressions that refer to values, this method should perform syntactic validation of the expression.

Below is one of the way to access from values, here is the code below.
ValueExpression ve =
            AdfmfJavaUtilities.getValueExpression("#{bindings.editEmployeeIterator.currentRow.dataProvider}",
                                                  Object.class);
        Object obj = ve.getValue(AdfmfJavaUtilities.getAdfELContext());
        Employee empRes = new Employee(); 
        if (obj instanceof ConcreteJavaBeanObject) {
            ConcreteJavaBeanObject cjbo = (ConcreteJavaBeanObject)obj;
            empRes = (Employee)cjbo.getInstance();
        }
        System.out.println(empRes.getId());
        System.out.println(empRes.getEmail());
        System.out.println(empRes.getFirstName());
        System.out.println(empRes.getLastName());
You can download the sample workspace from here. Application screen looks like below when it deployed and run on Android Device/Emulator. In first screen Employee list will be displayed, Next click on Add button to navigate for Employee form and looks like below.


Click on the Save button, check the employee form values by enabling debugger mode as shown below. You can check on how to Debugging ADF Mobile Apps on Android.


Tuesday, March 5, 2013

Integrating Open Weather Map in ADF Mobile Application

Today I tried implementing demo weather application using JSON-REST. OpenWeatherMap is a web service that provides free weather data and forecast API suitable for any cartographic services including web and smartphones applications. Ideology is inspired by OpenStreetMap and Wikipedia that make information free and available for everybody. OpenWeatherMap wide range of weather data - map with current weather, week forecast, precipitation, wind, clouds, data from weather stations, data from radars. Weather data is received from global meteorological broadcast services and more than 40000 weather stations, OpenWeatherMap provides response data as JSON string.

Few articles on JSON-REST - Using JSON-REST in ADF Mobile ADF Mobile REST JSON/XML 

Application screen looks like below when it deployed and run on Android Device/Emulator. Displays the blank screen. Click on the Add button to added the cities.


Enter and search the city name which you want to see weather information, this will internally call the Open Weather api to get the information about the city and also similar cities will be displayed as list items like shown in below screen. Now click on the City which you want to add it for the main screen, this will save the city details to the local SQLite database. You can add multiple cities to the main screen.


Weather information for added Cities with current date will be displayed, now clicking on the city name will display weather forecast screen.


In Forecast screen, next 7 days weather forecast information will be displayed for the selected city.


You can download the sample workspace from here.

Implementation Steps

Create an ADF Mobile Application, the application consists of two projects. Application Controller project of Application LifeCycle, Listeners, Device Features DataControl and ViewController project contains mobile features content like AMX Files, Task Flows and DataControl.

In ViewController project, create the connections for the below OpenWeatherMap API.
  • http://api.openweathermap.org/data/2.1/find/name - Find city via its name, the inquiry retunes list of cities that match search substring.
  • http://api.openweathermap.org/data/2.1/weather/city - Gets current weather in concrete chosen city
  • http://api.openweathermap.org/data/2.2/forecast/city - Gets Weather forecast in the city for the next 7 days.
To create connections go to File->New->Connections->Url Connection and name the connections mentioned in below connections.xml.
<?xml version = '1.0' encoding = 'UTF-8'?>
<References xmlns="http://xmlns.oracle.com/adf/jndi">
   <Reference name="OpenWeatherSearchCities" className="oracle.adf.model.connection.url.HttpURLConnection" xmlns="">
      <Factory className="oracle.adf.model.connection.url.URLConnectionFactory"/>
      <RefAddresses>
         <XmlRefAddr addrType="OpenWeatherSearchCities">
            <Contents>
               <urlconnection name="OpenWeatherSearchCities" url="http://api.openweathermap.org/data/2.1/find/name"/>
            </Contents>
         </XmlRefAddr>
      </RefAddresses>
   </Reference>
   <Reference name="OpenWeatherCurrentCityDetails" className="oracle.adf.model.connection.url.HttpURLConnection" xmlns="">
      <Factory className="oracle.adf.model.connection.url.URLConnectionFactory"/>
      <RefAddresses>
         <XmlRefAddr addrType="OpenWeatherCurrentCityDetails">
            <Contents>
               <urlconnection name="OpenWeatherCurrentCityDetails" url="http://api.openweathermap.org/data/2.1/weather/city"/>
            </Contents>
         </XmlRefAddr>
      </RefAddresses>
   </Reference>
   <Reference name="OpenWeatherForecast" className="oracle.adf.model.connection.url.HttpURLConnection" xmlns="">
      <Factory className="oracle.adf.model.connection.url.URLConnectionFactory"/>
      <RefAddresses>
         <XmlRefAddr addrType="OpenWeatherForecast">
            <Contents>
               <urlconnection name="OpenWeatherForecast" url="http://api.openweathermap.org/data/2.2/forecast/city"/>
            </Contents>
         </XmlRefAddr>
      </RefAddresses>
   </Reference>
</References>
In Application Controller project. Create a DBConnectionFactory.java class and this class contains code for connecting to SQLlite DB. Connecting to the SQLite database is somewhat different from opening a connection to an Oracle database. Please read the Access Device Native SQLite Database to Store Data article to know detailed steps for implementing SQLite.

Note:- Weather.sql file should be put in .adf/META-INF/ folder. You can get the sql file by downloading the OpenWeather application.

In ViewController project, locate and expand the Application Sources folder, create a CityCurrentDetails.java file and add the below code.
public class CityCurrentDetails extends City {
    private double temp;
    private double pressure;
    private double tempMin;
    private double tempMax;
    private double wind;
    private int clouds;
    private String description;
    private String icon;
    private transient PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this);

    public CityCurrentDetails() {
        super();
    }

    public CityCurrentDetails(double temp, double pressure, double tempMin, double tempMax, double wind, int clouds,
                              String description, String icon, int id, String name, String country, double lat,
                              double lon) {
        super();
        this.temp = temp;
        this.pressure = pressure;
        this.tempMin = tempMin;
        this.tempMax = tempMax;
        this.wind = wind;
        this.clouds = clouds;
        this.description = description;
        this.icon = icon;
        this.setId(id);
        this.setName(name);
        this.setCountry(country);
        this.setLat(lat);
        this.setLon(lon);
    }


    public void addPropertyChangeListener(PropertyChangeListener l) {
        propertyChangeSupport.addPropertyChangeListener(l);
    }

    public void removePropertyChangeListener(PropertyChangeListener l) {
        propertyChangeSupport.removePropertyChangeListener(l);
    }

    public void setTemp(double temp) {
        double oldTemp = this.temp;
        this.temp = temp;
        propertyChangeSupport.firePropertyChange("temp", oldTemp, temp);
    }

    public double getTemp() {
        return temp;
    }

    public void setPressure(double pressure) {
        double oldPressure = this.pressure;
        this.pressure = pressure;
        propertyChangeSupport.firePropertyChange("pressure", oldPressure, pressure);
    }

    public double getPressure() {
        return pressure;
    }

    public void setTempMin(double tempMin) {
        double oldTempMin = this.tempMin;
        this.tempMin = tempMin;
        propertyChangeSupport.firePropertyChange("tempMin", oldTempMin, tempMin);
    }

    public double getTempMin() {
        return tempMin;
    }

    public void setTempMax(double tempMax) {
        double oldTempMax = this.tempMax;
        this.tempMax = tempMax;
        propertyChangeSupport.firePropertyChange("tempMax", oldTempMax, tempMax);
    }

    public double getTempMax() {
        return tempMax;
    }

    public void setWind(double wind) {
        double oldWind = this.wind;
        this.wind = wind;
        propertyChangeSupport.firePropertyChange("wind", oldWind, wind);
    }

    public double getWind() {
        return wind;
    }

    public void setClouds(int clouds) {
        int oldClouds = this.clouds;
        this.clouds = clouds;
        propertyChangeSupport.firePropertyChange("clouds", oldClouds, clouds);
    }

    public int getClouds() {
        return clouds;
    }

    public void setDescription(String description) {
        String oldDescription = this.description;
        this.description = description;
        propertyChangeSupport.firePropertyChange("description", oldDescription, description);
    }

    public String getDescription() {
        return description;
    }

    public void setIcon(String icon) {
        String oldIcon = this.icon;
        this.icon = icon;
        propertyChangeSupport.firePropertyChange("icon", oldIcon, icon);
    }

    public String getIcon() {
        return icon;
    }
}
Create CityCurrentDetailsDC.java file and add the below code, create DataControl based on CityCurrentDetailsDC.java file.
public class CityCurrentDetailsDC {
    private static List s_cityCurrentDetails = null;
    private transient ProviderChangeSupport providerChangeSupport = new ProviderChangeSupport(this);

    public CityCurrentDetailsDC() {
        super();
    }

    public void addProviderChangeListener(ProviderChangeListener l) {
        providerChangeSupport.addProviderChangeListener(l);
    }

    public void removeProviderChangeListener(ProviderChangeListener l) {
        providerChangeSupport.removeProviderChangeListener(l);
    }

    /**
     * This method will read the record from the db and 
     * Gets current weather for all the cities listed
     * @return CityCurrentDetails[] list
     */
    public CityCurrentDetails[] fetchCityCurrentDetails() {
        s_cityCurrentDetails = new ArrayList();
        try {
            Connection conn = DBConnectionFactory.getConnection();
            s_cityCurrentDetails.clear();
            conn.setAutoCommit(false);

            PreparedStatement pStmt = conn.prepareStatement("SELECT * FROM CITY");
            ResultSet rs = pStmt.executeQuery();
            while (rs.next()) {
                if (rs.getInt("ID") != 0) {

                    RestServiceAdapter restServiceAdapter = Model.createRestServiceAdapter();
                    // Clear any previously set request properties, if any
                    restServiceAdapter.clearRequestProperties();
                    // Set the connection name
                    restServiceAdapter.setConnectionName("OpenWeatherCurrentCityDetails");
                    // Specify the type of request
                    restServiceAdapter.setRequestType(RestServiceAdapter.REQUEST_TYPE_GET);
                    restServiceAdapter.addRequestProperty("Content-Type", "application/json");
                    restServiceAdapter.addRequestProperty("Accept", "application/json; charset=UTF-8");
                    // 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("/" + rs.getInt("ID") + "?units=metric");
                    String response = "not found";
                    JSONBeanSerializationHelper jsonHelper = new JSONBeanSerializationHelper();

                    response = restServiceAdapter.send("");
                    CurrentDetailsServiceResponse serviceResponse =
                        (CurrentDetailsServiceResponse)jsonHelper.fromJSON(CurrentDetailsServiceResponse.class,
                                                                           response);

                    CityCurrentDetails cityCurDetails = new CityCurrentDetails();
                    cityCurDetails.setId(rs.getInt("ID"));
                    cityCurDetails.setName(rs.getString("NAME"));
                    cityCurDetails.setCountry(rs.getString("COUNTRY"));
                    cityCurDetails.setLat(rs.getDouble("LAT"));
                    cityCurDetails.setLon(rs.getDouble("LON"));

                    JSONObject cloudsObj = (JSONObject)serviceResponse.getClouds();
                    cityCurDetails.setClouds(cloudsObj.getInt("all"));

                    JSONObject mainObj = (JSONObject)serviceResponse.getMain();
                    DecimalFormat mainDecimalFormat = new DecimalFormat("###.#");
                    String temp = mainDecimalFormat.format(mainObj.getDouble("temp"));
                    cityCurDetails.setTemp(Double.valueOf(temp).doubleValue());
                    String tempMax = mainDecimalFormat.format(mainObj.getDouble("temp_max"));
                    cityCurDetails.setTempMax(Double.valueOf(tempMax).doubleValue());
                    String tempMin = mainDecimalFormat.format(mainObj.getDouble("temp_min"));
                    cityCurDetails.setTempMin(Double.valueOf(tempMin).doubleValue());
                    cityCurDetails.setPressure(mainObj.getDouble("pressure"));
                    // double humidity = mainObj.getDouble("humidity");

                    JSONObject windObj = (JSONObject)serviceResponse.getWind();
                    cityCurDetails.setWind(windObj.getDouble("speed"));

                    JSONArray weatherRes = (JSONArray)serviceResponse.getWeather();
                    for (int m = 0; m < weatherRes.length(); m++) {
                        JSONObject weatherObj = (JSONObject)weatherRes.getJSONObject(m);
                        cityCurDetails.setDescription(weatherObj.getString("description"));
                        cityCurDetails.setIcon(weatherObj.getString("icon"));
                    }
                    s_cityCurrentDetails.add(cityCurDetails);

                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        CityCurrentDetails c[] = null;
        c = (CityCurrentDetails[])s_cityCurrentDetails.toArray(new CityCurrentDetails[s_cityCurrentDetails.size()]);
        return c;
    }
}
The "OpenWeatherCurrentCityDetails" api will provide JSON response and structure will be as shown below.


Next step is important because we need do JSON deserialization, here we will use RestServiceAdapter and JSONBeanSerializationHelper classes. The RestServiceAdapter will handle the Rest Service and JSONBeanSerializationHelper helps us converting JSON to Java, for ex:-
CurrentDetailsServiceResponse serviceResponse =(CurrentDetailsServiceResponse)jsonHelper.fromJSON(CurrentDetailsServiceResponse.class, response);

Create CurrentDetailsServiceResponse.java file and add the below code. Make sure to use the proper data types.
public class CurrentDetailsServiceResponse {
    private int id;
    private JSONObject coord;
    private String name;
    private JSONObject main;
    private String dt;
    private JSONObject wind;
    private JSONObject clouds;
    private JSONArray weather;
    private JSONObject sys;

    public CurrentDetailsServiceResponse() {
        super();
    }

    public void setId(int id) {
        this.id = id;
    }

    public int getId() {
        return id;
    }

    public void setCoord(JSONObject coord) {
        this.coord = coord;
    }

    public JSONObject getCoord() {
        return coord;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setMain(JSONObject main) {
        this.main = main;
    }

    public JSONObject getMain() {
        return main;
    }

    public void setDt(String dt) {
        this.dt = dt;
    }

    public String getDt() {
        return dt;
    }

    public void setWind(JSONObject wind) {
        this.wind = wind;
    }

    public JSONObject getWind() {
        return wind;
    }

    public void setClouds(JSONObject clouds) {
        this.clouds = clouds;
    }

    public JSONObject getClouds() {
        return clouds;
    }

    public void setWeather(JSONArray weather) {
        this.weather = weather;
    }

    public JSONArray getWeather() {
        return weather;
    }

    public void setSys(JSONObject sys) {
        this.sys = sys;
    }

    public JSONObject getSys() {
        return sys;
    }
}
Next create a City.java file and add the below code.
public class City {
    private int id;
    private String name;
    private String country;
    private double lat;
    private double lon;
    private transient PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this);

    public City() {
        super();
    }

    public City(int id, String name, String country, double lat, double lon) {
        super();
        this.id = id;
        this.name = name;
        this.country = country;
        this.lat = lat;
        this.lon = lon;
    }

    public String getKey() {
        Integer i = new Integer(id);
        return i.toString();
    }

    public void addPropertyChangeListener(PropertyChangeListener l) {
        propertyChangeSupport.addPropertyChangeListener(l);
    }

    public void removePropertyChangeListener(PropertyChangeListener l) {
        propertyChangeSupport.removePropertyChangeListener(l);
    }

    public void setId(int id) {
        int oldId = this.id;
        this.id = id;
        propertyChangeSupport.firePropertyChange("id", oldId, id);
    }

    public int getId() {
        return id;
    }

    public void setName(String name) {
        String oldName = this.name;
        this.name = name;
        propertyChangeSupport.firePropertyChange("name", oldName, name);
    }

    public String getName() {
        return name;
    }

    public void setCountry(String country) {
        String oldCountry = this.country;
        this.country = country;
        propertyChangeSupport.firePropertyChange("country", oldCountry, country);
    }

    public String getCountry() {
        return country;
    }

    public void setLat(double lat) {
        double oldLat = this.lat;
        this.lat = lat;
        propertyChangeSupport.firePropertyChange("lat", oldLat, lat);
    }

    public double getLat() {
        return lat;
    }

    public void setLon(double lon) {
        double oldLon = this.lon;
        this.lon = lon;
        propertyChangeSupport.firePropertyChange("lon", oldLon, lon);
    }

    public double getLon() {
        return lon;
    }
}
Create CityDC.java file and add the below code, create DataControl based on CityDC.java file.
public class CityDC {
    private static List s_city = null;
    private transient ProviderChangeSupport providerChangeSupport = new ProviderChangeSupport(this);

    public CityDC() {
        super();
    }

    public void addProviderChangeListener(ProviderChangeListener l) {
        providerChangeSupport.addProviderChangeListener(l);
    }

    public void removeProviderChangeListener(ProviderChangeListener l) {
        providerChangeSupport.removeProviderChangeListener(l);
    }

    /**
     * Find city via its name, the inquiry retunes list of cities that match search substring.
     * @param queryString
     * @return City[] list
     */
    public City[] searchCities(String queryString) {
        s_city = new ArrayList();
        if (queryString != "" || queryString.length() > 0) {
            RestServiceAdapter restServiceAdapter = Model.createRestServiceAdapter();
            // Clear any previously set request properties, if any
            restServiceAdapter.clearRequestProperties();
            // Set the connection name
            restServiceAdapter.setConnectionName("OpenWeatherSearchCities");
            // Specify the type of request
            restServiceAdapter.setRequestType(RestServiceAdapter.REQUEST_TYPE_GET);
            restServiceAdapter.addRequestProperty("Content-Type", "application/json");
            restServiceAdapter.addRequestProperty("Accept", "application/json; charset=UTF-8");
            // 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("?q=" + queryString);
            String response = "not found";
            JSONBeanSerializationHelper jsonHelper = new JSONBeanSerializationHelper();

            try {
                // For GET request, there is no payload
                response = restServiceAdapter.send("");
                CityServiceResponse serviceResponse =
                    (CityServiceResponse)jsonHelper.fromJSON(CityServiceResponse.class, response);
                JSONArray listRes = (JSONArray)serviceResponse.getList();
                s_city = new ArrayList();
                for (int m = 0; m < listRes.length(); m++) {
                    JSONObject listObj = (JSONObject)listRes.getJSONObject(m);
                    int id = listObj.getInt("id");
                    String name = listObj.getString("name");

                    JSONObject coordObj = listObj.getJSONObject("coord");
                    double lat = coordObj.getDouble("lat");
                    double lon = coordObj.getDouble("lon");

                    JSONObject sysObj = listObj.getJSONObject("sys");
                    String county = sysObj.getString("country");
                    s_city.add(new City(id, name, county, lat, lon));
                }

            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        City c[] = null;
        c = (City[])s_city.toArray(new City[s_city.size()]);
        return c;
    }

    /**
     * Method will commit the details of newly created city object
     * @return
     */
    public boolean AddCityToDB(int id, String name, String country, double lat, double lon) {
        Trace.log(Utility.ApplicationLogger, Level.INFO, City.class, "AddCityToDB", "Coming inside AddCityToDB");
        boolean result = false;
        try {
            Connection conn = DBConnectionFactory.getConnection();
            conn.setAutoCommit(false);
            String insertSQL = "Insert into CITY (ID,NAME,COUNTRY,LAT,LON) values (?,?,?,?,?)";
            PreparedStatement pStmt = conn.prepareStatement(insertSQL);
            pStmt.setInt(1, id);
            pStmt.setString(2, name);
            pStmt.setString(3, country);
            pStmt.setDouble(4, lat);
            pStmt.setDouble(5, lon);
            pStmt.execute();
            conn.commit();
            result = true;
        } catch (SQLException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
        Trace.log(Utility.ApplicationLogger, Level.INFO, City.class, "AddCityToDB", "Exiting AddCityToDB");
        return result;
    }
}
The "OpenWeatherSearchCities" api will provide JSON response and structure will be as shown below.


Create a CityServiceResponse.java file to convert JSON response to java object.
public class CityServiceResponse {
    private int cod;
    private JSONArray list;
    private String units;

    public CityServiceResponse() {
        super();
    }

    public void setCod(int cod) {
        this.cod = cod;
    }

    public int getCod() {
        return cod;
    }
    
    public void setList(JSONArray list) {
        this.list = list;
    }

    public JSONArray getList() {
        return list;
    }

    public void setUnits(String units) {
        this.units = units;
    }

    public String getUnits() {
        return units;
    }
}
Next create a Forecast.java file and add the below code.
public class Forecast {
    private String dt;
    private double temp;
    private double night;
    private double eve;
    private double morn;
    private double pressure;
    private double humidity;
    private double speed;
    private String description;
    private String icon;
    private transient PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this);

    public Forecast(String dt, double temp, double night, double eve, double morn, double pressure, double humidity,
                    double speed, String description, String icon) {
        super();
        this.dt = dt;
        this.temp = temp;
        this.night = night;
        this.eve = eve;
        this.morn = morn;
        this.pressure = pressure;
        this.humidity = humidity;
        this.speed = speed;
        this.description = description;
        this.icon = icon;
    }

    public Forecast() {

    }

    public void addPropertyChangeListener(PropertyChangeListener l) {
        propertyChangeSupport.addPropertyChangeListener(l);
    }

    public void removePropertyChangeListener(PropertyChangeListener l) {
        propertyChangeSupport.removePropertyChangeListener(l);
    }

    public void setDt(String dt) {
        String oldDt = this.dt;
        this.dt = dt;
        propertyChangeSupport.firePropertyChange("dt", oldDt, dt);
    }

    public String getDt() {
        return dt;
    }

    public void setTemp(double temp) {
        double oldTemp = this.temp;
        this.temp = temp;
        propertyChangeSupport.firePropertyChange("temp", oldTemp, temp);
    }

    public double getTemp() {
        return temp;
    }

    public void setNight(double night) {
        double oldNight = this.night;
        this.night = night;
        propertyChangeSupport.firePropertyChange("night", oldNight, night);
    }

    public double getNight() {
        return night;
    }

    public void setEve(double eve) {
        double oldEve = this.eve;
        this.eve = eve;
        propertyChangeSupport.firePropertyChange("eve", oldEve, eve);
    }

    public double getEve() {
        return eve;
    }

    public void setMorn(double morn) {
        double oldMorn = this.morn;
        this.morn = morn;
        propertyChangeSupport.firePropertyChange("morn", oldMorn, morn);
    }

    public double getMorn() {
        return morn;
    }

    public void setPressure(double pressure) {
        double oldPressure = this.pressure;
        this.pressure = pressure;
        propertyChangeSupport.firePropertyChange("pressure", oldPressure, pressure);
    }

    public double getPressure() {
        return pressure;
    }

    public void setHumidity(double humidity) {
        double oldHumidity = this.humidity;
        this.humidity = humidity;
        propertyChangeSupport.firePropertyChange("humidity", oldHumidity, humidity);
    }

    public double getHumidity() {
        return humidity;
    }


    public void setDescription(String description) {
        String oldDescription = this.description;
        this.description = description;
        propertyChangeSupport.firePropertyChange("description", oldDescription, description);
    }

    public String getDescription() {
        return description;
    }

    public void setIcon(String icon) {
        String oldIcon = this.icon;
        this.icon = icon;
        propertyChangeSupport.firePropertyChange("icon", oldIcon, icon);
    }

    public String getIcon() {
        return icon;
    }

    public void setSpeed(double speed) {
        double oldSpeed = this.speed;
        this.speed = speed;
        propertyChangeSupport.firePropertyChange("speed", oldSpeed, speed);
    }

    public double getSpeed() {
        return speed;
    }
}
Create ForecastDC.java file and add the below code, create DataControl based on ForecastDC.java file.
public class ForecastDC {
    private static List s_forecast = null;

    public ForecastDC() {
        super();
    }

    /**
     * Gets Weather forecast in the city for the next 7 days 
     * @param cityId
     * @return Forecast[] list
     */
    public Forecast[] searchCities(int cityId) {
        RestServiceAdapter restServiceAdapter = Model.createRestServiceAdapter();
        // Clear any previously set request properties, if any
        restServiceAdapter.clearRequestProperties();
        // Set the connection name
        restServiceAdapter.setConnectionName("OpenWeatherForecast");
        // Specify the type of request
        restServiceAdapter.setRequestType(RestServiceAdapter.REQUEST_TYPE_GET);
        restServiceAdapter.addRequestProperty("Content-Type", "application/json");
        restServiceAdapter.addRequestProperty("Accept", "application/json; charset=UTF-8");
        // 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("/" + cityId + "?mode=daily_compact&units=metric");
        String response = "not found";
        JSONBeanSerializationHelper jsonHelper = new JSONBeanSerializationHelper();
        try {
            // For GET request, there is no payload
            response = restServiceAdapter.send("");
            ForecastServiceResponse serviceResponse =
                (ForecastServiceResponse)jsonHelper.fromJSON(ForecastServiceResponse.class, response);
            JSONArray listRes = (JSONArray)serviceResponse.getList();
            s_forecast = new ArrayList();
            System.out.println(listRes.length());
            for (int m = 0; m < listRes.length(); m++) {
                JSONObject listObj = (JSONObject)listRes.getJSONObject(m);
                Forecast forecast = new Forecast();
                
                long timeStamp = listObj.getInt("dt");
                Calendar mydate = Calendar.getInstance();
                mydate.setTimeInMillis(timeStamp * 1000);
                
                SimpleDateFormat dateFormat = new SimpleDateFormat("E dd");
                dateFormat.setCalendar(mydate);
                String dateVal = dateFormat.format(mydate.getTime());
                forecast.setDt(dateVal);
                
                forecast.setTemp(listObj.getDouble("temp"));
                forecast.setEve(listObj.getDouble("eve"));
                forecast.setMorn(listObj.getDouble("morn"));
                forecast.setNight(listObj.getDouble("night"));
                forecast.setHumidity(listObj.getDouble("humidity"));
                forecast.setPressure(listObj.getDouble("pressure"));
                forecast.setSpeed(listObj.getDouble("speed"));
                forecast.setDescription("coming here");
                JSONArray weatherRes = (JSONArray)listObj.getJSONArray("weather");
                for (int n = 0; n < weatherRes.length(); n++) {
                    JSONObject weatherObj = (JSONObject)weatherRes.getJSONObject(n);
                    forecast.setDescription(weatherObj.getString("description"));
                    forecast.setIcon(weatherObj.getString("icon"));
                }
                s_forecast.add(forecast);
            }

        } catch (Exception e) {
            e.printStackTrace();
        }

        Forecast f[] = null;
        f = (Forecast[])s_forecast.toArray(new Forecast[s_forecast.size()]);
        return f;
    }
}
The "OpenWeatherForecast" api will provide JSON response and structure will be as shown below.


Create a ForecastServiceResponse.java file to convert JSON response to java object.
public class ForecastServiceResponse {
    private int cnt;
    private JSONArray list;

    public ForecastServiceResponse() {
        super();
    }

    public void setCnt(int cnt) {
        this.cnt = cnt;
    }

    public int getCnt() {
        return cnt;
    }

    public void setList(JSONArray list) {
        this.list = list;
    }

    public JSONArray getList() {
        return list;
    }
}
In ViewController project. Locate and expand the Application Sources folder, then expand the META-INF folder. You will see the adfmf-feature.xml file, click on the adfmf-feature.xml file to launch the Feature editor. Add a new feature by clicking the green plus sign on the Features table near top of the editor this will launch the new Create ADF Mobile Feature dialog, modify the values as shown below.


In the Features table, select the newly created feature Email. Under the Features table, click the Content tab, and locate the Content table. Notice that the content item Weather.1 is created by default. Next add a new file by clicking the green plus sign and select taskflow option, this will launch the new Create ADF Mobile Task Flow dialog, modify the value as shown below.


Click on the WeatherTaskflow.xml to open the file in taskflow editor and follow the below steps.
  1. Create views and name them as DashBoardView, AddCityView and ForeCastView respectively.
  2. Draw the control flow case from DashBoardView to AddCityView and Outcome as "addCity".
  3. Draw the control flow case from AddCityView to DashBoardView and Outcome as "backToDashBoard".
  4. Draw the control flow case from DashBoardView to ForeCastView and Outcome as "foreCast".
  5. Draw the control flow case from ForeCastView to DashBoardView and Outcome as "backToDashBoard".
WeatherTaskflow.xml


Double click on DashBoardView view will launch Create ADF Mobile AMX Page dialog. Open the DashBoardView.amx page and to source tab and follow the below steps:
  1. In Header facet, amx:outputText set the value as "Open Weather". 
  2. In Primary Action facet, for amx:commandButton modify the values text: Refresh, actionListener: #{bindings.fetchCityCurrentDetails.execute}.
  3. In Secondary Action facet, for amx:commandButton modify the values text: Add, action: addCity.
  4. From DC palette drag and drop CityCurrentDetailsDC->fetchCityCurrentDetails->cityCurrentDetails as ADF Mobile List View and select the default options.
  5. Go to bindings Create executable binding->invoke Action with Id =invokeFetchCityCurrentDetails , Binds = fetchCityCurrentDetails and Refresh = always
  6. Modify the code as shown in below code. 
<amx:panelPage id="pp1">
        <amx:facet name="header">
            <amx:outputText value="Open Weather" id="ot1"/>
        </amx:facet>
        <amx:facet name="primary">
            <amx:commandButton text="Refresh" id="cb2" actionListener="#{bindings.fetchCityCurrentDetails.execute}"/>
        </amx:facet>
        <amx:facet name="secondary">
            <amx:commandButton text="Add" id="cb1" action="addCity"/>
        </amx:facet>
        <amx:listView var="row" value="#{bindings.CityCurrentDetails.collectionModel}"
                      fetchSize="#{bindings.CityCurrentDetails.rangeSize}" id="lv1"
                      styleClass="adfmf-listView-insetList">
            <amx:listItem id="li1" showLinkIcon="false" shortDesc="Forecast Row" action="foreCast">
                <amx:setPropertyListener from="#{row.id}" to="#{pageFlowScope.forecastCityId}" type="action"/>
                <amx:setPropertyListener from="#{row.name}" to="#{pageFlowScope.forecastName}" type="action"/>
                <amx:setPropertyListener from="#{row.country}" to="#{pageFlowScope.forecastCountry}" type="action"/>
                <amx:tableLayout id="tl4" width="100%" shortDesc="Forecast Table layout">
                    <amx:rowLayout id="rl41">
                        <amx:cellFormat id="cf4i" width="40px" rowSpan="2" halign="center" shortDesc="Image cell">
                            <amx:image id="imaged1" source="/Images/#{row.icon}.png"
                                       inlineStyle="height:32px;width:32px" shortDesc="Forecast Image"/>
                        </amx:cellFormat>
                        <amx:cellFormat id="cf41" columnSpan="3" width="100%" height="28px" shortDesc="City name cell">
                            <amx:outputText id="outputTextd1" value="#{row.name}, #{row.country} - #{row.description}"/>
                        </amx:cellFormat>
                    </amx:rowLayout>
                    <amx:rowLayout id="rl42">
                        <amx:cellFormat id="cf43" columnSpan="3" width="100%" height="20px" shortDesc="Temprature cell">
                            <amx:outputText id="outputTextd3"
                                            value="#{row.temp}°C, temperature from #{row.tempMin}°C to #{row.tempMax}°C"
                                            styleClass="adfmf-listItem-captionText"/>
                        </amx:cellFormat>
                    </amx:rowLayout>
                    <amx:rowLayout id="rl1">
                        <amx:cellFormat id="cf2" width="40px" halign="center" shortDesc=""/>
                        <amx:cellFormat id="cf1" columnSpan="3" width="100%" height="20px"
                                        shortDesc="Winds, Clouds, Pressure cell">
                            <amx:outputText id="ot2"
                                            value="Wind #{row.wind}m/s. clouds #{row.clouds}%, #{row.pressure} hpa"
                                            styleClass="adfmf-listItem-captionText"/>
                        </amx:cellFormat>
                    </amx:rowLayout>
                </amx:tableLayout>
            </amx:listItem>
        </amx:listView>
    </amx:panelPage>
DashBoardView amx page preview looks like below.

Double click on AddCityView view will launch Create ADF Mobile AMX Page dialog. Open the AddCityView.amx page and to source tab and follow the below steps:
  1. In Header facet, amx:outputText set the value as "Search City". 
  2. From DC palette drag and drop CityDC->SearchCities as ADF Mobile Parameter Form
  3. Move Search button code to Secondary Action facet.
  4. Drag and drop CityDC->SearchCities->City as ADF Mobile List View and select the default options
  5. Go to bindings and Create Control Binding as shown below
From Component palette drag and drop amx:setPropertyListener into amx:listItem and modify the values as shown in below code.
<amx:panelpage id="pp1">
        <amx:facet name="header">
            <amx:outputtext id="ot1" value="Search City">
        </amx:outputtext></amx:facet>
        <amx:facet name="secondary">
            <amx:commandbutton actionlistener="#{bindings.searchCities.execute}" disabled="#{!bindings.searchCities.enabled}" id="cb2" text="Search">
        </amx:commandbutton></amx:facet>
        <amx:panelformlayout id="pfl1">
            <amx:inputtext id="it2" inlinestyle="text-align:left;" value="#{bindings.queryString.inputValue}">
        </amx:inputtext></amx:panelformlayout>
        <amx:listview fetchsize="#{bindings.City.rangeSize}" id="lv1" value="#{bindings.City.collectionModel}" var="row">
            <amx:listitem action="backToDashBoard" actionlistener="#{bindings.AddCityToDB.execute}" id="li1">
                <amx:outputtext id="ot2" value="#{row.name}, #{row.country}">
                <amx:setpropertylistener from="#{row.id}" to="#{pageFlowScope.id}" type="action">
                <amx:setpropertylistener from="#{row.name}" to="#{pageFlowScope.name}" type="action">
                <amx:setpropertylistener from="#{row.country}" to="#{pageFlowScope.country}" type="action">
                <amx:setpropertylistener from="#{row.lat}" to="#{pageFlowScope.lat}" type="action">
                <amx:setpropertylistener from="#{row.lon}" to="#{pageFlowScope.lon}" type="action">
            </amx:setpropertylistener></amx:setpropertylistener></amx:setpropertylistener></amx:setpropertylistener></amx:setpropertylistener></amx:outputtext></amx:listitem>
        </amx:listview>
 </amx:panelpage>
AddCityView amx page preview looks like below.

 Double click on ForeCastView view will launch Create ADF Mobile AMX Page dialog. Open the AddCityView.amx page and to source tab and follow the below steps:
  1. In Header facet, amx:outputText set the value as "Forecast"
  2. In PrimaryAction facet, for amx:commandButton modify the values text: Back, action: backtoDashBoard. 
  3. From DC palette drag and drop ForecastDC->searchCities->Forecast as ADF Mobile List View and select the default options.
  4. In Edit Action Binding dialog for cityId parameter provide value as "#{pageFlowScope.forecastCityId}"
  5. Go to bindings Create executable binding->invoke Action with Id = invokeSearchCities, Binds = searchCities and Refresh = always
  6. Modify the code as shown in below code. 
<amx:panelPage id="pp1">
 <amx:facet name="header">
  <amx:outputText value="Forecast" id="ot1"/>
 </amx:facet>
 <amx:facet name="primary">
  <amx:commandButton id="cb1" text="Back" action="backtoDashBoard"/>
 </amx:facet>
 <amx:panelGroupLayout id="pgl1" styleClass="amx-style-groupbox" inlineStyle="width:400px;">
  <amx:outputText value="#{pageFlowScope.forecastName}, #{pageFlowScope.forecastCountry}" id="ot2"/>
 </amx:panelGroupLayout>
 <amx:listView var="row" value="#{bindings.Forecast.collectionModel}" fetchSize="#{bindings.Forecast.rangeSize}"
      id="lv1" styleClass="adfmf-listView-insetList" inlineStyle="width:400px;">
  <amx:listItem id="li1" showLinkIcon="false">
   <amx:tableLayout id="tl4" width="100%" shortDesc="Forecast Table layout">
    <amx:rowLayout id="rl41">
     <amx:cellFormat id="cf4i" columnSpan="4" halign="start" shortDesc="Date">
      <amx:outputText value="#{row.dt}, #{row.temp}°C - #{row.description} " id="ot3"/>
     </amx:cellFormat>
    </amx:rowLayout>
    <amx:rowLayout id="rl1">
     <amx:cellFormat id="cf1" width="20px" rowSpan="2" halign="center" shortDesc="Image cell">
      <amx:image id="imaged1" source="/Images/#{row.icon}.png"
           inlineStyle="height:32px;width:32px" shortDesc="Forecast Image"/>
     </amx:cellFormat>
     <amx:cellFormat id="cf41" columnSpan="3" width="100%" height="28px" shortDesc="City name cell">
      <amx:outputText id="outputTextd1"
          value="Morn #{row.morn}°C, Eve #{row.eve}°C, Night #{row.night}°C"
          styleClass="adfmf-listItem-captionText"/>
     </amx:cellFormat>
    </amx:rowLayout>
    <amx:rowLayout id="rl42">
     <amx:cellFormat id="cf43" columnSpan="3" width="100%" height="20px" shortDesc="Temprature cell">
      <amx:outputText id="outputTextd3"
          value="Wind #{row.speed}m/s. humidity #{row.humidity}%, #{row.pressure} hpa"
          styleClass="adfmf-listItem-captionText"/>
     </amx:cellFormat>
    </amx:rowLayout>
   </amx:tableLayout>
  </amx:listItem>
 </amx:listView>
</amx:panelPage>AddCityView amx page preview looks like below.

Deploy the application to device/emulator/package, Happy Weather Forecast :)