diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c6cbe56 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +*.iml +.gradle +/local.properties +/.idea/workspace.xml +/.idea/libraries +.DS_Store +/build +/captures diff --git a/.idea/.name b/.idea/.name new file mode 100644 index 0000000..90b2abe --- /dev/null +++ b/.idea/.name @@ -0,0 +1 @@ +Retofit2Example \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..9a8b7e5 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,22 @@ + + + + + \ No newline at end of file diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml new file mode 100644 index 0000000..e7bedf3 --- /dev/null +++ b/.idea/copyright/profiles_settings.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..97626ba --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..cd2ead8 --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,24 @@ + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..f976130 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + + + + + Android Lint + + + Groovy + + + HTML + + + J2ME issues + + + Java language level issues + + + JavaBeans issues + + + Pattern Validation + + + Threading issues + + + Threading issuesGroovy + + + XML + + + + + + + + + + + + + + + + + + + + + + + + + 1.8 + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..705e23c --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml new file mode 100644 index 0000000..7f68460 --- /dev/null +++ b/.idea/runConfigurations.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..4936dfd --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# Retrofit2Example diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..966edff --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,28 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 24 + buildToolsVersion "23.0.3" + + defaultConfig { + applicationId "com.opendroid.droid.retofit2example" + minSdkVersion 15 + targetSdkVersion 24 + versionCode 1 + versionName "1.0" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + compile fileTree(dir: 'libs', include: ['*.jar']) + testCompile 'junit:junit:4.12' + compile 'com.android.support:appcompat-v7:24.2.0' + compile 'com.squareup.retrofit2:retrofit:2.1.0' + compile 'com.squareup.retrofit2:converter-gson:2.1.0' +} diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..2397c38 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,17 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in /Users/ajaythakur/Developer/android-sdk-macosx/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} diff --git a/app/src/androidTest/java/com/opendroid/droid/retofit2example/ApplicationTest.java b/app/src/androidTest/java/com/opendroid/droid/retofit2example/ApplicationTest.java new file mode 100644 index 0000000..861e654 --- /dev/null +++ b/app/src/androidTest/java/com/opendroid/droid/retofit2example/ApplicationTest.java @@ -0,0 +1,13 @@ +package com.opendroid.droid.retofit2example; + +import android.app.Application; +import android.test.ApplicationTestCase; + +/** + * Testing Fundamentals + */ +public class ApplicationTest extends ApplicationTestCase { + public ApplicationTest() { + super(Application.class); + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..33b00c4 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/opendroid/droid/retofit2example/MainActivity.java b/app/src/main/java/com/opendroid/droid/retofit2example/MainActivity.java new file mode 100644 index 0000000..5c921b3 --- /dev/null +++ b/app/src/main/java/com/opendroid/droid/retofit2example/MainActivity.java @@ -0,0 +1,324 @@ +package com.opendroid.droid.retofit2example; + + +import android.app.Activity; +import android.content.Context; +import android.content.res.Resources; +import android.os.Bundle; +import android.os.IBinder; +import android.support.v7.app.AppCompatActivity; +import android.util.Log; +import android.view.View; +import android.view.inputmethod.InputMethodManager; +import android.widget.EditText; +import android.widget.ProgressBar; +import android.widget.TextView; +import android.widget.Toast; + +import java.lang.ref.WeakReference; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; +import java.util.concurrent.atomic.AtomicBoolean; + +import okhttp3.ResponseBody; +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; + +public class MainActivity extends AppCompatActivity { + protected final String TAG = getClass().getSimpleName(); + private RetainedAppData mRetainedAppData; + // UX handlers + private ProgressBar mProgressBar; + private EditText mInputCityName; + private TextView mCityNameTextView; + private TextView mCountryNameTextView; + private TextView mCodTextView; + private TextView mCoordsTextView; + private TextView mTempTextView; + private TextView mSunriseTextView; + private TextView mSunsetTextView; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + mProgressBar = (ProgressBar) findViewById(R.id.progress_bar_id); + mInputCityName = (EditText) findViewById(R.id.input_city_id); + // Data saved + if (savedInstanceState != null) { + if (getLastCustomNonConfigurationInstance() != null) { + mRetainedAppData = (RetainedAppData) getLastCustomNonConfigurationInstance(); + Log.d(TAG,"onCreate(): Reusing retained data set"); + } + } else { + mRetainedAppData = new RetainedAppData(); + Log.d(TAG, "onCreate(): Creating new data set"); + } + + // Setup activity reference + // mActivityRef = new WeakReference<>(this); + mRetainedAppData.setAppContext(this); + + if (mRetainedAppData.mData != null) { + updateUXWithWeather(mRetainedAppData.mData); + } + // Setup the progress bar + if (mRetainedAppData.isFetchInProgress()) { + mProgressBar.setVisibility(View.VISIBLE); + } else { + mProgressBar.setVisibility(View.INVISIBLE); + } + } + + @Override + protected void onDestroy() { + super.onDestroy(); + // mActivityRef = null; + mRetainedAppData.setAppContext(null); + Log.d(TAG,"onDestroy()"); + } + + @Override + public Object onRetainCustomNonConfigurationInstance() { + return mRetainedAppData; + } + + public void expandWeatherSync(View v) { + hideKeyboard(MainActivity.this, mInputCityName.getWindowToken()); + String city = mInputCityName.getText().toString(); + if (city.isEmpty()) { + Toast.makeText(getApplicationContext(),"No city specified.", + Toast.LENGTH_LONG).show(); + return; + } + mRetainedAppData.runRetrofitTestSync(city); + } + + public void expandWeatherAsync(View v) { + hideKeyboard(MainActivity.this, mInputCityName.getWindowToken()); + String city = mInputCityName.getText().toString(); + if (city.isEmpty()) { + Toast.makeText(getApplicationContext(),"No city specified.", + Toast.LENGTH_LONG).show(); + return; + } + mRetainedAppData.runRetrofitTestAsync(city); + } + + /** + * This method is used to hide a keyboard after a user has + * finished typing the url. + */ + public static void hideKeyboard(Activity activity, + IBinder windowToken) { + InputMethodManager mgr = (InputMethodManager) activity.getSystemService + (Context.INPUT_METHOD_SERVICE); + mgr.hideSoftInputFromWindow(windowToken, 0); + } + + void updateUXWithWeather (final Weather data) { + runOnUiThread(new Runnable() { + @Override + public void run() { + // Setup UX handlers + // Get the UX handlers every time. This is to avoid a condition + // when runOnUiThread may not have updated UX handles when screen is rotated. + // 'mActivityRef.get()' + mProgressBar = (ProgressBar) findViewById(R.id.progress_bar_id); + mInputCityName = (EditText) findViewById(R.id.input_city_id); + mCityNameTextView = (TextView) findViewById(R.id.city_name_id); + mCountryNameTextView = (TextView) findViewById(R.id.country_name_id); + mCodTextView = (TextView) findViewById(R.id.cod_id); + mCoordsTextView = (TextView) findViewById(R.id.coords_id); + mTempTextView = (TextView) findViewById(R.id.temp_id); + mSunriseTextView = (TextView) findViewById(R.id.sunrise_id); + mSunsetTextView = (TextView) findViewById(R.id.sunset_id); + + // Refresh UX data + if (mRetainedAppData.isFetchInProgress()) { + mProgressBar.setVisibility(View.VISIBLE); + } else { + mProgressBar.setVisibility(View.INVISIBLE); + } + + // Print data to Android display + Resources res = getResources(); + String textToPrint = res.getString(R.string.city) + data.getName(); + mCityNameTextView.setText(textToPrint); + textToPrint = res.getString(R.string.country) + data.getCountry(); + mCountryNameTextView.setText(textToPrint); + textToPrint = res.getString(R.string.coordinates) +"(" + data.getLat() + "," + data.getLon() + ")"; + mCoordsTextView.setText(textToPrint); + textToPrint = res.getString(R.string.cod) + data.getCod(); + mCodTextView.setText(textToPrint); + String tempF = String.format(Locale.UK,"Temperature: %.2f F", (data.getTemp() - 273.15) * 1.8 + 32.00); + mTempTextView.setText(tempF); + DateFormat dfLocalTz = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss", Locale.UK); + Date sunriseTime = new Date(data.getSunrise() * 1000); + Date sunsetTime = new Date(data.getSunset() * 1000); + textToPrint = res.getString(R.string.sunrise) + dfLocalTz.format(sunriseTime); + mSunriseTextView.setText(textToPrint); + textToPrint = res.getString(R.string.sunset) + dfLocalTz.format(sunsetTime); + mSunsetTextView.setText(textToPrint); + } + }); + } + + /** + * This is main class object that should save all data upon configuration changes. + * This object is saved by the 'onRetainCustomNonConfigurationInstance' method. + * + * Note: In the video it is referred to as 'private class TestWeather' + */ + private static class RetainedAppData { + private WeakReference mActivityRef; + protected final String TAG = "RTD"; + private Weather mData; // Weather data received + private AtomicBoolean mInProgress = new AtomicBoolean(false); // Is a download in progress + private WeatherRestAdapter mWeatherRestAdapter; // REST Adapter + private Callback mWeatherCallback = new Callback() { + @Override + public void onResponse(Call call, Response response) { + // response.isSuccessful() is true if the response code is 2xx + if (response.isSuccessful()) { + Weather data = response.body(); + Log.d(TAG, "Async success: Weather: Name:" + data.getName() + ", cod:" + data.getCod() + + ",Coord: (" + data.getLat() + "," + data.getLon() + + "), Temp:" + data.getTemp() + + "\nSunset:" + data.getSunset() + ", " + data.getSunrise() + + ", Country:" + data.getCountry()); + mData = data; + if (mActivityRef.get() != null) { + mActivityRef.get().updateUXWithWeather(mData); + mActivityRef.get().runOnUiThread(new Runnable() { + @Override + public void run() { + mActivityRef.get().mProgressBar = (ProgressBar) mActivityRef.get(). + findViewById(R.id.progress_bar_id); + mActivityRef.get().mProgressBar.setVisibility(View.INVISIBLE); + } + }); + } + mInProgress.set(false); + } else { + int statusCode = response.code(); + + // handle request errors yourself + ResponseBody errorBody = response.errorBody(); + Log.d(TAG,"Error code:" + statusCode + ", Error:" + errorBody); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + mInProgress.set(false); + if (mActivityRef.get() != null) { + mActivityRef.get().runOnUiThread(new Runnable() { + @Override + public void run() { + mActivityRef.get().mProgressBar = (ProgressBar) mActivityRef.get(). + findViewById(R.id.progress_bar_id); + mActivityRef.get().mProgressBar.setVisibility(View.INVISIBLE); + } + }); + } + } + }; + // Method to test Async. call + public void runRetrofitTestAsync (final String city) { + if ( (mActivityRef.get() != null) && (mInProgress.get())) { + Toast.makeText(mActivityRef.get(),"Weather fetch in progress.", + Toast.LENGTH_LONG).show(); + return; + } + // Get the Adapter + if (mWeatherRestAdapter == null) + mWeatherRestAdapter = new WeatherRestAdapter(); + + if (mActivityRef.get() != null) { + mActivityRef.get().mProgressBar.setVisibility(View.VISIBLE); + } + + // Test delay + try { + mInProgress.set(true); + mWeatherRestAdapter.testWeatherApi(city, mWeatherCallback); // Call Async API + } catch (Exception e) { + Log.d(TAG, "Thread sleep error" + e); + } + } + + // Method to test sync. call + public void runRetrofitTestSync (final String city) { + + if ((mActivityRef.get() != null) && (mInProgress.get())) { + Toast.makeText(mActivityRef.get(),"Weather fetch in progress.", + Toast.LENGTH_LONG).show(); + return; + } + if (mActivityRef.get() != null) { + mActivityRef.get().mProgressBar.setVisibility(View.VISIBLE); + } + + if (mWeatherRestAdapter == null) + mWeatherRestAdapter = new WeatherRestAdapter(); + + mInProgress.set(true); + + // Test Sync version -- in a separate thread + // Not doing this will crash the app. As Retro sync calls can not be made from + // UI thread. + new Thread(new Runnable() { + @Override + public void run() { + try { + // Call Async API -- always call in a try block if you dont want app to + // crash. You get 'HTTP/1.1 500 Internal Server Error' more often than + // you think. + Thread.sleep(10000); + Weather data = mWeatherRestAdapter.testWeatherApiSync(city); + if (data != null) { + Log.d(TAG, "Sync: Data: cod:" + data.getName() + ", cod:" + data.getCod() + + ",Coord: (" + data.getLat() + "," + data.getLon() + + "), Temp:" + data.getTemp() + + "\nSunset:" + data.getSunset() + ", " + data.getSunrise() + + ", Country:" + data.getCountry()); + mData = data; + if (mActivityRef.get() != null) { + mActivityRef.get().updateUXWithWeather(mData); + } + } else { + Log.e(TAG, "Sync: no data fetched"); + } + } catch (Exception ex) { + Log.e(TAG, "Sync: exception", ex); + if (mActivityRef.get() != null) { + mActivityRef.get().runOnUiThread(new Runnable() { + @Override + public void run() { + mActivityRef.get().mProgressBar = (ProgressBar) mActivityRef.get(). + findViewById(R.id.progress_bar_id); + mActivityRef.get().mProgressBar.setVisibility(View.INVISIBLE); + } + }); + } + } finally { + mInProgress.set(false); + } + } + }).start(); + } + + void setAppContext (MainActivity ref) { + mActivityRef = new WeakReference<>(ref); + } + + boolean isFetchInProgress() { + return mInProgress.get(); + } + } +} diff --git a/app/src/main/java/com/opendroid/droid/retofit2example/Weather.java b/app/src/main/java/com/opendroid/droid/retofit2example/Weather.java new file mode 100644 index 0000000..d53e7ca --- /dev/null +++ b/app/src/main/java/com/opendroid/droid/retofit2example/Weather.java @@ -0,0 +1,282 @@ +package com.opendroid.droid.retofit2example; + +import java.util.List; + +/** + * Created by ajaythakur on 6/16/15. + * + * { + * "coord":{"lon":-122.08,"lat":37.39}, + * "sys":{ + * "type":1,"id":451,"message":1.1091, + * "country":"United States of America", + * "sunrise":1434545231,"sunset":1434598293 + * }, + * "weather":[{"id":701,"main":"Mist","description":"mist","icon":"50n"}], + * "base":"stations", + * "main":{"temp":288.73,"pressure":1014,"humidity":82,"temp_min":284.26,"temp_max":294.15}, + * "visibility":16093, + * "wind":{"speed":4.1,"deg":350}, + * "clouds":{"all":20}, + * "dt":1434517063, + * "id":5375480, + * "name":"Mountain View", + * "cod":200 + * } + */ +public class Weather { + private Coord coord; + private int cod; + private String base; + private String name; + private Main main; + private List weather; + private Sys sys; + + public Weather(Coord coord, int code, String base, String name, Main main) { + this.coord = coord; + this.cod = code; + this.base = base; + this.name = name; + this.main = main; + } + + public int getCod() { + return cod; + } + + public void setCod(int code) { + this.cod = code; + } + + public String getBase() { + return base; + } + + public void setBase(String base) { + this.base = base; + } + + public Coord getCoord() { + return coord; + } + + public void setCoord(Coord coord) { + this.coord = coord; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Main getMain() { + return main; + } + + public void setMain(Main main) { + this.main = main; + } + + class Main { + double temp; + double pressure; + int humidity; + + public Main(double temp, int pressure, int humidity) { + this.temp = temp; + this.pressure = pressure; + this.humidity = humidity; + } + + public double getTemp() { + return temp; + } + + public void setTemp(double temp) { + this.temp = temp; + } + + public double getPressure() { + return pressure; + } + + public void setPressure(double pressure) { + this.pressure = pressure; + } + + public int getHumidity() { + return humidity; + } + + public void setHumidity(int humidity) { + this.humidity = humidity; + } + } + + class Coord { + double lat; + double lon; + + public Coord(double lat, double lon) { + this.lat = lat; + this.lon = lon; + } + + public double getLat() { + return lat; + } + + public void setLat(double lat) { + this.lat = lat; + } + + public double getLon() { + return lon; + } + + public void setLon(double lon) { + this.lon = lon; + } + } + + class WeatherDetails { + private int id; + String main; + String description; + String icon; + + public WeatherDetails() { + } + + public WeatherDetails(int id, String main, String description, String icon) { + this.id = id; + this.main = main; + this.description = description; + this.icon = icon; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getMain() { + return main; + } + + public void setMain(String main) { + this.main = main; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getIcon() { + return icon; + } + + public void setIcon(String icon) { + this.icon = icon; + } + } + + class Sys { + int type; + int id; + String country; + long sunrise; + long sunset; + + public Sys() { + } + + public Sys(int type, int id, String country, long sunrise, long sunset) { + this.type = type; + this.id = id; + this.country = country; + this.sunrise = sunrise; + this.sunset = sunset; + } + + public int getType() { + return type; + } + + public void setType(int type) { + this.type = type; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getCountry() { + return country; + } + + public void setCountry(String country) { + this.country = country; + } + + public long getSunrise() { + return sunrise; + } + + public void setSunrise(long sunrise) { + this.sunrise = sunrise; + } + + public long getSunset() { + return sunset; + } + + public void setSunset(long sunset) { + this.sunset = sunset; + } + } + + // Accesor methods + public double getLat() { + return coord.getLat(); + } + + public double getLon() { + return coord.getLon(); + } + + public double getPressure() { + return main.getPressure(); + } + + public double getTemp() { + return main.getTemp(); + } + + public String getCountry() { + return sys.getCountry(); + } + + public long getSunrise() { + return sys.getSunrise(); + } + + public long getSunset() { + return sys.getSunset(); + } +} diff --git a/app/src/main/java/com/opendroid/droid/retofit2example/WeatherApi.java b/app/src/main/java/com/opendroid/droid/retofit2example/WeatherApi.java new file mode 100644 index 0000000..e0f18f3 --- /dev/null +++ b/app/src/main/java/com/opendroid/droid/retofit2example/WeatherApi.java @@ -0,0 +1,15 @@ +package com.opendroid.droid.retofit2example; + +/** + * Created by ajaythakur on 8/23/16. + */ +import retrofit2.Call; +import retrofit2.http.GET; +import retrofit2.http.Query; + +public interface WeatherApi { + @GET("/data/2.5/weather") + Call getWeatherFromApi ( + @Query("q") String cityName, + @Query("APPID") String appId); +} diff --git a/app/src/main/java/com/opendroid/droid/retofit2example/WeatherRestAdapter.java b/app/src/main/java/com/opendroid/droid/retofit2example/WeatherRestAdapter.java new file mode 100644 index 0000000..a34fc0f --- /dev/null +++ b/app/src/main/java/com/opendroid/droid/retofit2example/WeatherRestAdapter.java @@ -0,0 +1,49 @@ +package com.opendroid.droid.retofit2example; + +/** + * Created by ajaythakur on 8/23/16. + */ +import android.util.Log; + +import java.io.IOException; + +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Retrofit; +import retrofit2.converter.gson.GsonConverterFactory; + +public class WeatherRestAdapter { + protected final String TAG = getClass().getSimpleName(); + protected Retrofit mRestAdapter; + protected WeatherApi mApi; + static final String WEATHER_URL="http://api.openweathermap.org/"; + static final String OPEN_WEATHER_API = "51337ba29f38cb7a5664cda04d84f4cd"; + + public WeatherRestAdapter() { + mRestAdapter = new Retrofit.Builder() + .baseUrl(WEATHER_URL) + .addConverterFactory(GsonConverterFactory.create()) + .build(); + mApi = mRestAdapter.create(WeatherApi.class); // create the interface + Log.d(TAG, "WeatherRestAdapter -- created"); + } + + public void testWeatherApi(String city, Callback callback){ + Log.d(TAG, "testWeatherApi: for city:" + city); + mApi.getWeatherFromApi(city, OPEN_WEATHER_API).enqueue(callback); + } + + public Weather testWeatherApiSync(String city) { + Log.d(TAG, "testWeatherApi: for city:" + city); + + Call call = mApi.getWeatherFromApi(city, OPEN_WEATHER_API); + Weather result = null; + try { + result = call.execute().body(); + } catch (IOException e) { + e.printStackTrace(); + } + + return result; + } +} diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..07ef6b1 --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,112 @@ + + + + + + + + + +