1 package uk
.me
.njae
.sunshine
;
3 import android
.content
.Intent
;
4 import android
.content
.SharedPreferences
;
5 import android
.net
.Uri
;
6 import android
.os
.AsyncTask
;
7 import android
.os
.Bundle
;
8 import android
.preference
.PreferenceManager
;
9 import android
.support
.v4
.app
.Fragment
;
10 import android
.util
.Log
;
11 import android
.view
.LayoutInflater
;
12 import android
.view
.Menu
;
13 import android
.view
.MenuInflater
;
14 import android
.view
.MenuItem
;
15 import android
.view
.View
;
16 import android
.view
.ViewGroup
;
17 import android
.widget
.AdapterView
;
18 import android
.widget
.ArrayAdapter
;
19 import android
.widget
.ListView
;
20 import android
.widget
.Toast
;
22 import org
.json
.JSONArray
;
23 import org
.json
.JSONException
;
24 import org
.json
.JSONObject
;
26 import java
.io
.BufferedReader
;
27 import java
.io
.IOException
;
28 import java
.io
.InputStream
;
29 import java
.io
.InputStreamReader
;
30 import java
.io
.UnsupportedEncodingException
;
31 import java
.net
.HttpURLConnection
;
33 import java
.net
.URLEncoder
;
34 import java
.text
.SimpleDateFormat
;
35 import java
.util
.ArrayList
;
36 import java
.util
.Arrays
;
37 import java
.util
.Date
;
38 import java
.util
.List
;
41 * A placeholder fragment containing a simple view.
43 public class ForecastFragment
extends Fragment
{
45 private final String LOG_TAG
= ForecastFragment
.class.getSimpleName();
47 private ArrayAdapter
<String
> mForecastAdapter
;
49 public ForecastFragment() {
53 public void onCreate(Bundle savedInstanceState
) {
54 super.onCreate(savedInstanceState
);
55 setHasOptionsMenu(true);
59 public void onCreateOptionsMenu(Menu menu
, MenuInflater inflater
) {
60 // Inflate the menu; this adds items to the action bar if it is present.
61 inflater
.inflate(R
.menu
.forecastfragment
, menu
);
65 public boolean onOptionsItemSelected(MenuItem item
) {
66 // Handle action bar item clicks here. The action bar will
67 // automatically handle clicks on the Home/Up button, so long
68 // as you specify a parent activity in AndroidManifest.xml.
69 int id
= item
.getItemId();
70 if (id
== R
.id
.action_refresh
) {
74 if (id
== R
.id
.action_show_location
) {
75 SharedPreferences preferences
= PreferenceManager
.getDefaultSharedPreferences(getActivity());
76 String location
= preferences
.getString(getString(R
.string
.pref_location_key
),
77 getString(R
.string
.pref_location_default
));
78 Intent intent
= new Intent(Intent
.ACTION_VIEW
);
81 geoLocation
= Uri
.parse("geo:0,0?q=" + URLEncoder
.encode(location
, "UTF-8"));
82 intent
.setData(geoLocation
);
83 } catch (UnsupportedEncodingException e
) {
84 Log
.e(LOG_TAG
, "Error ", e
);
86 if (intent
.resolveActivity(getActivity().getPackageManager()) != null) {
87 startActivity(intent
);
91 return super.onOptionsItemSelected(item
);
94 private void updateWeather() {
95 FetchWeatherTask weatherTask
= new FetchWeatherTask();
96 SharedPreferences preferences
= PreferenceManager
.getDefaultSharedPreferences(getActivity());
97 String location
= preferences
.getString(getString(R
.string
.pref_location_key
),
98 getString(R
.string
.pref_location_default
));
99 // weatherTask.execute("2642465");
100 weatherTask
.execute(location
);
104 public View
onCreateView(LayoutInflater inflater
, ViewGroup container
,
105 Bundle savedInstanceState
) {
107 // String[] forecastArray = {
108 // "Today - Sunny - 10/10",
109 // "Tomorrow - Cloudy - 11/11",
110 // "Tuesday - Snow - 12/12",
111 // "Wednesday - Rain - 13/13",
112 // "Thursday - Hail - 14/14",
113 // "Friday - Scorchio - 15/15",
114 // "Saturday - Fog - 16/16"
117 // List<String> weekForecast = new ArrayList<String>(
118 // Arrays.asList(forecastArray));
120 mForecastAdapter
= new ArrayAdapter
<String
>(
122 R
.layout
.list_item_forecast
,
123 R
.id
.list_item_forecast_textview
,
124 new ArrayList
<String
>() // weekForecast
127 View rootView
= inflater
.inflate(R
.layout
.fragment_main
, container
, false);
129 ListView listView
= (ListView
) rootView
.findViewById(R
.id
.listview_forecast
);
130 listView
.setAdapter(mForecastAdapter
);
131 listView
.setOnItemClickListener(new AdapterView
.OnItemClickListener() {
133 public void onItemClick(AdapterView
<?
> adapterView
, View view
, int position
, long l
) {
134 String forecast
= mForecastAdapter
.getItem(position
);
135 Intent intent
= new Intent(getActivity(), DetailActivity
.class)
136 .putExtra(Intent
.EXTRA_TEXT
, forecast
);
137 startActivity(intent
);
144 public void onStart() {
149 public class FetchWeatherTask
extends AsyncTask
<String
, Void
, String
[]> {
151 private final String LOG_TAG
= FetchWeatherTask
.class.getSimpleName();
154 protected void onPostExecute(String
[] result
) {
155 if (result
!= null) {
156 mForecastAdapter
.clear();
157 // for (String dayForecastStr: result) {
158 // mForecastAdapter.add(dayForecastStr);
160 mForecastAdapter
.addAll(result
);
165 protected String
[] doInBackground(String
... params
) {
166 if (params
.length
== 0) {
170 // These two need to be declared outside the try/catch
171 // so that they can be closed in the finally block.
172 HttpURLConnection urlConnection
= null;
173 BufferedReader reader
= null;
175 // Will contain the raw JSON response as a string.
176 String forecastJsonStr
;
178 String format
= "json";
179 String units
= "metric";
183 // Construct the URL for the OpenWeatherMap query
184 // Possible parameters are avaiable at OWM's forecast API page, at
185 // http://openweathermap.org/API#forecast
186 final String FORECAST_BASE_URL
= "http://api.openweathermap.org/data/2.5/forecast/daily?";
187 // final String QUERY_PARAM = "id"; // use this if using a location ID
188 final String QUERY_PARAM
= "q";
189 final String FORMAT_PARAM
= "mode";
190 final String UNITS_PARAM
= "units";
191 final String DAYS_PARAM
= "cnt";
193 Uri builtUri
= Uri
.parse(FORECAST_BASE_URL
).buildUpon()
194 .appendQueryParameter(QUERY_PARAM
, params
[0])
195 .appendQueryParameter(FORMAT_PARAM
, format
)
196 .appendQueryParameter(UNITS_PARAM
, units
)
197 .appendQueryParameter(DAYS_PARAM
, Integer
.toString(numDays
))
200 URL url
= new URL(builtUri
.toString());
201 /**/ Log
.v(LOG_TAG
, "Built URI " + builtUri
.toString());
203 // Create the request to OpenWeatherMap, and open the connection
204 urlConnection
= (HttpURLConnection
) url
.openConnection();
205 urlConnection
.setRequestMethod("GET");
206 urlConnection
.connect();
208 // Read the input stream into a String
209 InputStream inputStream
= urlConnection
.getInputStream();
210 StringBuffer buffer
= new StringBuffer();
211 if (inputStream
== null) {
215 reader
= new BufferedReader(new InputStreamReader(inputStream
));
218 while ((line
= reader
.readLine()) != null) {
219 // Since it's JSON, adding a newline isn't necessary (it won't affect parsing)
220 // But it does make debugging a *lot* easier if you print out the completed
221 // buffer for debugging.
222 buffer
.append(line
+ "\n");
225 if (buffer
.length() == 0) {
226 // Stream was empty. No point in parsing.
229 forecastJsonStr
= buffer
.toString();
230 // Log.v(LOG_TAG, "Forecast JSON string: " + forecastJsonStr);
232 return getWeatherDataFromJson(forecastJsonStr
, numDays
);
233 } catch (JSONException e
) {
234 Log
.e(LOG_TAG
, e
.getMessage(), e
);
238 // This will only happen if we fail to read the forecast
241 } catch (IOException e
) {
242 Log
.e(LOG_TAG
, "Error ", e
);
243 // If the code didn't successfully get the weather data, there's no point in attempting
245 forecastJsonStr
= null;
247 if (urlConnection
!= null) {
248 urlConnection
.disconnect();
250 if (reader
!= null) {
253 } catch (final IOException e
) {
254 Log
.e(LOG_TAG
, "Error closing stream", e
);
261 /* The date/time conversion code is going to be moved outside the asynctask later,
262 * so for convenience we're breaking it out into its own method now.
264 private String
getReadableDateString(long time
) {
265 // Because the API returns a unix timestamp (measured in seconds),
266 // it must be converted to milliseconds in order to be converted to valid date.
267 Date date
= new Date(time
* 1000);
268 SimpleDateFormat format
= new SimpleDateFormat("E, MMM d");
269 return format
.format(date
).toString();
273 * Prepare the weather high/lows for presentation.
275 private String
formatHighLows(double high
, double low
) {
276 SharedPreferences preferences
= PreferenceManager
.getDefaultSharedPreferences(getActivity());
277 String units
= preferences
.getString(getString(R
.string
.pref_units_key
),
278 getString(R
.string
.pref_units_default
));
279 // For presentation, assume the user doesn't care about tenths of a degree.
280 long roundedHigh
= Math
.round(high
);
281 long roundedLow
= Math
.round(low
);
283 if (units
.equals(getString(R
.string
.pref_units_imperial
))) {
284 roundedHigh
= Math
.round(high
* 9 / 5 + 32);
285 roundedLow
= Math
.round(low
* 9 / 5 + 32);
288 String highLowStr
= roundedHigh
+ "/" + roundedLow
;
293 * Take the String representing the complete forecast in JSON Format and
294 * pull out the data we need to construct the Strings needed for the wireframes.
296 * Fortunately parsing is easy: constructor takes the JSON string and converts it
297 * into an Object hierarchy for us.
299 private String
[] getWeatherDataFromJson(String forecastJsonStr
, int numDays
)
300 throws JSONException
{
302 // These are the names of the JSON objects that need to be extracted.
303 final String OWM_LIST
= "list";
304 final String OWM_WEATHER
= "weather";
305 final String OWM_TEMPERATURE
= "temp";
306 final String OWM_MAX
= "max";
307 final String OWM_MIN
= "min";
308 final String OWM_DATETIME
= "dt";
309 final String OWM_DESCRIPTION
= "main";
311 JSONObject forecastJson
= new JSONObject(forecastJsonStr
);
312 JSONArray weatherArray
= forecastJson
.getJSONArray(OWM_LIST
);
314 String
[] resultStrs
= new String
[numDays
];
315 for (int i
= 0; i
< weatherArray
.length(); i
++) {
316 // For now, using the format "Day, description, hi/low"
321 // Get the JSON object representing the day
322 JSONObject dayForecast
= weatherArray
.getJSONObject(i
);
324 // The date/time is returned as a long. We need to convert that
325 // into something human-readable, since most people won't read "1400356800" as
327 long dateTime
= dayForecast
.getLong(OWM_DATETIME
);
328 day
= getReadableDateString(dateTime
);
330 // description is in a child array called "weather", which is 1 element long.
331 JSONObject weatherObject
= dayForecast
.getJSONArray(OWM_WEATHER
).getJSONObject(0);
332 description
= weatherObject
.getString(OWM_DESCRIPTION
);
334 // Temperatures are in a child object called "temp". Try not to name variables
335 // "temp" when working with temperature. It confuses everybody.
336 JSONObject temperatureObject
= dayForecast
.getJSONObject(OWM_TEMPERATURE
);
337 double high
= temperatureObject
.getDouble(OWM_MAX
);
338 double low
= temperatureObject
.getDouble(OWM_MIN
);
340 highAndLow
= formatHighLows(high
, low
);
341 resultStrs
[i
] = day
+ " - " + description
+ " - " + highAndLow
;
344 // for (String s: resultStrs) {
345 // Log.v(LOG_TAG, "Forecast entry: " + s);