Skip to content

Commit

Permalink
Use loaders to fill UI
Browse files Browse the repository at this point in the history
Implement loader callbacks to populate data into UI.
Do this for both the forecastFragment and DetailACtivityFragment.
  • Loading branch information
Protino committed Aug 5, 2016
1 parent 14e27a9 commit a5e9071
Show file tree
Hide file tree
Showing 7 changed files with 314 additions and 161 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@ I had completed 70% of the Sunshine app almost 3 months. I've started the androi

`MyOpenWeatherMapApiKey = "[YOUR_API_KEY_HERE]"`

You can get an API_KEY from <a href="http://openweathermap.org">Open Weather</a>
You can get an API_KEY from <a href="http://openweathermap.org" target="_blank">Open Weather</a>

Please report issues with log errors if not possible, screenshots will do.
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,14 @@
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.database.DatabaseUtils;
import android.net.Uri;
import android.os.AsyncTask;
import android.preference.PreferenceManager;
import android.text.format.Time;
import android.util.Log;
import android.widget.ArrayAdapter;

import com.calgen.prodek.sunshine_v2.BuildConfig;
import com.calgen.prodek.sunshine_v2.R;
import com.calgen.prodek.sunshine_v2.data.WeatherContract.WeatherEntry;

import org.json.JSONArray;
Expand All @@ -42,61 +38,15 @@
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Vector;

public class FetchWeatherTask extends AsyncTask<String, Void, String[]> {
public class FetchWeatherTask extends AsyncTask<String, Void, Void> {

private final String LOG_TAG = FetchWeatherTask.class.getSimpleName();
private final Context mContext;
private ArrayAdapter<String> mForecastAdapter;
private boolean DEBUG = true;

public FetchWeatherTask(Context context, ArrayAdapter<String> forecastAdapter) {
public FetchWeatherTask(Context context) {
mContext = context;
mForecastAdapter = forecastAdapter;
}

/* The date/time conversion code is going to be moved outside the asynctask later,
* so for convenience we're breaking it out into its own method now.
*/
private String getReadableDateString(long time) {
// Because the API returns a unix timestamp (measured in seconds),
// it must be converted to milliseconds in order to be converted to valid date.
Date date = new Date(time);
SimpleDateFormat format = new SimpleDateFormat("E, MMM d");
return format.format(date).toString();
}

/**
* Prepare the weather high/lows for presentation.
*/
private String formatHighLows(double high, double low) {
// Data is fetched in Celsius by default.
// If user prefers to see in Fahrenheit, convert the values here.
// We do this rather than fetching in Fahrenheit so that the user can
// change this option without us having to re-fetch the data once
// we start storing the values in a database.
SharedPreferences sharedPrefs =
PreferenceManager.getDefaultSharedPreferences(mContext);
String unitType = sharedPrefs.getString(
mContext.getString(R.string.pref_temperature_key),
mContext.getString(R.string.pref_temperature_default));

if (unitType.equals(mContext.getString(R.string.pref_units_imperial))) {
high = (high * 1.8) + 32;
low = (low * 1.8) + 32;
} else if (!unitType.equals(mContext.getString(R.string.pref_units_metric))) {
Log.d(LOG_TAG, "Unit type not found: " + unitType);
}

// For presentation, assume the user doesn't care about tenths of a degree.
long roundedHigh = Math.round(high);
long roundedLow = Math.round(low);

String highLowStr = roundedHigh + "/" + roundedLow;
return highLowStr;
}

/**
Expand Down Expand Up @@ -130,26 +80,6 @@ public long addLocation(String locationSetting, String cityName, double lat, dou
return locationId;
}

/*
Students: This code will allow the FetchWeatherTask to continue to return the strings that
the UX expects so that we can continue to test the application even once we begin using
the database.
*/
String[] convertContentValuesToUXFormat(Vector<ContentValues> cvv) {
// return strings to keep UI functional for now
String[] resultStrs = new String[cvv.size()];
for (int i = 0; i < cvv.size(); i++) {
ContentValues weatherValues = cvv.elementAt(i);
String highAndLow = formatHighLows(
weatherValues.getAsDouble(WeatherEntry.COLUMN_MAX_TEMP),
weatherValues.getAsDouble(WeatherEntry.COLUMN_MIN_TEMP));
resultStrs[i] = getReadableDateString(
weatherValues.getAsLong(WeatherEntry.COLUMN_DATE)) +
" - " + weatherValues.getAsString(WeatherEntry.COLUMN_SHORT_DESC) +
" - " + highAndLow;
}
return resultStrs;
}

/**
* Take the String representing the complete forecast in JSON Format and
Expand All @@ -158,7 +88,7 @@ String[] convertContentValuesToUXFormat(Vector<ContentValues> cvv) {
* Fortunately parsing is easy: constructor takes the JSON string and converts it
* into an Object hierarchy for us.
*/
private String[] getWeatherDataFromJson(String forecastJsonStr,
private void getWeatherDataFromJson(String forecastJsonStr,
String locationSetting)
throws JSONException {

Expand Down Expand Up @@ -310,18 +240,14 @@ private String[] getWeatherDataFromJson(String forecastJsonStr,

Log.d(LOG_TAG, "FetchWeatherTask Complete. " + cVVector.size() + " Inserted");

String[] resultStrs = convertContentValuesToUXFormat(cVVector);
return resultStrs;

} catch (JSONException e) {
Log.e(LOG_TAG, e.getMessage(), e);
e.printStackTrace();
}
return null;
}

@Override
protected String[] doInBackground(String... params) {
protected Void doInBackground(String... params) {

// If there's no zip code, there's nothing to look up. Verify size of params.
if (params.length == 0) {
Expand Down Expand Up @@ -368,6 +294,7 @@ protected String[] doInBackground(String... params) {
urlConnection.setRequestMethod("GET");
urlConnection.connect();


// Read the input stream into a String
InputStream inputStream = urlConnection.getInputStream();
StringBuffer buffer = new StringBuffer();
Expand All @@ -390,11 +317,15 @@ protected String[] doInBackground(String... params) {
return null;
}
forecastJsonStr = buffer.toString();
getWeatherDataFromJson(forecastJsonStr, locationQuery);
} catch (IOException e) {
Log.e(LOG_TAG, "Error ", e);
// If the code didn't successfully get the weather data, there's no point in attemping
// to parse it.
return null;
} catch (JSONException e) {
Log.e(LOG_TAG, e.getMessage(), e);
e.printStackTrace();
} finally {
if (urlConnection != null) {
urlConnection.disconnect();
Expand All @@ -407,25 +338,6 @@ protected String[] doInBackground(String... params) {
}
}
}

try {
return getWeatherDataFromJson(forecastJsonStr, locationQuery);
} catch (JSONException e) {
Log.e(LOG_TAG, e.getMessage(), e);
e.printStackTrace();
}
// This will only happen if there was an error getting or parsing the forecast.
return null;
}

@Override
protected void onPostExecute(String[] result) {
if (result != null && mForecastAdapter != null) {
mForecastAdapter.clear();
for (String dayForecastStr : result) {
mForecastAdapter.add(dayForecastStr);
}
// New data is back from the server. Hooray!
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,7 @@ public int bulkInsert(@NonNull Uri uri, @NonNull ContentValues[] values) {
db.setTransactionSuccessful();
} finally {
db.endTransaction();
db.close();
}
getContext().getContentResolver().notifyChange(uri, null);
return returnCount;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
package com.calgen.prodek.sunshine_v2.fragment;

import android.content.Intent;
import android.database.Cursor;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.support.v4.view.MenuItemCompat;
import android.support.v7.widget.ShareActionProvider;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
Expand All @@ -15,74 +19,130 @@
import android.widget.TextView;

import com.calgen.prodek.sunshine_v2.R;
import com.calgen.prodek.sunshine_v2.Utility;
import com.calgen.prodek.sunshine_v2.data.WeatherContract.WeatherEntry;

/**
* A placeholder fragment containing a simple view.
*/
public class DetailActivityFragment extends Fragment {

public static String forecastData;
private String LOG_TAG = DetailActivityFragment.class.getSimpleName();
private ShareActionProvider myShareActionProvider;
public class DetailActivityFragment extends Fragment implements LoaderManager.LoaderCallbacks<Cursor> {

private static final String LOG_TAG = DetailActivityFragment.class.getSimpleName();

private static final String FORECAST_SHARE_HASHTAG = " #SunshineApp";
private static final int DETAIL_LOADER = 0;
private static final String[] FORECAST_COLUMNS = {
WeatherEntry.TABLE_NAME + "." + WeatherEntry._ID,
WeatherEntry.COLUMN_DATE,
WeatherEntry.COLUMN_SHORT_DESC,
WeatherEntry.COLUMN_MAX_TEMP,
WeatherEntry.COLUMN_MIN_TEMP,
};
// these constants correspond to the projection defined above, and must change if the
// projection changes
private static final int COL_WEATHER_ID = 0;
private static final int COL_WEATHER_DATE = 1;
private static final int COL_WEATHER_DESC = 2;
private static final int COL_WEATHER_MAX_TEMP = 3;
private static final int COL_WEATHER_MIN_TEMP = 4;
private ShareActionProvider mShareActionProvider;
private String mForecast;
private TextView detailTextView;

public DetailActivityFragment() {
setHasOptionsMenu(true);
}

@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
setHasOptionsMenu(true);
super.onCreate(savedInstanceState);
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_detail, container, false);
detailTextView = (TextView) rootView.findViewById(R.id.detail_forecast_data);
return rootView;
}

@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
// Inflate the menu; this adds items to the action bar if it is present.
inflater.inflate(R.menu.menu_detail, menu);
MenuItem shareItem = menu.findItem(R.id.action_share);
myShareActionProvider = (ShareActionProvider) MenuItemCompat.getActionProvider(shareItem);

myShareActionProvider.setShareIntent(createShareIntent());
super.onCreateOptionsMenu(menu, inflater);
}
// Retrieve the share menu item
MenuItem menuItem = menu.findItem(R.id.action_share);

@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
// Get the provider and hold onto it to set/change the share intent.
mShareActionProvider = (ShareActionProvider) MenuItemCompat.getActionProvider(menuItem);

switch (id) {
case R.id.action_share:
break;
// If onLoadFinished happens before this, we can go ahead and set the share intent now.
if (mForecast != null) {
mShareActionProvider.setShareIntent(createShareForecastIntent());
}
return super.onOptionsItemSelected(item);
}

private Intent createShareIntent() {
Intent myShareIntent = new Intent(Intent.ACTION_SEND);
myShareIntent.setType("text/plain");
myShareIntent.putExtra(Intent.EXTRA_TEXT, forecastData + "#Sunshine_App");
setShareIntent(myShareIntent);
return myShareIntent;
private Intent createShareForecastIntent() {
Intent shareIntent = new Intent(Intent.ACTION_SEND);
shareIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
shareIntent.setType("text/plain");
shareIntent.putExtra(Intent.EXTRA_TEXT, mForecast + FORECAST_SHARE_HASHTAG);
return shareIntent;
}

@Override
public void onActivityCreated(Bundle savedInstanceState) {
getLoaderManager().initLoader(DETAIL_LOADER, null, this);
super.onActivityCreated(savedInstanceState);
}

// Call to update the share intent
private void setShareIntent(Intent shareIntent) {
if (myShareActionProvider != null) {
myShareActionProvider.setShareIntent(shareIntent);
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
Log.v(LOG_TAG, "In onCreateLoader");
Intent intent = getActivity().getIntent();
if (intent == null) {
return null;
}

// Now create and return a CursorLoader that will take care of
// creating a Cursor for the data being displayed.
return new CursorLoader(
getActivity(),
intent.getData(),
FORECAST_COLUMNS,
null,
null,
null
);
}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
if (!data.moveToFirst()) {
return;
}

View rootView = inflater.inflate(R.layout.fragment_detail, container, false);
String dateString = Utility.formatDate(
data.getLong(COL_WEATHER_DATE));

Intent intent = getActivity().getIntent();
if (intent != null && intent.hasExtra(Intent.EXTRA_TEXT))
forecastData = intent.getStringExtra(Intent.EXTRA_TEXT);
String weatherDescription =
data.getString(COL_WEATHER_DESC);

TextView textView = (TextView) rootView.findViewById(R.id.detail_forecast_data);
textView.setText(forecastData);
boolean isMetric = Utility.isMetric(getActivity());

return rootView;
String high = Utility.formatTemperature(
data.getDouble(COL_WEATHER_MAX_TEMP), isMetric);

String low = Utility.formatTemperature(
data.getDouble(COL_WEATHER_MIN_TEMP), isMetric);

mForecast = String.format("%s - %s - %s/%s", dateString, weatherDescription, high, low);
detailTextView.setText(mForecast);

// If onCreateOptionsMenu has already happened, we need to update the share intent now.
if (mShareActionProvider != null) {
mShareActionProvider.setShareIntent(createShareForecastIntent());
}
}

@Override
public void onLoaderReset(Loader<Cursor> loader) {
}
}

Loading

0 comments on commit a5e9071

Please sign in to comment.