Map display not quite working
[Sunshine.git] / app / src / main / java / uk / me / njae / sunshine / ForecastFragment.java
1 package uk.me.njae.sunshine;
2
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;
21
22 import org.json.JSONArray;
23 import org.json.JSONException;
24 import org.json.JSONObject;
25
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;
32 import java.net.URL;
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;
39
40 /**
41 * A placeholder fragment containing a simple view.
42 */
43 public class ForecastFragment extends Fragment {
44
45 private final String LOG_TAG = ForecastFragment.class.getSimpleName();
46
47 private ArrayAdapter<String> mForecastAdapter;
48
49 public ForecastFragment() {
50 }
51
52 @Override
53 public void onCreate(Bundle savedInstanceState) {
54 super.onCreate(savedInstanceState);
55 setHasOptionsMenu(true);
56 }
57
58 @Override
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);
62 }
63
64 @Override
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) {
71 updateWeather();
72 return true;
73 }
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);
79 Uri geoLocation;
80 try {
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);
85 }
86 if (intent.resolveActivity(getActivity().getPackageManager()) != null) {
87 startActivity(intent);
88 }
89
90 }
91 return super.onOptionsItemSelected(item);
92 }
93
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);
101 }
102
103 @Override
104 public View onCreateView(LayoutInflater inflater, ViewGroup container,
105 Bundle savedInstanceState) {
106
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"
115 // };
116 //
117 // List<String> weekForecast = new ArrayList<String>(
118 // Arrays.asList(forecastArray));
119
120 mForecastAdapter = new ArrayAdapter<String>(
121 getActivity(),
122 R.layout.list_item_forecast,
123 R.id.list_item_forecast_textview,
124 new ArrayList<String>() // weekForecast
125 );
126
127 View rootView = inflater.inflate(R.layout.fragment_main, container, false);
128
129 ListView listView = (ListView) rootView.findViewById(R.id.listview_forecast);
130 listView.setAdapter(mForecastAdapter);
131 listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
132 @Override
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);
138 }
139 });
140
141 return rootView;
142 }
143
144 public void onStart() {
145 super.onStart();
146 updateWeather();
147 }
148
149 public class FetchWeatherTask extends AsyncTask<String, Void, String[]> {
150
151 private final String LOG_TAG = FetchWeatherTask.class.getSimpleName();
152
153 @Override
154 protected void onPostExecute(String[] result) {
155 if (result != null) {
156 mForecastAdapter.clear();
157 // for (String dayForecastStr: result) {
158 // mForecastAdapter.add(dayForecastStr);
159 // }
160 mForecastAdapter.addAll(result);
161 }
162 }
163
164 @Override
165 protected String[] doInBackground(String... params) {
166 if (params.length == 0) {
167 return null;
168 }
169
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;
174
175 // Will contain the raw JSON response as a string.
176 String forecastJsonStr;
177
178 String format = "json";
179 String units = "metric";
180 int numDays = 7;
181
182 try {
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";
192
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))
198 .build();
199
200 URL url = new URL(builtUri.toString());
201 /**/ Log.v(LOG_TAG, "Built URI " + builtUri.toString());
202
203 // Create the request to OpenWeatherMap, and open the connection
204 urlConnection = (HttpURLConnection) url.openConnection();
205 urlConnection.setRequestMethod("GET");
206 urlConnection.connect();
207
208 // Read the input stream into a String
209 InputStream inputStream = urlConnection.getInputStream();
210 StringBuffer buffer = new StringBuffer();
211 if (inputStream == null) {
212 // Nothing to do.
213 return null;
214 }
215 reader = new BufferedReader(new InputStreamReader(inputStream));
216
217 String line;
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");
223 }
224
225 if (buffer.length() == 0) {
226 // Stream was empty. No point in parsing.
227 return null;
228 }
229 forecastJsonStr = buffer.toString();
230 // Log.v(LOG_TAG, "Forecast JSON string: " + forecastJsonStr);
231 try {
232 return getWeatherDataFromJson(forecastJsonStr, numDays);
233 } catch (JSONException e) {
234 Log.e(LOG_TAG, e.getMessage(), e);
235 e.printStackTrace();
236 }
237
238 // This will only happen if we fail to read the forecast
239 return null;
240
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
244 // to parse it.
245 forecastJsonStr = null;
246 } finally {
247 if (urlConnection != null) {
248 urlConnection.disconnect();
249 }
250 if (reader != null) {
251 try {
252 reader.close();
253 } catch (final IOException e) {
254 Log.e(LOG_TAG, "Error closing stream", e);
255 }
256 }
257 }
258 return null;
259 }
260
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.
263 */
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();
270 }
271
272 /**
273 * Prepare the weather high/lows for presentation.
274 */
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);
282
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);
286 }
287
288 String highLowStr = roundedHigh + "/" + roundedLow;
289 return highLowStr;
290 }
291
292 /**
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.
295 * <p/>
296 * Fortunately parsing is easy: constructor takes the JSON string and converts it
297 * into an Object hierarchy for us.
298 */
299 private String[] getWeatherDataFromJson(String forecastJsonStr, int numDays)
300 throws JSONException {
301
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";
310
311 JSONObject forecastJson = new JSONObject(forecastJsonStr);
312 JSONArray weatherArray = forecastJson.getJSONArray(OWM_LIST);
313
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"
317 String day;
318 String description;
319 String highAndLow;
320
321 // Get the JSON object representing the day
322 JSONObject dayForecast = weatherArray.getJSONObject(i);
323
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
326 // "this saturday".
327 long dateTime = dayForecast.getLong(OWM_DATETIME);
328 day = getReadableDateString(dateTime);
329
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);
333
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);
339
340 highAndLow = formatHighLows(high, low);
341 resultStrs[i] = day + " - " + description + " - " + highAndLow;
342 }
343
344 // for (String s: resultStrs) {
345 // Log.v(LOG_TAG, "Forecast entry: " + s);
346 // }
347 return resultStrs;
348 }
349 }
350 }