--- /dev/null
+package uk.me.njae.sunshine;
+
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+import uk.me.njae.sunshine.data.WeatherContract.LocationEntry;
+import uk.me.njae.sunshine.data.WeatherContract.WeatherEntry;
+import uk.me.njae.sunshine.data.WeatherDbHelper;
+
+public class TestDb extends AndroidTestCase {
+
+ public static final String LOG_TAG = TestDb.class.getSimpleName();
+
+ public void testCreateDb() throws Throwable {
+ mContext.deleteDatabase(WeatherDbHelper.DATABASE_NAME);
+ SQLiteDatabase db = new WeatherDbHelper(
+ this.mContext).getWritableDatabase();
+ assertEquals(true, db.isOpen());
+ db.close();
+ }
+
+ public void testInsertReadDb() {
+
+ // Test data we're going to insert into the DB to see if it works.
+ String testLocationSetting = "99705";
+ String testCityName = "North Pole";
+ double testLatitude = 64.7488;
+ double testLongitude = -147.353;
+
+ // If there's an error in those massive SQL table creation Strings,
+ // errors will be thrown here when you try to get a writable database.
+ WeatherDbHelper dbHelper = new WeatherDbHelper(mContext);
+ SQLiteDatabase db = dbHelper.getWritableDatabase();
+
+ // Create a new map of values, where column names are the keys
+ ContentValues values = new ContentValues();
+ values.put(LocationEntry.COLUMN_LOCATION_SETTING, testLocationSetting);
+ values.put(LocationEntry.COLUMN_CITY_NAME, testCityName);
+ values.put(LocationEntry.COLUMN_COORD_LAT, testLatitude);
+ values.put(LocationEntry.COLUMN_COORD_LONG, testLongitude);
+
+ long locationRowId;
+ locationRowId = db.insert(LocationEntry.TABLE_NAME, null, values);
+
+ // Verify we got a row back.
+ assertTrue(locationRowId != -1);
+ Log.d(LOG_TAG, "New row id: " + locationRowId);
+
+ // Data's inserted. IN THEORY. Now pull some out to stare at it and verify it made
+ // the round trip.
+
+ // Specify which columns you want.
+ String[] columns = {
+ LocationEntry._ID,
+ LocationEntry.COLUMN_LOCATION_SETTING,
+ LocationEntry.COLUMN_CITY_NAME,
+ LocationEntry.COLUMN_COORD_LAT,
+ LocationEntry.COLUMN_COORD_LONG
+ };
+
+ // A cursor is your primary interface to the query results.
+ Cursor cursor = db.query(
+ LocationEntry.TABLE_NAME, // Table to Query
+ columns,
+ null, // Columns for the "where" clause
+ null, // Values for the "where" clause
+ null, // columns to group by
+ null, // columns to filter by row groups
+ null // sort order
+ );
+
+ // If possible, move to the first row of the query results.
+ if (cursor.moveToFirst()) {
+ // Get the value in each column by finding the appropriate column index.
+ int locationIndex = cursor.getColumnIndex(LocationEntry.COLUMN_LOCATION_SETTING);
+ String location = cursor.getString(locationIndex);
+
+ int nameIndex = cursor.getColumnIndex((LocationEntry.COLUMN_CITY_NAME));
+ String name = cursor.getString(nameIndex);
+
+ int latIndex = cursor.getColumnIndex((LocationEntry.COLUMN_COORD_LAT));
+ double latitude = cursor.getDouble(latIndex);
+
+ int longIndex = cursor.getColumnIndex((LocationEntry.COLUMN_COORD_LONG));
+ double longitude = cursor.getDouble(longIndex);
+
+ // Hooray, data was returned! Assert that it's the right data, and that the database
+ // creation code is working as intended.
+ // Then take a break. We both know that wasn't easy.
+ assertEquals(testCityName, name);
+ assertEquals(testLocationSetting, location);
+ assertEquals(testLatitude, latitude);
+ assertEquals(testLongitude, longitude);
+
+ // Fantastic. Now that we have a location, add some weather!
+ } else {
+ // That's weird, it works on MY machine...
+ fail("No values returned :(");
+ }
+
+ // Fantastic. Now that we have a location, add some weather!
+ ContentValues weatherValues = new ContentValues();
+ weatherValues.put(WeatherEntry.COLUMN_LOC_KEY, locationRowId);
+ weatherValues.put(WeatherEntry.COLUMN_DATETEXT, "20141205");
+ weatherValues.put(WeatherEntry.COLUMN_DEGREES, 1.1);
+ weatherValues.put(WeatherEntry.COLUMN_HUMIDITY, 1.2);
+ weatherValues.put(WeatherEntry.COLUMN_PRESSURE, 1.3);
+ weatherValues.put(WeatherEntry.COLUMN_MAX_TEMP, 75);
+ weatherValues.put(WeatherEntry.COLUMN_MIN_TEMP, 65);
+ weatherValues.put(WeatherEntry.COLUMN_SHORT_DESC, "Asteroids");
+ weatherValues.put(WeatherEntry.COLUMN_WIND_SPEED, 5.5);
+ weatherValues.put(WeatherEntry.COLUMN_WEATHER_ID, 321);
+
+ long weatherRowId = db.insert(WeatherEntry.TABLE_NAME, null, weatherValues);
+ assertTrue(weatherRowId != -1);
+
+ // A cursor is your primary interface to the query results.
+ Cursor weatherCursor = db.query(
+ WeatherEntry.TABLE_NAME, // Table to Query
+ null, // leaving "columns" null just returns all the columns.
+ null, // cols for "where" clause
+ null, // values for "where" clause
+ null, // columns to group by
+ null, // columns to filter by row groups
+ null // sort order
+ );
+
+ if (!weatherCursor.moveToFirst()) {
+ fail("No weather data returned!");
+ }
+
+ assertEquals(weatherCursor.getInt(
+ weatherCursor.getColumnIndex(WeatherEntry.COLUMN_LOC_KEY)), locationRowId);
+ assertEquals(weatherCursor.getString(
+ weatherCursor.getColumnIndex(WeatherEntry.COLUMN_DATETEXT)), "20141205");
+ assertEquals(weatherCursor.getDouble(
+ weatherCursor.getColumnIndex(WeatherEntry.COLUMN_DEGREES)), 1.1);
+ assertEquals(weatherCursor.getDouble(
+ weatherCursor.getColumnIndex(WeatherEntry.COLUMN_HUMIDITY)), 1.2);
+ assertEquals(weatherCursor.getDouble(
+ weatherCursor.getColumnIndex(WeatherEntry.COLUMN_PRESSURE)), 1.3);
+ assertEquals(weatherCursor.getInt(
+ weatherCursor.getColumnIndex(WeatherEntry.COLUMN_MAX_TEMP)), 75);
+ assertEquals(weatherCursor.getInt(
+ weatherCursor.getColumnIndex(WeatherEntry.COLUMN_MIN_TEMP)), 65);
+ assertEquals(weatherCursor.getString(
+ weatherCursor.getColumnIndex(WeatherEntry.COLUMN_SHORT_DESC)), "Asteroids");
+ assertEquals(weatherCursor.getDouble(
+ weatherCursor.getColumnIndex(WeatherEntry.COLUMN_WIND_SPEED)), 5.5);
+ assertEquals(weatherCursor.getInt(
+ weatherCursor.getColumnIndex(WeatherEntry.COLUMN_WEATHER_ID)), 321);
+
+ weatherCursor.close();
+ dbHelper.close();
+ }
+}
--- /dev/null
+package uk.me.njae.sunshine.data;
+
+import android.provider.BaseColumns;
+
+/**
+ * Created by neil on 09/11/14.
+ */
+public class WeatherContract {
+ /* Inner class that defines the table contents of the location table */
+ public static final class LocationEntry implements BaseColumns {
+
+ // Table name
+ public static final String TABLE_NAME = "location";
+
+ // The location setting string is what will be sent to openweathermap
+ // as the location query.
+ public static final String COLUMN_LOCATION_SETTING = "location_setting";
+
+ // Human readable location string, provided by the API. Because for styling,
+ // "Mountain View" is more recognizable than 94043.
+ public static final String COLUMN_CITY_NAME = "city_name";
+
+ // In order to uniquely pinpoint the location on the map when we launch the
+ // map intent, we store the latitude and longitude as returned by openweathermap.
+ public static final String COLUMN_COORD_LAT = "coord_lat";
+ public static final String COLUMN_COORD_LONG = "coord_long";
+ }
+
+ /* Inner class that defines the table contents of the weather table */
+ public static final class WeatherEntry implements BaseColumns {
+
+ public static final String TABLE_NAME = "weather";
+
+ // Column with the foreign key into the location table.
+ public static final String COLUMN_LOC_KEY = "location_id";
+ // Date, stored as Text with format yyyy-MM-dd
+ public static final String COLUMN_DATETEXT = "date";
+ // Weather id as returned by API, to identify the icon to be used
+ public static final String COLUMN_WEATHER_ID = "weather_id";
+
+ // Short description and long description of the weather, as provided by API.
+ // e.g "clear" vs "sky is clear".
+ public static final String COLUMN_SHORT_DESC = "short_desc";
+
+ // Min and max temperatures for the day (stored as floats)
+ public static final String COLUMN_MIN_TEMP = "min";
+ public static final String COLUMN_MAX_TEMP = "max";
+
+ // Humidity is stored as a float representing percentage
+ public static final String COLUMN_HUMIDITY = "humidity";
+
+ // Humidity is stored as a float representing percentage
+ public static final String COLUMN_PRESSURE = "pressure";
+
+ // Windspeed is stored as a float representing windspeed mph
+ public static final String COLUMN_WIND_SPEED = "wind";
+
+ // Degrees are meteorological degrees (e.g, 0 is north, 180 is south). Stored as floats.
+ public static final String COLUMN_DEGREES = "degrees";
+ }
+}
--- /dev/null
+package uk.me.njae.sunshine.data;
+
+import android.content.Context;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+
+import uk.me.njae.sunshine.data.WeatherContract.LocationEntry;
+import uk.me.njae.sunshine.data.WeatherContract.WeatherEntry;
+
+
+/**
+ * Manages a local database for weather data.
+ */
+public class WeatherDbHelper extends SQLiteOpenHelper {
+
+ // If you change the database schema, you must increment the database version.
+ private static final int DATABASE_VERSION = 1;
+
+ public static final String DATABASE_NAME = "weather.db";
+
+ public WeatherDbHelper(Context context) {
+ super(context, DATABASE_NAME, null, DATABASE_VERSION);
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase sqLiteDatabase) {
+ // Create a table to hold locations. A location consists of the string supplied in the
+ // location setting, the city name, and the latitude and longitude
+ final String SQL_CREATE_LOCATION_TABLE = "CREATE TABLE " + LocationEntry.TABLE_NAME + " (" +
+ LocationEntry._ID + " INTEGER PRIMARY KEY," +
+ LocationEntry.COLUMN_LOCATION_SETTING + " TEXT UNIQUE NOT NULL, " +
+ LocationEntry.COLUMN_CITY_NAME + " TEXT NOT NULL, " +
+ LocationEntry.COLUMN_COORD_LAT + " REAL NOT NULL, " +
+ LocationEntry.COLUMN_COORD_LONG + " REAL NOT NULL, " +
+ "UNIQUE (" + LocationEntry.COLUMN_LOCATION_SETTING +") ON CONFLICT IGNORE"+
+ " );";
+
+ final String SQL_CREATE_WEATHER_TABLE = "CREATE TABLE " + WeatherEntry.TABLE_NAME + " (" +
+ // Why AutoIncrement here, and not above?
+ // Unique keys will be auto-generated in either case. But for weather
+ // forecasting, it's reasonable to assume the user will want information
+ // for a certain date and all dates *following*, so the forecast data
+ // should be sorted accordingly.
+ WeatherEntry._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
+
+ // the ID of the location entry associated with this weather data
+ WeatherEntry.COLUMN_LOC_KEY + " INTEGER NOT NULL, " +
+ WeatherEntry.COLUMN_DATETEXT + " TEXT NOT NULL, " +
+ WeatherEntry.COLUMN_SHORT_DESC + " TEXT NOT NULL, " +
+ WeatherEntry.COLUMN_WEATHER_ID + " INTEGER NOT NULL," +
+
+ WeatherEntry.COLUMN_MIN_TEMP + " REAL NOT NULL, " +
+ WeatherEntry.COLUMN_MAX_TEMP + " REAL NOT NULL, " +
+
+ WeatherEntry.COLUMN_HUMIDITY + " REAL NOT NULL, " +
+ WeatherEntry.COLUMN_PRESSURE + " REAL NOT NULL, " +
+ WeatherEntry.COLUMN_WIND_SPEED + " REAL NOT NULL, " +
+ WeatherEntry.COLUMN_DEGREES + " REAL NOT NULL, " +
+
+ // Set up the location column as a foreign key to location table.
+ " FOREIGN KEY (" + WeatherEntry.COLUMN_LOC_KEY + ") REFERENCES " +
+ LocationEntry.TABLE_NAME + " (" + LocationEntry._ID + "), " +
+
+ // To assure the application have just one weather entry per day
+ // per location, it's created a UNIQUE constraint with REPLACE strategy
+ " UNIQUE (" + WeatherEntry.COLUMN_DATETEXT + ", " +
+ WeatherEntry.COLUMN_LOC_KEY + ") ON CONFLICT REPLACE);";
+
+ sqLiteDatabase.execSQL(SQL_CREATE_LOCATION_TABLE);
+ sqLiteDatabase.execSQL(SQL_CREATE_WEATHER_TABLE);
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase sqLiteDatabase, int oldVersion, int newVersion) {
+ sqLiteDatabase.execSQL("DROP TABLE IF EXISTS " + LocationEntry.TABLE_NAME);
+ sqLiteDatabase.execSQL("DROP TABLE IF EXISTS " + WeatherEntry.TABLE_NAME);
+ onCreate(sqLiteDatabase);
+ }
+}