Java tutorial
/** * Copyright 2014 Gustavo Martin Morcuende * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package name.gumartinm.weather.information.fragment.overview; import java.io.IOException; import java.net.MalformedURLException; import java.net.URISyntaxException; import java.net.URL; import java.text.DecimalFormat; import java.text.NumberFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.List; import java.util.Locale; import org.apache.http.client.ClientProtocolException; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.net.http.AndroidHttpClient; import android.os.AsyncTask; import android.os.Bundle; import android.preference.PreferenceManager; import android.support.v4.app.ListFragment; import android.support.v4.content.LocalBroadcastManager; import android.view.View; import android.widget.ListView; import com.fasterxml.jackson.core.JsonParseException; import name.gumartinm.weather.information.R; import name.gumartinm.weather.information.activity.SpecificActivity; import name.gumartinm.weather.information.fragment.specific.SpecificFragment; import name.gumartinm.weather.information.httpclient.CustomHTTPClient; import name.gumartinm.weather.information.model.DatabaseQueries; import name.gumartinm.weather.information.model.WeatherLocation; import name.gumartinm.weather.information.model.forecastweather.Forecast; import name.gumartinm.weather.information.parser.JPOSForecastParser; import name.gumartinm.weather.information.service.IconsList; import name.gumartinm.weather.information.service.PermanentStorage; import name.gumartinm.weather.information.service.ServiceForecastParser; import name.gumartinm.weather.information.service.conversor.TempUnitsConversor; import name.gumartinm.weather.information.service.conversor.UnitsConversor; import timber.log.Timber; public class OverviewFragment extends ListFragment { private static final String TAG = "OverviewFragment"; private static final String BROADCAST_INTENT_ACTION = "name.gumartinm.weather.information.UPDATEFORECAST"; private BroadcastReceiver mReceiver; @Override public void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); } @Override public void onActivityCreated(final Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); final ListView listWeatherView = this.getListView(); listWeatherView.setChoiceMode(ListView.CHOICE_MODE_NONE); if (savedInstanceState != null) { // Restore UI state final Forecast forecast = (Forecast) savedInstanceState.getSerializable("Forecast"); if (forecast != null) { final PermanentStorage store = new PermanentStorage(this.getActivity().getApplicationContext()); store.saveForecast(forecast); } } this.setHasOptionsMenu(false); this.setEmptyText(this.getString(R.string.text_field_remote_error)); this.setListShownNoAnimation(false); } @Override public void onResume() { super.onResume(); this.mReceiver = new BroadcastReceiver() { @Override public void onReceive(final Context context, final Intent intent) { final String action = intent.getAction(); if (action.equals(BROADCAST_INTENT_ACTION)) { final Forecast forecastRemote = (Forecast) intent.getSerializableExtra("forecast"); // 1. Check conditions. They must be the same as the ones that triggered the AsyncTask. final DatabaseQueries query = new DatabaseQueries(context.getApplicationContext()); final WeatherLocation weatherLocation = query.queryDataBase(); final PermanentStorage store = new PermanentStorage(context.getApplicationContext()); final Forecast forecast = store.getForecast(); if (forecast == null || !OverviewFragment.this.isDataFresh(weatherLocation.getLastForecastUIUpdate())) { if (forecastRemote != null) { // 2. Update UI. OverviewFragment.this.updateUI(forecastRemote); // 3. Update Data. store.saveForecast(forecastRemote); weatherLocation.setLastForecastUIUpdate(new Date()); query.updateDataBase(weatherLocation); // 4. Show list. OverviewFragment.this.setListShownNoAnimation(true); } else { // Empty list and show error message (see setEmptyText in onCreate) OverviewFragment.this.setListAdapter(null); OverviewFragment.this.setListShownNoAnimation(true); } } } } }; // Register receiver final IntentFilter filter = new IntentFilter(); filter.addAction(BROADCAST_INTENT_ACTION); LocalBroadcastManager.getInstance(this.getActivity().getApplicationContext()) .registerReceiver(this.mReceiver, filter); final DatabaseQueries query = new DatabaseQueries(this.getActivity().getApplicationContext()); final WeatherLocation weatherLocation = query.queryDataBase(); if (weatherLocation == null) { // Nothing to do. // Empty list and show error message (see setEmptyText in onCreate) this.setListAdapter(null); this.setListShownNoAnimation(true); return; } final PermanentStorage store = new PermanentStorage(this.getActivity().getApplicationContext()); final Forecast forecast = store.getForecast(); if (forecast != null && this.isDataFresh(weatherLocation.getLastForecastUIUpdate())) { this.updateUI(forecast); } else { // Load remote data (aynchronous) // Gets the data from the web. this.setListShownNoAnimation(false); final OverviewTask task = new OverviewTask(this.getActivity().getApplicationContext(), new CustomHTTPClient(AndroidHttpClient.newInstance(this.getString(R.string.http_client_agent))), new ServiceForecastParser(new JPOSForecastParser())); task.execute(weatherLocation.getLatitude(), weatherLocation.getLongitude()); } } @Override public void onSaveInstanceState(final Bundle savedInstanceState) { // Save UI state final PermanentStorage store = new PermanentStorage(this.getActivity().getApplicationContext()); final Forecast forecast = store.getForecast(); if (forecast != null) { savedInstanceState.putSerializable("Forecast", forecast); } super.onSaveInstanceState(savedInstanceState); } @Override public void onPause() { LocalBroadcastManager.getInstance(this.getActivity().getApplicationContext()) .unregisterReceiver(this.mReceiver); super.onPause(); } @Override public void onListItemClick(final ListView l, final View v, final int position, final long id) { final SpecificFragment fragment = (SpecificFragment) this.getFragmentManager() .findFragmentById(R.id.weather_specific_fragment); if (fragment == null) { // handset layout final Intent intent = new Intent(this.getActivity().getApplicationContext(), SpecificActivity.class); intent.putExtra("CHOSEN_DAY", (int) id); OverviewFragment.this.getActivity().startActivity(intent); } else { // tablet layout fragment.updateUIByChosenDay((int) id); } } private void updateUI(final Forecast forecastWeatherData) { // 1. Update units of measurement. final UnitsConversor tempUnitsConversor = new TempUnitsConversor( this.getActivity().getApplicationContext()); // 2. Update number day forecast. final SharedPreferences sharedPreferences = PreferenceManager .getDefaultSharedPreferences(this.getActivity().getApplicationContext()); final String keyPreference = this.getResources().getString(R.string.weather_preferences_day_forecast_key); final String dayForecast = sharedPreferences.getString(keyPreference, "5"); final int mDayForecast = Integer.valueOf(dayForecast); // 3. Formatters final DecimalFormat tempFormatter = (DecimalFormat) NumberFormat.getNumberInstance(Locale.US); tempFormatter.applyPattern("###.##"); final SimpleDateFormat dayNameFormatter = new SimpleDateFormat("EEE", Locale.US); final SimpleDateFormat monthAndDayNumberormatter = new SimpleDateFormat("MMM d", Locale.US); // 4. Prepare data for UI. final List<OverviewEntry> entries = new ArrayList<OverviewEntry>(); final OverviewAdapter adapter = new OverviewAdapter(this.getActivity(), R.layout.weather_main_entry_list); final Calendar calendar = Calendar.getInstance(); int count = mDayForecast; for (final name.gumartinm.weather.information.model.forecastweather.List forecast : forecastWeatherData .getList()) { Bitmap picture; if ((forecast.getWeather().size() > 0) && (forecast.getWeather().get(0).getIcon() != null) && (IconsList.getIcon(forecast.getWeather().get(0).getIcon()) != null)) { final String icon = forecast.getWeather().get(0).getIcon(); picture = BitmapFactory.decodeResource(this.getResources(), IconsList.getIcon(icon).getResourceDrawable()); } else { picture = BitmapFactory.decodeResource(this.getResources(), R.drawable.weather_severe_alert); } final Long forecastUNIXDate = (Long) forecast.getDt(); calendar.setTimeInMillis(forecastUNIXDate * 1000L); final Date dayTime = calendar.getTime(); final String dayTextName = dayNameFormatter.format(dayTime); final String monthAndDayNumberText = monthAndDayNumberormatter.format(dayTime); Double maxTemp = null; if (forecast.getTemp().getMax() != null) { maxTemp = (Double) forecast.getTemp().getMax(); maxTemp = tempUnitsConversor.doConversion(maxTemp); } Double minTemp = null; if (forecast.getTemp().getMin() != null) { minTemp = (Double) forecast.getTemp().getMin(); minTemp = tempUnitsConversor.doConversion(minTemp); } if ((maxTemp != null) && (minTemp != null)) { // TODO i18n? final String tempSymbol = tempUnitsConversor.getSymbol(); entries.add(new OverviewEntry(dayTextName, monthAndDayNumberText, tempFormatter.format(maxTemp) + tempSymbol, tempFormatter.format(minTemp) + tempSymbol, picture)); } count = count - 1; if (count == 0) { break; } } // 5. Update UI. adapter.addAll(entries); this.setListAdapter(adapter); } private boolean isDataFresh(final Date lastUpdate) { if (lastUpdate == null) { return false; } final SharedPreferences sharedPreferences = PreferenceManager .getDefaultSharedPreferences(this.getActivity().getApplicationContext()); final String keyPreference = this.getString(R.string.weather_preferences_refresh_interval_key); final String refresh = sharedPreferences.getString(keyPreference, this.getResources().getStringArray(R.array.weather_preferences_refresh_interval)[0]); final Date currentTime = new Date(); if (((currentTime.getTime() - lastUpdate.getTime())) < Long.valueOf(refresh)) { return true; } return false; } private static class OverviewTask extends AsyncTask<Object, Void, Forecast> { // Store the context passed to the AsyncTask when the system instantiates it. private final Context localContext; private final CustomHTTPClient HTTPClient; private final ServiceForecastParser weatherService; public OverviewTask(final Context context, final CustomHTTPClient HTTPClient, final ServiceForecastParser weatherService) { this.localContext = context; this.HTTPClient = HTTPClient; this.weatherService = weatherService; } @Override protected Forecast doInBackground(final Object... params) { final double latitude = (Double) params[0]; final double longitude = (Double) params[1]; Forecast forecast = null; try { forecast = this.doInBackgroundThrowable(latitude, longitude); } catch (final JsonParseException e) { Timber.e(e, "OverviewTask doInBackground exception: "); } catch (final ClientProtocolException e) { Timber.e(e, "OverviewTask doInBackground exception: "); } catch (final MalformedURLException e) { Timber.e(e, "OverviewTask doInBackground exception: "); } catch (final URISyntaxException e) { Timber.e(e, "OverviewTask doInBackground exception: "); } catch (final IOException e) { // logger infrastructure swallows UnknownHostException :/ Timber.e(e, "OverviewTask doInBackground exception: " + e.getMessage()); } catch (final Throwable e) { // I loathe doing this but we must show some error to our dear user by means // of returning Forecast null value. Timber.e(e, "OverviewTask doInBackground exception: "); } finally { HTTPClient.close(); } return forecast; } private Forecast doInBackgroundThrowable(final double latitude, final double longitude) throws URISyntaxException, ClientProtocolException, JsonParseException, IOException { final SharedPreferences sharedPreferences = PreferenceManager .getDefaultSharedPreferences(localContext.getApplicationContext()); final String APPID = sharedPreferences .getString(localContext.getString(R.string.weather_preferences_app_id_key), ""); final String APIVersion = localContext.getString(R.string.api_version); final String urlAPI = localContext.getString(R.string.uri_api_weather_forecast); String url = weatherService.createURIAPIForecast(urlAPI, APIVersion, latitude, longitude, localContext.getString(R.string.weather_preferences_day_forecast_fourteen_day)); if (!APPID.isEmpty()) { url = url.concat("&APPID=" + APPID); } final String jsonData = HTTPClient.retrieveDataAsString(new URL(url)); return weatherService.retrieveForecastFromJPOS(jsonData); } @Override protected void onPostExecute(final Forecast forecast) { // Call updateUI on the UI thread. final Intent forecastData = new Intent(BROADCAST_INTENT_ACTION); forecastData.putExtra("forecast", forecast); LocalBroadcastManager.getInstance(this.localContext).sendBroadcastSync(forecastData); } } }