-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #8 from santiago-hollmann/develop
Merge develop into master
- Loading branch information
Showing
52 changed files
with
2,071 additions
and
169 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
# Weathy | ||
Weathy is a sample weather app for Android. It uses material design and the OpenWeatherMap API. | ||
|
||
# Setup | ||
Just make a git clone of the project and import it into Android Studio. | ||
|
||
# License | ||
The project is under Mozilla Public License v2.0. For more information check this: https://www.mozilla.org/en-US/MPL/2.0/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
11 changes: 11 additions & 0 deletions
11
app/src/main/java/com/shollmann/weathy/api/ExcludedFromAPISerialization.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package com.shollmann.weathy.api; | ||
|
||
import java.lang.annotation.ElementType; | ||
import java.lang.annotation.Retention; | ||
import java.lang.annotation.RetentionPolicy; | ||
import java.lang.annotation.Target; | ||
|
||
@Retention(RetentionPolicy.RUNTIME) | ||
@Target({ElementType.TYPE, ElementType.FIELD}) | ||
public @interface ExcludedFromAPISerialization { | ||
} |
38 changes: 38 additions & 0 deletions
38
app/src/main/java/com/shollmann/weathy/api/OpenWeatherApi.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
package com.shollmann.weathy.api; | ||
|
||
import com.shollmann.weathy.api.baseapi.BaseApi; | ||
import com.shollmann.weathy.api.baseapi.BaseApiCall; | ||
import com.shollmann.weathy.api.baseapi.CachePolicy; | ||
import com.shollmann.weathy.api.baseapi.CallId; | ||
import com.shollmann.weathy.api.contract.OpenWeatherContract; | ||
import com.shollmann.weathy.api.model.WeatherReport; | ||
import com.shollmann.weathy.helper.Constants; | ||
|
||
import retrofit.Callback; | ||
import retrofit.RequestInterceptor; | ||
|
||
public class OpenWeatherApi extends BaseApi<OpenWeatherContract> { | ||
|
||
public OpenWeatherApi(String baseUrl) { | ||
super(baseUrl, OpenWeatherContract.class); | ||
} | ||
|
||
@Override | ||
protected void onRequest(RequestInterceptor.RequestFacade request) { | ||
request.addQueryParam(Constants.OpenWeatherApi.APP_ID_QUERY_PARAM, Constants.OpenWeatherApi.API_KEY); | ||
request.addQueryParam(Constants.OpenWeatherApi.UNITS_QUERY_PARAM, Constants.OpenWeatherApi.METRIC); | ||
} | ||
|
||
public void getWeatherForCityName(String cityName, CallId callId, Callback<WeatherReport> callback) { | ||
CachePolicy cachePolicy = CachePolicy.CACHE_ELSE_NETWORK; | ||
cachePolicy.setCacheKey(String.format("weather_report_for_%1$s", cityName)); | ||
cachePolicy.setCacheTTL(Constants.Time.TEN_MINUTES); | ||
|
||
BaseApiCall<WeatherReport> apiCall = registerCall(callId, cachePolicy, callback, WeatherReport.class); | ||
|
||
if (apiCall != null && apiCall.requiresNetworkCall()) { | ||
getService().getWeatherForCityName(cityName, apiCall); | ||
} | ||
} | ||
|
||
} |
208 changes: 208 additions & 0 deletions
208
app/src/main/java/com/shollmann/weathy/api/baseapi/BaseApi.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,208 @@ | ||
package com.shollmann.weathy.api.baseapi; | ||
|
||
import com.google.gson.ExclusionStrategy; | ||
import com.google.gson.FieldAttributes; | ||
import com.google.gson.Gson; | ||
import com.google.gson.GsonBuilder; | ||
import com.shollmann.weathy.api.ExcludedFromAPISerialization; | ||
import com.shollmann.weathy.helper.Constants; | ||
import com.shollmann.weathy.ui.WeathyApplication; | ||
import com.squareup.okhttp.Cache; | ||
import com.squareup.okhttp.OkHttpClient; | ||
|
||
import java.io.File; | ||
import java.lang.reflect.Type; | ||
import java.util.HashMap; | ||
import java.util.Map; | ||
import java.util.Set; | ||
import java.util.concurrent.TimeUnit; | ||
|
||
import retrofit.Callback; | ||
import retrofit.RequestInterceptor; | ||
import retrofit.RestAdapter; | ||
import retrofit.client.OkClient; | ||
import retrofit.converter.Converter; | ||
import retrofit.converter.GsonConverter; | ||
import retrofit.mime.TypedByteArray; | ||
import retrofit.mime.TypedInput; | ||
|
||
|
||
public abstract class BaseApi<T> { | ||
|
||
private static final long DEFAULT_TIMEOUT = Constants.Time.TEN_SECONDS; | ||
private static final long DEFAULT_CACHE_DIR_SIZE = Constants.Size.TWO_MEBIBYTES; | ||
|
||
private Cache cache; | ||
private Class<T> contract; | ||
protected T service; | ||
private Map<CallId, BaseApiCall> ongoingCalls = new HashMap<>(); | ||
|
||
public BaseApi(String baseUrl, Class<T> contract) { | ||
this.contract = contract; | ||
initializeHttpCache(DEFAULT_CACHE_DIR_SIZE); | ||
setUrl(baseUrl); | ||
} | ||
|
||
private void initializeHttpCache(long dirSize) { | ||
String cacheDirectoryName = this.getClass().getSimpleName() + Constants.CACHE; | ||
File cacheDirectory = new File(WeathyApplication.getApplication().getCacheDir(), cacheDirectoryName); | ||
cache = new Cache(cacheDirectory, dirSize); | ||
} | ||
|
||
protected long getGeneralTimeout() { | ||
return DEFAULT_TIMEOUT; | ||
} | ||
|
||
public void setUrl(String baseUrl) { | ||
service = generateService(contract, baseUrl); | ||
} | ||
|
||
private T generateService(Class<T> contract, String baseUrl) { | ||
OkHttpClient okHttpClient = new OkHttpClient(); | ||
okHttpClient.setReadTimeout(getGeneralTimeout(), TimeUnit.SECONDS); | ||
okHttpClient.setWriteTimeout(getGeneralTimeout(), TimeUnit.SECONDS); | ||
okHttpClient.setConnectTimeout(getGeneralTimeout(), TimeUnit.SECONDS); | ||
okHttpClient.setCache(cache); | ||
|
||
RestAdapter.Builder builder = new RestAdapter.Builder() | ||
.setEndpoint(baseUrl) | ||
.setClient(new OkClient(okHttpClient)) | ||
.setConverter(getConverter()) | ||
.setRequestInterceptor(new RequestInterceptor() { | ||
@Override | ||
public void intercept(RequestFacade request) { | ||
onRequest(request); | ||
} | ||
} | ||
); | ||
builder.setLogLevel(RestAdapter.LogLevel.FULL); | ||
return builder.build().create(contract); | ||
} | ||
|
||
protected Converter getConverter() { | ||
GsonBuilder gsonBuilder = new GsonBuilder(); | ||
gsonBuilder.addSerializationExclusionStrategy(new ExclusionStrategy() { | ||
@Override | ||
public boolean shouldSkipField(FieldAttributes f) { | ||
if (f.getAnnotation(ExcludedFromAPISerialization.class) != null) { | ||
return true; | ||
} | ||
return false; | ||
} | ||
|
||
@Override | ||
public boolean shouldSkipClass(Class<?> clazz) { | ||
if (clazz.getAnnotation(ExcludedFromAPISerialization.class) != null) { | ||
return true; | ||
} | ||
return false; | ||
} | ||
}); | ||
|
||
Gson gson = gsonBuilder.create(); | ||
return new GsonConverter(gson); | ||
} | ||
|
||
protected void onRequest(RequestInterceptor.RequestFacade request) { | ||
} | ||
|
||
protected T getService() { | ||
return service; | ||
} | ||
|
||
public boolean hasOngoingCall(CallId callId) { | ||
return ongoingCalls.containsKey(callId); | ||
} | ||
|
||
public synchronized <CT> BaseApiCall<CT> registerCall(CallId callId, CachePolicy cachePolicy, Callback<CT> callback, Type responseType) { | ||
// LogInternal.logBaseApiCall("register-call", callId.toString()); | ||
if (ongoingCalls.containsKey(callId)) { | ||
// LogInternal.logBaseApiCall("pre-existing call detected", callId.toString()); | ||
cancelCall(callId); | ||
} | ||
BaseApiCall<CT> newCall = new BaseApiCall<>(this, callId, cachePolicy, callback, responseType); | ||
if (callback == null) { | ||
newCall.cancelCall(); // If callback == null on register then ignore the response. | ||
} | ||
ongoingCalls.put(callId, newCall); | ||
return newCall; | ||
} | ||
|
||
public synchronized boolean registerCallback(CallId callId, Callback callback) { | ||
BaseApiCall ongoingCall = ongoingCalls.get(callId); | ||
if (ongoingCall != null) { | ||
ongoingCall.updateCallback(callback); | ||
// LogInternal.logBaseApiCall("register-callback", callId.toString()); | ||
return true; | ||
} else { | ||
// LogInternal.logBaseApiCall("register-callback ignored", callId.toString(), Log.DEBUG); | ||
return false; | ||
} | ||
} | ||
|
||
public synchronized boolean unregisterCallback(CallId callId) { | ||
BaseApiCall ongoingCall = ongoingCalls.get(callId); | ||
if (ongoingCall != null) { | ||
ongoingCall.removeCallback(); | ||
// LogInternal.logBaseApiCall("unregister-callback", callId.toString()); | ||
return true; | ||
} else { | ||
// LogInternal.logBaseApiCall("unregister-callback ignored", callId.toString(), Log.DEBUG); | ||
return false; | ||
} | ||
} | ||
|
||
public synchronized void cancelCalls(CallOrigin callOrigin) { | ||
Set<CallId> ongoingCallIds = ongoingCalls.keySet(); | ||
if (ongoingCallIds != null && ongoingCallIds.size() > 0) { | ||
// LogInternal.logBaseApiCall("cancel-calls", callOrigin.name()); | ||
for (CallId callId : ongoingCallIds) { | ||
if (callId.getOrigin() == callOrigin) { | ||
cancelCall(callId); | ||
} | ||
} | ||
} else { | ||
// LogInternal.logBaseApiCall("cancel-calls ignored", callOrigin.name(), Log.DEBUG); | ||
} | ||
} | ||
|
||
public synchronized void cancelCall(CallId callId) { | ||
BaseApiCall ongoingCall = ongoingCalls.get(callId); | ||
if (ongoingCall != null) { | ||
ongoingCall.cancelCall(); | ||
ongoingCalls.remove(callId); | ||
// LogInternal.logBaseApiCall("cancel-call", callId.toString(), Log.INFO); | ||
} else { | ||
// LogInternal.logBaseApiCall("cancel-call ignored", callId.toString(), Log.DEBUG); | ||
} | ||
} | ||
|
||
public synchronized void removeCall(CallId callId) { | ||
BaseApiCall ongoingCall = ongoingCalls.get(callId); | ||
if (ongoingCall != null) { | ||
ongoingCalls.remove(callId); | ||
// LogInternal.logBaseApiCall("remove-call done", callId.toString(), Log.DEBUG); | ||
} else { | ||
// LogInternal.logBaseApiCall("remove-call ignored", callId.toString(), Log.DEBUG); | ||
} | ||
} | ||
|
||
protected TypedInput generateJsonTypedInput(Object object) { | ||
byte[] requestBytes = new byte[0]; | ||
try { | ||
requestBytes = (new Gson()).toJson(object).getBytes(Constants.UTF_8); | ||
} catch (Throwable t) { | ||
// LogInternal.error(t.toString()); | ||
} | ||
return new TypedByteArray("application/json", requestBytes); | ||
} | ||
|
||
public void deleteCache() { | ||
try { | ||
cache.delete(); | ||
} catch (Exception e) { | ||
// LogInternal.error(e.toString()); | ||
} | ||
initializeHttpCache(DEFAULT_CACHE_DIR_SIZE); | ||
} | ||
} |
Oops, something went wrong.