Got basic detail view 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.net.Uri;
5 import android.os.AsyncTask;
6 import android.os.Bundle;
7 import android.support.v4.app.Fragment;
8 import android.util.Log;
9 import android.view.LayoutInflater;
10 import android.view.Menu;
11 import android.view.MenuInflater;
12 import android.view.MenuItem;
13 import android.view.View;
14 import android.view.ViewGroup;
15 import android.widget.AdapterView;
16 import android.widget.ArrayAdapter;
17 import android.widget.ListView;
18 import android.widget.Toast;
19
20 import org.json.JSONArray;
21 import org.json.JSONException;
22 import org.json.JSONObject;
23
24 import java.io.BufferedReader;
25 import java.io.IOException;
26 import java.io.InputStream;
27 import java.io.InputStreamReader;
28 import java.net.HttpURLConnection;
29 import java.net.URL;
30 import java.text.SimpleDateFormat;
31 import java.util.ArrayList;
32 import java.util.Arrays;
33 import java.util.Date;
34 import java.util.List;
35
36 /**
37 * A placeholder fragment containing a simple view.
38 */
39 public class ForecastFragment extends Fragment {
40
41 private ArrayAdapter<String> mForecastAdapter;
42
43 public ForecastFragment() {
44 }
45
46 @Override
47 public void onCreate(Bundle savedInstanceState) {
48 super.onCreate(savedInstanceState);
49 setHasOptionsMenu(true);
50 }
51
52 @Override
53 public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
54 // Inflate the menu; this adds items to the action bar if it is present.
55 inflater.inflate(R.menu.forecastfragment, menu);
56 }
57
58 @Override
59 public boolean onOptionsItemSelected(MenuItem item) {
60 // Handle action bar item clicks here. The action bar will
61 // automatically handle clicks on the Home/Up button, so long
62 // as you specify a parent activity in AndroidManifest.xml.
63 int id = item.getItemId();
64 if (id == R.id.action_refresh) {
65 FetchWeatherTask weatherTask = new FetchWeatherTask();
66 weatherTask.execute("2642465");
67 return true;
68 }
69 return super.onOptionsItemSelected(item);
70 }
71
72 @Override
73 public View onCreateView(LayoutInflater inflater, ViewGroup container,
74 Bundle savedInstanceState) {
75
76 String[] forecastArray = {
77 "Today - Sunny - 10/10",
78 "Tomorrow - Cloudy - 11/11",
79 "Tuesday - Snow - 12/12",
80 "Wednesday - Rain - 13/13",
81 "Thursday - Hail - 14/14",
82 "Friday - Scorchio - 15/15",
83 "Saturday - Fog - 16/16"
84 };
85
86 List<String> weekForecast = new ArrayList<String>(
87 Arrays.asList(forecastArray));
88 mForecastAdapter = new ArrayAdapter<String>(
89 getActivity(),
90 R.layout.list_item_forecast,
91 R.id.list_item_forecast_textview,
92 weekForecast
93 );
94
95 View rootView = inflater.inflate(R.layout.fragment_main, container, false);
96
97 ListView listView = (ListView) rootView.findViewById(R.id.listview_forecast);
98 listView.setAdapter(mForecastAdapter);
99 listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
100 @Override
101 public void onItemClick(AdapterView<?> adapterView, View view, int position, long l) {
102 String forecast = mForecastAdapter.getItem(position);
103 Intent intent = new Intent(getActivity(), DetailActivity.class)
104 .putExtra(Intent.EXTRA_TEXT, forecast);
105 startActivity(intent);
106 }
107 });
108
109 return rootView;
110 }
111
112 public class FetchWeatherTask extends AsyncTask<String, Void, String[]> {
113
114 private final String LOG_TAG = FetchWeatherTask.class.getSimpleName();
115
116 @Override
117 protected void onPostExecute(String[] result) {
118 if (result != null) {
119 mForecastAdapter.clear();
120 // for (String dayForecastStr: result) {
121 // mForecastAdapter.add(dayForecastStr);
122 // }
123 mForecastAdapter.addAll(result);
124 }
125 }
126
127 @Override
128 protected String[] doInBackground(String... params) {
129 if (params.length == 0) {
130 return null;
131 }
132
133 // These two need to be declared outside the try/catch
134 // so that they can be closed in the finally block.
135 HttpURLConnection urlConnection = null;
136 BufferedReader reader = null;
137
138 // Will contain the raw JSON response as a string.
139 String forecastJsonStr;
140
141 String format = "json";
142 String units = "metric";
143 int numDays = 7;
144
145 try {
146 // Construct the URL for the OpenWeatherMap query
147 // Possible parameters are avaiable at OWM's forecast API page, at
148 // http://openweathermap.org/API#forecast
149 final String FORECAST_BASE_URL = "http://api.openweathermap.org/data/2.5/forecast/daily?";
150 final String QUERY_PARAM = "id";
151 final String FORMAT_PARAM = "mode";
152 final String UNITS_PARAM = "units";
153 final String DAYS_PARAM = "cnt";
154
155 Uri builtUri = Uri.parse(FORECAST_BASE_URL).buildUpon()
156 .appendQueryParameter(QUERY_PARAM, params[0])
157 .appendQueryParameter(FORMAT_PARAM, format)
158 .appendQueryParameter(UNITS_PARAM, units)
159 .appendQueryParameter(DAYS_PARAM, Integer.toString(numDays))
160 .build();
161
162 URL url = new URL(builtUri.toString());
163 /**/ Log.v(LOG_TAG, "Built URI " + builtUri.toString());
164
165 // Create the request to OpenWeatherMap, and open the connection
166 urlConnection = (HttpURLConnection) url.openConnection();
167 urlConnection.setRequestMethod("GET");
168 urlConnection.connect();
169
170 // Read the input stream into a String
171 InputStream inputStream = urlConnection.getInputStream();
172 StringBuffer buffer = new StringBuffer();
173 if (inputStream == null) {
174 // Nothing to do.
175 return null;
176 }
177 reader = new BufferedReader(new InputStreamReader(inputStream));
178
179 String line;
180 while ((line = reader.readLine()) != null) {
181 // Since it's JSON, adding a newline isn't necessary (it won't affect parsing)
182 // But it does make debugging a *lot* easier if you print out the completed
183 // buffer for debugging.
184 buffer.append(line + "\n");
185 }
186
187 if (buffer.length() == 0) {
188 // Stream was empty. No point in parsing.
189 return null;
190 }
191 forecastJsonStr = buffer.toString();
192 // Log.v(LOG_TAG, "Forecast JSON string: " + forecastJsonStr);
193 try {
194 return getWeatherDataFromJson(forecastJsonStr, numDays);
195 } catch (JSONException e) {
196 Log.e(LOG_TAG, e.getMessage(), e);
197 e.printStackTrace();
198 }
199
200 // This will only happen if we fail to read the forecast
201 return null;
202
203 } catch (IOException e) {
204 Log.e(LOG_TAG, "Error ", e);
205 // If the code didn't successfully get the weather data, there's no point in attempting
206 // to parse it.
207 forecastJsonStr = null;
208 } finally {
209 if (urlConnection != null) {
210 urlConnection.disconnect();
211 }
212 if (reader != null) {
213 try {
214 reader.close();
215 } catch (final IOException e) {
216 Log.e(LOG_TAG, "Error closing stream", e);
217 }
218 }
219 }
220 return null;
221 }
222
223 /* The date/time conversion code is going to be moved outside the asynctask later,
224 * so for convenience we're breaking it out into its own method now.
225 */
226 private String getReadableDateString(long time) {
227 // Because the API returns a unix timestamp (measured in seconds),
228 // it must be converted to milliseconds in order to be converted to valid date.
229 Date date = new Date(time * 1000);
230 SimpleDateFormat format = new SimpleDateFormat("E, MMM d");
231 return format.format(date).toString();
232 }
233
234 /**
235 * Prepare the weather high/lows for presentation.
236 */
237 private String formatHighLows(double high, double low) {
238 // For presentation, assume the user doesn't care about tenths of a degree.
239 long roundedHigh = Math.round(high);
240 long roundedLow = Math.round(low);
241
242 String highLowStr = roundedHigh + "/" + roundedLow;
243 return highLowStr;
244 }
245
246 /**
247 * Take the String representing the complete forecast in JSON Format and
248 * pull out the data we need to construct the Strings needed for the wireframes.
249 * <p/>
250 * Fortunately parsing is easy: constructor takes the JSON string and converts it
251 * into an Object hierarchy for us.
252 */
253 private String[] getWeatherDataFromJson(String forecastJsonStr, int numDays)
254 throws JSONException {
255
256 // These are the names of the JSON objects that need to be extracted.
257 final String OWM_LIST = "list";
258 final String OWM_WEATHER = "weather";
259 final String OWM_TEMPERATURE = "temp";
260 final String OWM_MAX = "max";
261 final String OWM_MIN = "min";
262 final String OWM_DATETIME = "dt";
263 final String OWM_DESCRIPTION = "main";
264
265 JSONObject forecastJson = new JSONObject(forecastJsonStr);
266 JSONArray weatherArray = forecastJson.getJSONArray(OWM_LIST);
267
268 String[] resultStrs = new String[numDays];
269 for (int i = 0; i < weatherArray.length(); i++) {
270 // For now, using the format "Day, description, hi/low"
271 String day;
272 String description;
273 String highAndLow;
274
275 // Get the JSON object representing the day
276 JSONObject dayForecast = weatherArray.getJSONObject(i);
277
278 // The date/time is returned as a long. We need to convert that
279 // into something human-readable, since most people won't read "1400356800" as
280 // "this saturday".
281 long dateTime = dayForecast.getLong(OWM_DATETIME);
282 day = getReadableDateString(dateTime);
283
284 // description is in a child array called "weather", which is 1 element long.
285 JSONObject weatherObject = dayForecast.getJSONArray(OWM_WEATHER).getJSONObject(0);
286 description = weatherObject.getString(OWM_DESCRIPTION);
287
288 // Temperatures are in a child object called "temp". Try not to name variables
289 // "temp" when working with temperature. It confuses everybody.
290 JSONObject temperatureObject = dayForecast.getJSONObject(OWM_TEMPERATURE);
291 double high = temperatureObject.getDouble(OWM_MAX);
292 double low = temperatureObject.getDouble(OWM_MIN);
293
294 highAndLow = formatHighLows(high, low);
295 resultStrs[i] = day + " - " + description + " - " + highAndLow;
296 }
297
298 // for (String s: resultStrs) {
299 // Log.v(LOG_TAG, "Forecast entry: " + s);
300 // }
301 return resultStrs;
302 }
303 }
304 }