Weather App

This Weather app is a simple single page app that uses information from an API at Weather Underground. Weather Underground provides a free level that gives a small amount of calls. Currently they’ll provide 500 API calls per day. For testing purposes this should be enough. If you go over that number, the calls will be denied until the next day.

Because of how quickly Ionic is updating it’s CLI (Command Line Interface) and templates. You may find that when a component is generated edits that I mention may already be done. For example, when the WeatherUnderground provider is added, older versions of the CLI won’t add the provider to the Providers list in app.modules.ts, newer versions may add them. It’s always best to check until you feel more comfortable.

Something that tripped me up while writing this was capitalization. I would sometimes write WeatherUnderground and other times WeatherUnderGround. The computer doesn’t really care which is used. The problem was that I’d mix them up and Ionic couldn’t find the correct piece. As a hint, when you get a message that it can’t find something, check the capitalization. It’s a quick and easy check and might save you hours of back tracing the code.

Create the Blank Template

npm -g install ionic@latest

Make sure the latest Ionic is installed. This is a good idea anytime you start a new project. An option is instead of using latest, a version number can be put in, if a certain version number has been agreed upon by the team.

Ionic start WeatherApp blank

Create the blank WeatherApp. We won’t be adding second pages, so now menus need to be added and the blank template will be fine.

Besides creating the all the background code, or as some call it, the plumbing. The HomePage is created. The HomePage is a Page component. It contains three files. A HTML file for the layout. The SCSS (Sass) file for the styling. And the TS (TypeScript) file for the action.

cd WeatherApp

This is one of those should be obvious but I’ve read in more than a few places that this causes a lot of grief. So don’t forget to go into the directory. Personally I’ve missed this a few times myself and received confusing errors. A good sign is when Ionic says something about not being an Ionic project.

Add the platforms

ionic cordova platform add ios

This is not strictly required until you are planning to deploy the app to a device or emulator. Replace ios for windows or android to add the platform of your choice.

Add plugins

Plugins are ways of adding extra power to your app.

Add the Whitelist plugin at the CLI

ionic cordova plugin add cordova-plugin-whitelist --save

This is a standard plugin that’ll be used in most applications. It will give you access to resources.

Edit src/index.html to include

<meta http-equiv="Content-Security-Policy" content="font-src 'self' data:; img-src * data:; default-src gap://ready file://* *; script-src 'self' 'unsafe-inline' 'unsafe-eval' * ; style-src 'self' 'unsafe-inline' *">

Add the Crosswalk Plugin at the CLI

ionic cordova plugin add cordova-plugin-crosswalk-webview --saveionic

This plugin is optional. It will bundle a modern browser into your app. This is useful for older versions of Android that may not have an updated browser. The app will be larger and for users of updated Android, they won’t see any difference.

Add the WK Webview engine plugin at the CLI

ionic cordova plugin add https://github.com/driftyco/cordova-plugin-wkwebview-engine.git --save

Similar to the Crosswalk plugin this will load an updated browser for older versions of iOS.

Modify config.xml to include the following before the </widget> tag

<allow-navigation href="http://localhost:8080/*"/>
<feature name="CDVWKWebViewEngine">
    <param name="ios-package" value="CDVWKWebViewEngine" />
</feature>

<preference name"CordovaWebViewEngine" value"CDVWKWWebViewEngine" />

Add a Data provider

Create a provider to use to access the WeatherUnderGround API. A provider is a component that is just the TS file. It’s use is to provide access to data or service. It is often referred to as a service.

ionic g provider WeatherUnderGround

Modify src/app/app.module.ts

Need to import the HTTP Module and the provider we created. As mentioned earlier, don’t be surprised if one or both of these lines has already be put into place during the generate command earlier.

import { HttpModule } from '@angular/http';
import { WeatherUnderGroundProvider } from '../providers/weather-under-ground/weather-under-ground';

Edit the imports to contain the HttpModule.

imports: [
BrowserModule,
HttpModule,
IonicModule.forRoot(MyApp)
],

Edit the Providers section to include WeatherUnderground

Need to declare the WeatherUnderGroundProvider in the Providers section.

providers: [
StatusBar,
SplashScreen,
{provide: ErrorHandler, useClass: IonicErrorHandler},
WeatherUnderGroundProvider
]

Fetch data

When calling an API it’s best to use a Promise or Observable. The reason for this is that if the app just waits for the call to finish it’ll lock up the User Interface (UI) until it finishes. I’m not going to go into what a Promise or Observable are, that’s for another blog post or two.

Joshua Morony has a YouTube video on Observables. Joshua creates a lot of posts and videos about Ionic. He explains things very well.

The API call requires the API_KEY that you’ll get from Weather Underground. It’s best not to share the Key since it’s on your account that any calls will be put against. Weather Underground does allow for easy recreation. If you find your key has gone public, create a new key for your app and deactivate the other one.

Getting your own API-KEY starts with signing up at Weather Underground API.

http://api.wunderground.com/api/[API_KEY]/conditions/q/[STATE]]/[CITY].json

In this call we are using a city and state. We could easily change it to a ZIP code or GPS coordinates. I would add a second fetchData function that would take the location data in and make the call.

Edit the weather-under-ground.ts file

The provider will contain the steps to make the API call and pass back a Promise that the data would be filled in at a later time.

import { Injectable } from '@angular/core';
import { Http } from '@angular/http';
import 'rxjs/add/operator/map';

@Injectable()
export class WeatherUnderGroundProvider {
//
  city : string = "San_Francisco";
  API_KEY : string = "[API_KEY]";

  data: any= null;

  constructor(public http: Http) {
  }

  fetchData() {
    return this.load().then(data => {
      return data;  
    }); 
  }

  load() { 
    let url = 
      "http://api.wunderground.com/api/"
      +this.API_KEY
      +"/conditions/q/CA/"
      +this.city
      +".json";

    if (this.data) { 
      return Promise.resolve(this.data); 
    } 
    return new Promise(resolve => { 
      this.http.get(url).map(res => res.json()).subscribe(data => { 
        this.data = data; resolve(this.data); 
      }); 
    }); 
  }

}

Edit src/pages/home/home.ts constructor

For this app we’ll be putting inside the HomePage constructor a call to the fetchData function and a then to specify what to do when the promise is fulfilled. The call to fetchData does not need to be in the constructor. For example it could be in response to an input; such as a ZIP Code; from the user.

WeatherInfo :any;

constructor(public navCtrl: NavController,
            public WeatherService: WeatherUnderGroundProvider) {
    WeatherService.fetchData().then(    
    theResult => {
        this.WeatherInfo = theResult.current_observation;
    }
    )   
}

Import the provider

import { WeatherUnderGroundProvider } from '../../providers/weather-underground/weather-underground';

This is an example of the response that Weather Under Ground Provides

This is an example of the JSON we get back from a call to Weather Underground. Looking back at the constructor for HomePage, we’re not using the entire response. I’m using only the portion under current_observation. Granted it’s still the majority of the data, but it keeps us from writing current_observation every time we use the data.

{
  "response": {
    "version": "0.1",
    "termsofService": "http://www.wunderground.com/weather/api/d/terms.html",
    "features": {
      "conditions": 1
    }
  },
  "current_observation": {
      "image": {
        "url": "http://icons-ak.wxug.com/graphics/wu2/logo_130x80.png",
        "title": "Weather Underground",
        "link": "http://www.wunderground.com"
      },
    "display_location": {
      "full": "San Francisco, CA",
      "city": "San Francisco",
      "state": "CA",
      "state_name": "California",
      "country": "US",
      "country_iso3166": "US",
      "zip": "94101",
      "latitude": "37.77500916",
      "longitude": "-122.41825867",
      "elevation": "47.00000000"
    },
    "observation_location": {
      "full": "SOMA - Near Van Ness, San Francisco, California",
      "city": "SOMA - Near Van Ness, San Francisco",
      "state": "California",
      "country": "US",
      "country_iso3166": "US",
      "latitude": "37.773285",
      "longitude": "-122.417725",
      "elevation": "49 ft"
    },
    "estimated": {},
    "station_id": "KCASANFR58",
    "observation_time": "Last Updated on June 27, 5:27 PM PDT",
    "observation_time_rfc822": "Wed, 27 Jun 2012 17:27:13 -0700",
    "observation_epoch": "1340843233",
    "local_time_rfc822": "Wed, 27 Jun 2012 17:27:14 -0700",
    "local_epoch": "1340843234",
    "local_tz_short": "PDT",
    "local_tz_long": "America/Los_Angeles",
    "local_tz_offset": "-0700",
    "weather": "Partly Cloudy",
    "temperature_string": "66.3 F (19.1 C)",
    "temp_f": 66.3,
    "temp_c": 19.1,
    "relative_humidity": "65%",
    "wind_string": "From the NNW at 22.0 MPH Gusting to 28.0 MPH",
    "wind_dir": "NNW",
    "wind_degrees": 346,
    "wind_mph": 22.0,
    "wind_gust_mph": "28.0",
    "wind_kph": 35.4,
    "wind_gust_kph": "45.1",
    "pressure_mb": "1013",
    "pressure_in": "29.93",
    "pressure_trend": "+",
    "dewpoint_string": "54 F (12 C)",
    "dewpoint_f": 54,
    "dewpoint_c": 12,
    "heat_index_string": "NA",
    "heat_index_f": "NA",
    "heat_index_c": "NA",
    "windchill_string": "NA",
    "windchill_f": "NA",
    "windchill_c": "NA",
    "feelslike_string": "66.3 F (19.1 C)",
    "feelslike_f": "66.3",
    "feelslike_c": "19.1",
    "visibility_mi": "10.0",
    "visibility_km": "16.1",
    "solarradiation": "",
    "UV": "5",
    "precip_1hr_string": "0.00 in ( 0 mm)",
    "precip_1hr_in": "0.00",
    "precip_1hr_metric": " 0",
    "precip_today_string": "0.00 in (0 mm)",
    "precip_today_in": "0.00",
    "precip_today_metric": "0",
    "icon": "partlycloudy",
    "icon_url": "http://icons-ak.wxug.com/i/c/k/partlycloudy.gif",
    "forecast_url": "http://www.wunderground.com/US/CA/San_Francisco.html",
    "history_url": "http://www.wunderground.com/history/airport/KCASANFR58/2012/6/27/DailyHistory.html",
    "ob_url": "http://www.wunderground.com/cgi-bin/findweather/getForecast?query=37.773285,-122.417725"
    }
}

Display the data

Since is may take seconds or minutes (or if the service is down, or if the user is offline the promise will never be resolved), we can’t just use the data in the HTML. A check is needed, *ngIf="WeatherInfo" will only process and display that div (and whatever is in it) if the data is present. Once the promise is resolved, that div will be handled and in comes the data.

Please test your app with the internet off and look to see what the app looks like. Is it still usable? Does it look broken? Does the user know what’s happening? As users we now what it feels like to have an app hang or crash just because at that moment we don’t have internet. As developers we should work to make that a distant memory.

Edit src/pages/home/home.html

<ion-header>
  <ion-navbar>
    <ion-title>
      Weather App
    </ion-title>
  </ion-navbar>
</ion-header>

<ion-content padding>
  <div *ngIf="WeatherInfo">
    <h1>Here is the weather for </h1>
    <p>DewPoint: </p>
    <p>Temp (F): </p>
    <img src="" />
  </div>
</ion-content>

Onward

I’ll agree this is not the prettiest looking app. The two parts I’d like to improve is to add some styling and a little interactivity. A bare list of facts is not pleasant. Some nice formatting, color and background image that depends on the weather being shown would go a long way. Not everyone lives in San Francisco. It’d be nice to find out the weather here in my home town of Bloomington.