Project information
- Project name: WeatherApp
- Project identifier: JMEWeatherApp
- Owner: Jarrrgh
- Create date: 2011-11-29 09:36:40
- Project url: JMEWeatherApp
- Version control: https://projects.developer.nokia.com/hg/JMEWeatherApp/
- Project (WebDAV) shared folder: https://projects.developer.nokia.com/dav/JMEWeatherApp/
- Downloads: Downloads
Example: WeatherApp
WeatherApp is a simple weather application showing a four-day forecast with temperatures, relative humidity, wind speed, and wind direction. The application retrieves the current location via cell ID or GPS or for a user-defined location and uses it for retrieving weather forecast information. Worldweatheronline.com APIs are used for both weather data and location search data. The application uses the org.json.me library for parsing JSON. The application has been designed to work especially on various Series 40 devices with different form factors and input methods. WeatherApp also demonstrates in-app advertisement in form of banners and full-screen ads.
Prerequisites
You need the following to develop and test this MIDlet:
- Eclipse Pulsar or NetBeans with Java ME support
- Nokia SDK 1.0 for Java or newer
- A Series 40 or Symbian device with CLDC 1.1, MIDP 2.0
For more information about the MIDlet, see the following subsections on this page:
- Design for details about the design and functionality of the MIDlet
- Implementation for instructions on how to implement the classes that make up the MIDlet
You can download the project files for the MIDlet from the download page.
© Nokia 2011-2012.
Design
WeatherApp has been designed to scale on a range of Series 40 devices. Graphics are available in two sizes and have been tested on the following screen sizes: 128x160, 240x320, and 320x240 pixels. The application also runs on Symbian devices. The WeatherApp has a view for checking the weather view and some subviews for settings.
The quickest available positioning method is used. If the location based on cell ID cannot be retrieved, the application tries to get the location based on GPS. If that is not available either, the user needs to enter the location manually through the location search. The search has autocomplete. The application has been designed so that missing location APIs do not cause any errors: even if the device does not have any location APIs, the application will still run gracefully.
Worldweatheronline.com APIs are being used for both weather data and location search data. Due to licensing terms, the API key is not published in the source code of this application. If you compile the application from the sources, remember to include your own WorldWeatherOnline key in the Keys.java file or start the application in test mode. Test mode can be enabled by setting JAD attribute TestMode to "on". In the test mode the application uses a static example forecast data instead of a real forecast.
Implementation
Basic structure
The main view of WeatherApp is basically a Canvas, which draws forecasts for the selected day. All the settings views and location-related views use plain platform-styled lcdui items. View changing is handled by ViewMaster, which also takes care of storing views into a view stack. The view stack is used in the back functionality.
In-app advertising
WeatherApp introduces in-app advertising in form of banners and full-screen ads. In-app advertising lets you monetise your application by showing advertisements provided by Inneractive. Advertisements are monetised through impressions and clicks. The click-through rate, which is the ratio between impressions and clicks, also affects the revenue.
The user needs to be able to click a banner. When the banner is clicked, an advertisement endpoint url is launched inside a browser. On touch devices it is easy just to tap the banner, but non-touch devices also require focus handling, which allows user to select the banner and then click it.
In WeatherApp a full-screen ad is shown when user is about to exit the application. Additionally, banner ads are shown in the bottom view. One banner is shown for 60 seconds and then changed to a new one with a sliding animation. Since the average usage time of WeatherApp is probably fairly short, the ads need to be updated quite frequently. Inneractive recommends to use an interval of circa 2-3 minutes. For more information, visit the Inneractive website. The Ad SDK can also be downloaded from the Inneractive website. It contains good examples demonstrating the usage of the SDK. It also includes an Ad Placement Strategy guide and a step-by-step guide for adding in-app advertisements to your application.
It is advisable to retrieve ads inside a worker thread, so that the main thread does not get blocked. The following code shows how it has been done in WeatherApp:
private boolean running = false; private static MIDlet context; private AdListener listener = null; private class Worker extends Thread { private int task = 0; private long interval = -1; public synchronized void run() { running = true; while (running) { switch (task) { case DISPLAY_FULL_AD: IADView.displayInterstitialAd(context); if (listener != null) { listener.adShowNotify(); } break; case GET_BANNER_AD: Ad ad = null; try { ad = new Ad(IADView.getBannerAdData(context)); } catch (IllegalArgumentException iae) {} if (listener != null) { listener.bannerReceived(ad); } break; } try { if (interval < 0) { task = IDLE; wait(); } else { wait(interval); } } catch (InterruptedException ex) {} } } public synchronized void doTask(int task, long interval) { this.task = task; this.interval = interval; notify(); } }
Adding ads to WeatherApp required some modifications to the layout, since there needed to be some extra space for the banners. Unfortunately the layout that had been serving all the different screen resolutions did not work anymore, so an additional layout had to be designed for landscape and smaller screen sizes. Also it needs to be noted that full-screen ads bite quite a bit of memory, so it's advisable to free as much memory as possible before showing an ad.
Defining the location
The options for defining the location are standalone GPS, assisted GPS, online cell ID (see Determining Current Location via cell ID), WLAN, and offline cell ID. The Nokia Location API appendix provides LocationUtil, which can be used for specifying the desired positioning method. Unfortunately LocationUtil is only available on Series 40 devices with Java Runtime 1.0.0 for Series 40 or newer. Symbian devices do not support LocationUtil at all. Check the Developer's Guide for more information about Specifying the location provider. Note that the location based on cell ID can only be retrieved with the getLocation() method, whereas GPS locations can be retrieved frequently using the locationListener approach. To be able to listen for location changes based on the cell ID, getLocation needs to be called frequently in a loop. However, if the application is not signed, this will cause an access prompt on every call. For more information, see Best practises for listening to location updates with Java ME.
Now, to ensure that the application runs on devices that don't have LocationUtil or the Location API, they need to be hidden from the code execution, so that they are not loaded automatically. This means that neither the com.nokia.mid.location nor the javax.microedition.location packages can be imported inside the classes, which should run on all the devices. Classes that use either LocationUtil or Location API need to be isolated from the rest of the application. These classes can then be loaded at run time using Class.forName(<PACKAGE>.<CLASS_NAME>). This method will throw an exception if some classes are missing (for example, LocationUtil). However, the exception can be caught, necessary actions can be taken, and the application can continue trying to use the next best option. Take a look at the article How to use an optional API in Java ME, which explains how to do this in practice.
Requesting forecasts and locations
Weather data can be requested from World Weather Online's Free Local Weather API with a simple GET request, which includes the API key, response format, number of days and location information. Location can be defined as city name and optionally country name, IP address, ZIP code or geographic coordinates. In WeatherApp, both city/country names and latitude/longitude pairs are used for forecast retrieval depending whether the locationing is done automatically using cell ID/GPS or typed manually. WeatherApp uses JSON format, but the weather data is also available in XML and CSV format. API key can be obtained from the World Weather Online site and there is also a this handy-dandy Weather Feed API Request Builder available.
Forecast request example
http://free.worldweatheronline.com/feed/weather.ashx?q=London,United+Kingdom&format=json&num_of_days=5&key=<YOUR API KEY>
City/Location Search API is another free API provided by WorldWeatherOnline. WeatherApp uses it in location search for searching actual locations based on a city name or part of the city name. Free API limits the maximum number of possible responses to 3.
Location request example
http://www.worldweatheronline.com/feed/search.ashx?query=London&format=JSON&num_of_results=3&key=<YOUR API KEY>
Parsing JSON
JSON parsing is very simple with org.json.me. However, a few things need to be kept in mind. JSON is parsed using JSONArrays and JSONObjects, so keep your eye on the square brackets, to distinguish when a JSONArray should be used over JSONObject. The JSON below is part of a weather forecast response from World Weather Online.
{ "data": { "current_condition": [ { "cloudcover": "75", "humidity": "87", "observation_time": "02:41 PM", "precipMM": "3.0", "pressure": "991", "temp_C": "8", "temp_F": "46", "visibility": "10", "weatherCode": "302", "weatherDesc": [ { "value": "Moderate rain" } ], "weatherIconUrl": [ { "value": "http://www.worldweatheronline.com/images/wsymbols01_png_64/wsymbol_0018_cloudy_with_heavy_rain.png" } ], "winddir16Point": "ESE", "winddirDegree": "120", "windspeedKmph": "24", "windspeedMiles": "15" } ], "request": [ { "query": "London, United Kingdom", "type": "City" } ], "weather": [ { "date": "2012-04-23", "precipMM": "6.3", "tempMaxC": "9", "tempMaxF": "48", "tempMinC": "4", "tempMinF": "38", "weatherCode": "266", "weatherDesc": [ { "value": "Light drizzle" } ], "weatherIconUrl": [ { "value": "http://www.worldweatheronline.com/images/wsymbols01_png_64/wsymbol_0017_cloudy_with_light_rain.png" } ], "winddir16Point": "ESE", "winddirDegree": "109", "winddirection": "ESE", "windspeedKmph": "22", "windspeedMiles": "14" }, . . . ] } }
A response might contain required and optional keys. Required keys can be parsed using the getString method, which will throw JSONException if a required key cannot be found. TheOptString function comes handy when a certain key is optional in the response. It won't interrupt the parsing with JSONException but returns a default value instead. The default value can be set through a function parameter. If the default value is not specified, the function will return an empty string. The functions below parse a forecast response and turn it into a collection of Weather objects. For further information about org.json, take a look at the Javadoc.
private Vector forecasts = new Vector(); public void parse(String response) throws ParseError { try { JSONObject obj = new JSONObject(response); if (obj.isNull("data")) { return; } JSONObject data = obj.getJSONObject("data"); JSONArray currentCondition = data.getJSONArray("current_condition"); forecasts.addElement(parseWeather(currentCondition.getJSONObject(0))); JSONArray upcomingConditions = data.getJSONArray("weather"); int length = data.length(); for (int i = 0; i < length; ++i) { forecasts.addElement(parseWeather(upcomingConditions.getJSONObject(i))); } } catch (Exception e) { throw new ParseError(e.getMessage()); } } private Weather parseWeather(JSONObject weather) throws JSONException { Weather w = new Weather(); w.humidity = weather.optString("humidity"); w.temperatureC = weather.optString("temp_C"); w.temperatureF = weather.optString("temp_F"); w.minTempC = weather.optString("tempMinC"); w.minTempF = weather.optString("tempMinF"); w.maxTempC = weather.optString("tempMaxC"); w.maxTempF = weather.optString("tempMaxF"); w.windDirectionDegrees = weather.getString("winddirDegree"); w.windDirectionPoints = weather.getString("winddir16Point"); w.windSpeedKmph = weather.getString("windspeedKmph"); w.windSpeedMph = weather.getString("windspeedMiles"); JSONArray description = weather.getJSONArray("weatherDesc"); w.description = description.getJSONObject(0).getString("value"); JSONArray iconUrl = weather.getJSONArray("weatherIconUrl"); w.iconUrl = iconUrl.getJSONObject(0).getString("value"); return w; }
Recent locations
All locations used by the application are stored in the Recent locations view. Locations are saved in the RecordStore, so that they persist for the next time user starts the WeatherApp. The code snippet below shows how to do this. Note that on Symbian the elements are stored in opposite order compared to Series 40.
public static void save() { try { RecordStore.deleteRecordStore("locations"); // Clear data } catch(Exception e) { /* Nothing to delete */ } try { RecordStore rs = RecordStore.openRecordStore("locations", true); int count = recentLocations.size(); for (int i = 0; i < count; i++) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); DataOutputStream dos = new DataOutputStream(baos); Location lctn = (Location) recentLocations.elementAt(i); // Write city and country strings to record store dos.writeUTF(lctn.city); dos.writeUTF(lctn.country); byte[] b = baos.toByteArray(); rs.addRecord(b, 0, b.length); } rs.closeRecordStore(); } catch (RecordStoreException rse) {/* Fail */ } catch (IOException ioe) { /* Fail */ } } public static void load() { try { boolean symbian = System.getProperty("microedition.platform").indexOf("S60") > -1; RecordStore rs = RecordStore.openRecordStore("locations", true); RecordEnumeration re = rs.enumerateRecords(null, null, true); while(re.hasNextElement()) { int id = re.nextRecordId(); ByteArrayInputStream bais = new ByteArrayInputStream(rs.getRecord(id)); DataInputStream dis = new DataInputStream(bais); try { // Read city and country strings from record store Location location = new Location(); location.city = dis.readUTF(); location.country = dis.readUTF(); if(symbian) { // On Symbian the elements have been stored in opposite order compared to Series 40 recentLocations.addElement(location); } else { recentLocations.insertElementAt(location, 0); } } catch (EOFException eofe) { System.out.println(eofe); eofe.printStackTrace(); } } rs.closeRecordStore(); } catch (RecordStoreException rse) { /* Fail */ } catch (IOException ioe) { /* Fail */ } }
Manual location search
The manual location search is used for finding cities, if there is no automatic way for retrieving the location or the user just wants to know the weather forecast for another city. Search works asynchronously and sends automatically an http search request when the input in the text field changes.
public void itemStateChanged(Item item) { if (item instanceof TextField && !searchField.getString().equals(lastSearch)) { searchLocations(); } }
To limit the amount of searches, there is a mechanism which delays the search by one second (1000 milliseconds). The timer is reset every time the user types something. This gives the user a “search as you type” kind of functionality. Limiting the searches is sometimes called throttling and the current implementation looks like this:
public void throttleSearch() { // Cancel previous Timer... if(throttle != null) throttle.cancel(); // ...and start a new one throttle = new Timer(); throttle.schedule(new TimerTask() { public void run() { searchLocations(); cancel(); } }, 1000); }
The search view consists of a Form, a TextField, and some customItems. CustomItems are used because there is no way to append a regular list to a Form other than a ChoiceGroup, which can only contain checkboxes and radio buttons.
Night and day modes
The application has different visual styles for Night and Day modes. The mode is selected according to the weather icon url in the http response, so in the end the WorldWeatherOnline decides when day time ends and the night begins.
Conclusion
Parsing JSON is pretty straightforward especially because there are good libraries at org.json.me that can be used for the task.
Getting the location is a bit more complicated. Users tend to get irritated easily because of extra prompts and long waiting times. That is why it is important to evaluate the best method for each use case. The cell ID method is fast, but quite inaccurate, whereas GPS can be much more accurate, but may have a long delay. Also the flow should be designed so that the application can flexibly use a different positioning method if the preferred one is unavailable on a certain device. Also the amount of prompts should be kept to a minimum. Asking the same thing twice is already one time too many. This applies both to the network access prompt and the positioning prompt.
CustomItems offer a way to deal with things that are not possible to implement with items provided by lcdui. However, there seems to be quite a bit of variation between different platforms and platform versions. For example, retaining the platform look on custom items is not always that easy.
Source code
The example has been created with NetBeans 6.9.1 and Nokia SDK 1.1 for Java. The project can be easily opened in NetBeans by selecting 'Open Project' from the File menu and selecting the application.
Before opening the project, make sure the Nokia SDK 1.0 for Java or newer is installed and added to NetBeans. Build the project by selecting 'Build main project'.
You can install the application on a phone by transferring the JAR file with the Nokia Suite or over Bluetooth.
Known issues
- When mist, fog, or black clouds are forecast, the visual mode will be always in Day mode.
- On some devices network access is set to “ask always” by default. This can be changed on Series 40 by scrolling down to the MIDlet in question and selecting "Options"-> "Application access" to view and change the API access settings. On Symbian this can be done via Application Manager in similar way.
- In search view existing list items do not change size while changing orientation. This is due to a Symbian bug, which would turn them into white boxes.
- In Belle, softkey labeling is not possible. In a list view, the right softkey can be either an 'ok' tick mark or and options menu icon. For example, to prevent the Add command from turning into a tick mark in locations view, the command type has been set to EXIT with lower priority than the existing back button. This way the command appears under options menu.
Release downloads
Attachments
-
weather_view.png
(40.7 KB) -
added by Jarrrgh 5 months ago.
-
search_view.png
(49.5 KB) -
added by Jarrrgh 5 months ago.
-
weather_view2.png
(44.7 KB) -
added by Jarrrgh 3 months ago.
-
weather_view3.png
(45.5 KB) -
added by Jarrrgh 8 weeks ago.



