************************************************************************
strings.xml
************************************************************************
WeatherViewer
YOUR_API_KEY
http://api.openweathermap.org/data/2.5/forecast/daily?q=
Invalid URL
A graphical representation of the weather conditions
High: %s
Low: %s
%1$s: %2$s
Humidity: %s
Enter city (e.g, Boston, MA, US)
Unable to read weather data
Unable to connect to OpenWeatherMap.org
************************************************************************
Weather.java
************************************************************************
package at.htl.weatherviewer;
import java.text.NumberFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.TimeZone;
public class Weather {
public final String dayOfWeek;
public final String minTemp;
public final String maxTemp;
public final String humidity;
public final String description;
public final String iconURL;
public Weather(long timeStamp, double minTemp, double maxTemp,
double humiditiy, String description, String iconName) {
//NumberFormat to format double temperatures rounded to integers
NumberFormat numberFormat = NumberFormat.getInstance() ;
numberFormat.setMaximumFractionDigits(0);
this.dayOfWeek = convertTimeStampToDay(timeStamp);
this.minTemp = numberFormat.format(minTemp) + "\u00B0C";
this.maxTemp = numberFormat.format(maxTemp) + "\u00B0C";
this.humidity = NumberFormat.getPercentInstance().format(humiditiy / 100.0);
this.description = description;
this.iconURL = "http://openweathermap.org/img/w/" + iconName + ".png";
}
// convert timestamp to a day's name (e.g. Monday, Tuesday, ..)
private static String convertTimeStampToDay(long timeStamp) {
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(timeStamp * 1000);
TimeZone tz = TimeZone.getDefault(); // get device's time zone
// adjust time for device's time zone
calendar.add(Calendar.MILLISECOND, tz.getOffset(calendar.getTimeInMillis()));
// SimpleDateFormat that returns the day's name
SimpleDateFormat dateFormatter = new SimpleDateFormat("EEEE");
return dateFormatter.format(calendar.getTime());
}
}
************************************************************************
WeatherArrayAdpter.java
************************************************************************
package at.htl.weatherviewer;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class WeatherArrayAdapter extends ArrayAdapter {
private static final String LOG_TAG = WeatherArrayAdapter.class.getSimpleName();
// class for reusing views as list items scroll off and onto the screen
private static class ViewHolder {
ImageView conditionImageView;
TextView dayTextView;
TextView lowTextView;
TextView hiTextView;
TextView humidityTextView;
}
// stores already downloaded Bitmaps for reuse
private Map bitmaps = new HashMap<>();
// constructor to initialize superclass inherited members
public WeatherArrayAdapter(Context context, List forecast) {
super(context, -1, forecast);
}
// creates the custom view for the ListView's items
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View rowView = convertView;
// get Weather object for this specified ListView position
Weather day = getItem(position);
ViewHolder viewHolder; // object that references's list item's views
// check for reusable ViewHolder from ListView item that scrolled
// offscreen; otherwise create a new ViewHolder
if (rowView == null) { // no reusable ViewHolder, so create one
viewHolder = new ViewHolder();
LayoutInflater inflater = LayoutInflater.from(getContext());
rowView = inflater.inflate(R.layout.list_item, parent, false);
viewHolder.conditionImageView =
(ImageView) rowView.findViewById(R.id.conditionImageView);
viewHolder.dayTextView =
(TextView) rowView.findViewById(R.id.dayTextView);
viewHolder.lowTextView =
(TextView) rowView.findViewById(R.id.lowTextView);
viewHolder.hiTextView =
(TextView) rowView.findViewById(R.id.hiTextView);
viewHolder.humidityTextView =
(TextView) rowView.findViewById(R.id.humidityTextView);
rowView.setTag(viewHolder); // assign the data to the item as a tag
} else { // reuse existing ViewHolder stored as the list item's tag
viewHolder = (ViewHolder) rowView.getTag();
}
// if weather condition icon already downloaded use it;
// otherwise, download icon in a separate thread
if (bitmaps.containsKey(day.iconURL)) {
viewHolder.conditionImageView.setImageBitmap(bitmaps.get(day.iconURL));
} else {
// download and display weather condition image
Log.d(LOG_TAG, "day.iconURL = " + day.iconURL);
new LoadImageTask(viewHolder.conditionImageView).execute(day.iconURL);
}
// get other data from Weather object and place into views
Context context = getContext(); // for loading String resources
viewHolder.dayTextView.setText(context.getString(
R.string.day_description, day.dayOfWeek, day.description));
viewHolder.lowTextView.setText(
context.getString(R.string.low_temp, day.minTemp));
viewHolder.hiTextView.setText(
context.getString(R.string.high_temp, day.maxTemp));
viewHolder.humidityTextView.setText(
context.getString(R.string.humidity, day.humidity));
return rowView; // return completed list item to display
}
// AsyncTask to load weather conditions icons in a separate thread
private class LoadImageTask extends AsyncTask {
private ImageView imageView; // displays the thumbnail
// store ImageView on which to set the download Bitmap
public LoadImageTask(ImageView imageView) {
this.imageView = imageView;
}
// load image: params[0] is the String URL representing the image
@Override
protected Bitmap doInBackground(String... params) {
Bitmap bitmap = null;
HttpURLConnection connection = null;
try {
URL url = new URL(params[0]); // create URL for image
// open an HttpURLConnection, get its InputStream
// and download the image
connection = (HttpURLConnection) url.openConnection();
try (InputStream inputStream = connection.getInputStream()) {
bitmap = BitmapFactory.decodeStream(inputStream);
bitmaps.put(params[0], bitmap); // cache for later use
} catch (Exception e) {
e.printStackTrace();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
connection.disconnect();
}
return bitmap;
}
// set weather condition image in list item
@Override
protected void onPostExecute(Bitmap bitmap) {
imageView.setImageBitmap(bitmap);
}
}
}
************************************************************************
MainActivity.java
************************************************************************
package at.htl.weatherviewer;
import android.content.Context;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import android.widget.AdapterView;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.Toast;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends AppCompatActivity {
private static final String LOG_TAG = MainActivity.class.getSimpleName();
// List of Weather objects representing the forecast
private List weatherList = new ArrayList<>();
// ArrayAdapter for binding Weather objects to a ListView
private WeatherArrayAdapter weatherArrayAdapter;
private ListView weatherListView;
// configure Toolbar, ListView and FAB
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// autogenerated code to inflate layout and configure Toolbar
setContentView(R.layout.activity_main);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
// create ArrayAdapter to bind weatherList to the weatherListView
weatherListView = (ListView) findViewById(R.id.weatherListView);
weatherArrayAdapter = new WeatherArrayAdapter(this, weatherList);
weatherListView.setAdapter(weatherArrayAdapter);
weatherListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView> parent, View view, int position, long id) {
Toast.makeText(getBaseContext(), "Item clicked", Toast.LENGTH_SHORT).show();
Snackbar.make(findViewById(R.id.coordinatorLayout), "item clicked", Snackbar.LENGTH_SHORT).show();
}
});
// Configure FAB to hide keyboard and initiate web service request
FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
// get next from locationEditText and create web service URL
EditText locationEditText = (EditText) findViewById(R.id.locationEditText);
URL url = createURL(locationEditText.getText().toString());
// hide keyboard and initiate a GetWeatherTask to download
// weather data from OpenWeatherMap.org in a separate thread
if (url != null) {
dismissKeyboard(locationEditText);
GetWeatherTask getLocalWeatherTask = new GetWeatherTask();
getLocalWeatherTask.execute(url);
} else {
Snackbar.make(findViewById(R.id.coordinatorLayout),
R.string.invalid_url, Snackbar.LENGTH_LONG).show();
}
}
});
}
private void dismissKeyboard(View view) {
InputMethodManager imm =
(InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
}
// create openweathermap.org web service URL using city
private URL createURL(String city) {
String apiKey = getString(R.string.api_key);
String baseUrl = getString(R.string.web_service_url);
try {
// create URL for specified city and metric units (Celsius)
String urlString = baseUrl + URLEncoder.encode(city, "UTF-8") +
"&units=metric&cnt=16&APPID=" + apiKey;
// "&units=metric&lang=de&cnt=16&APPID=" + apiKey; // deutsche Übersetzung
return new URL(urlString);
} catch (Exception e) {
e.printStackTrace();
}
return null; // URL was malformed
}
// makes the REST web service call to get weather data and
// saves the data to a local HTML file
private class GetWeatherTask extends AsyncTask {
private final String LOG_TAG = GetWeatherTask.class.getSimpleName();
@Override
protected JSONObject doInBackground(URL... params) {
HttpURLConnection connection = null;
try {
connection = (HttpURLConnection) params[0].openConnection();
int response = connection.getResponseCode();
if (response == HttpURLConnection.HTTP_OK) {
StringBuilder builder = new StringBuilder();
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(connection.getInputStream()))) {
String line;
while ((line = reader.readLine()) != null) {
builder.append(line);
}
} catch (IOException e) {
Snackbar.make(findViewById(R.id.coordinatorLayout),
R.string.read_error, Snackbar.LENGTH_LONG).show();
e.printStackTrace();
}
Log.d(LOG_TAG, builder.toString());
return new JSONObject(builder.toString());
} else {
Snackbar.make(findViewById(R.id.coordinatorLayout),
R.string.connect_error, Snackbar.LENGTH_LONG).show();
}
} catch (Exception e) {
Snackbar.make(findViewById(R.id.coordinatorLayout),
R.string.connect_error, Snackbar.LENGTH_LONG).show();
e.printStackTrace();
} finally {
connection.disconnect(); // close the HttpURLConnection
}
return null;
}
// process JSON response and update ListView
@Override
protected void onPostExecute(JSONObject weather) {
convertJSONtoArrayList(weather); // repopulate weatherList
weatherArrayAdapter.notifyDataSetChanged(); // rebind to ListView
weatherListView.smoothScrollToPosition(0); // scroll to top
}
}
// create Weather objects from JSONObject containing the forecast
private void convertJSONtoArrayList(JSONObject forecast) {
weatherList.clear(); // clear old weather data
try {
// get forecast's "list" JSONArray
JSONArray list = forecast.getJSONArray("list");
// convert each element of list to a Weather object
for (int i = 0; i < list.length(); ++i) {
JSONObject day = list.getJSONObject(i); // get one day's data
// get the day's temperatures ("temp") JSONObject
JSONObject temperatures = day.getJSONObject("temp");
// get day's "weather" JSONObject for the description and icon
JSONObject weather =
day.getJSONArray("weather").getJSONObject(0);
// add new Weather object to weatherList
weatherList.add(new Weather(
day.getLong("dt"), // date/time timestamp
temperatures.getDouble("min"), // minimum temperature
temperatures.getDouble("max"), // maximum temperature
day.getDouble("humidity"), // percent humidity
weather.getString("description"), // weather conditions
weather.getString("icon"))); // icon name
}
} catch (JSONException e) {
e.printStackTrace();
}
}
}
************************************************************************
************************************************************************
************************************************************************
************************************************************************
************************************************************************
************************************************************************
************************************************************************
************************************************************************
************************************************************************
************************************************************************
************************************************************************
************************************************************************
************************************************************************
************************************************************************
************************************************************************
************************************************************************
************************************************************************
************************************************************************
************************************************************************
************************************************************************
************************************************************************
************************************************************************
************************************************************************
************************************************************************
************************************************************************
************************************************************************
************************************************************************
************************************************************************
************************************************************************
************************************************************************
************************************************************************
************************************************************************
************************************************************************
************************************************************************
************************************************************************
************************************************************************