Skip to content

Commit

Permalink
Merge pull request #8 from santiago-hollmann/develop
Browse files Browse the repository at this point in the history
Merge develop into master
  • Loading branch information
santiago-hollmann committed Apr 27, 2016
2 parents 1d12610 + 656e90b commit 0b1c465
Show file tree
Hide file tree
Showing 52 changed files with 2,071 additions and 169 deletions.
8 changes: 8 additions & 0 deletions README.md
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/
4 changes: 3 additions & 1 deletion app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,7 @@ dependencies {
compile 'com.android.support:appcompat-v7:23.2.1'
compile 'com.android.support:design:23.2.1'
compile 'com.android.support:cardview-v7:23.2.1'

compile 'com.squareup.retrofit:retrofit:1.9.0'
compile 'com.google.code.gson:gson:2.6.2'
compile 'com.squareup.okhttp:okhttp-urlconnection:2.3.0'
}
28 changes: 20 additions & 8 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,21 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.shollmann.weathy" >
<manifest
package="com.shollmann.weathy"
xmlns:android="http://schemas.android.com/apk/res/android">

<uses-permission android:name="android.permission.INTERNET"/>

<supports-screens
android:largeScreens="true"
android:normalScreens="true"
android:smallScreens="true"
android:xlargeScreens="false"/>

<application
android:name=".ui.WeathyApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:hardwareAccelerated="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme" >
android:theme="@style/AppTheme">
<activity
android:name=".HomeActivity"
android:name=".ui.activity.HomeActivity"
android:label="@string/app_name"
android:theme="@style/AppTheme.NoActionBar" >
android:screenOrientation="portrait"
android:theme="@style/AppTheme.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<action android:name="android.intent.action.MAIN"/>

<category android:name="android.intent.category.LAUNCHER" />
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
Expand Down
24 changes: 0 additions & 24 deletions app/src/main/java/com/shollmann/weathy/HomeActivity.java

This file was deleted.

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 app/src/main/java/com/shollmann/weathy/api/OpenWeatherApi.java
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 app/src/main/java/com/shollmann/weathy/api/baseapi/BaseApi.java
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);
}
}
Loading

0 comments on commit 0b1c465

Please sign in to comment.