diff --git a/app/build.gradle b/app/build.gradle
index 8c0c4cd1a7f095c0dbda101a487cf45821af03d1..d431fddabd25fe35b37eb39b23d7117ef9c61e21 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -26,6 +26,31 @@ android {
         }
     }
 
+    packagingOptions {
+        exclude 'META-INF/DEPENDENCIES'
+        exclude 'META-INF/LICENSE'
+        exclude 'META-INF/LICENSE.txt'
+        exclude 'META-INF/license.txt'
+        exclude 'META-INF/NOTICE'
+        exclude 'META-INF/NOTICE.txt'
+        exclude 'META-INF/notice.txt'
+        exclude 'META-INF/ASL2.0'
+    }
+
+    lintOptions {
+        abortOnError false
+    }
+
+}
+
+ext {
+    okhttp_version = "3.0.1"
+    oltu_version = "1.0.0"
+    retrofit_version = "2.0.0-beta3"
+    gson_version = "2.4"
+    swagger_annotations_version = "1.5.0"
+    junit_version = "4.12"
+
 }
 
 dependencies {
@@ -46,6 +71,14 @@ dependencies {
     implementation 'com.google.dagger:dagger-android:2.11'
     compileOnly 'javax.annotation:jsr250-api:1.0'
 
+    // RetroFit
+    implementation "com.squareup.okhttp3:okhttp:$okhttp_version"
+    implementation "com.squareup.retrofit2:retrofit:$retrofit_version"
+    implementation "com.squareup.retrofit2:converter-gson:$retrofit_version"
+    implementation "com.google.code.gson:gson:$gson_version"
+    implementation "io.swagger:swagger-annotations:$swagger_annotations_version"
+    implementation "org.apache.oltu.oauth2:org.apache.oltu.oauth2.client:$oltu_version"
+
     // Room
     implementation 'android.arch.persistence.room:runtime:1.0.0'
     annotationProcessor 'android.arch.persistence.room:compiler:1.0.0'
diff --git a/app/src/main/java/me/szaki/xkcd/xkcdbrowser/XKCDBrowserApplicationComponent.java b/app/src/main/java/me/szaki/xkcd/xkcdbrowser/XKCDBrowserApplicationComponent.java
index a90d0783dd69df9370dcf918e12744b4bc742c1a..6d3221882c38cbd468bb1682185a4cd1e33db5f3 100644
--- a/app/src/main/java/me/szaki/xkcd/xkcdbrowser/XKCDBrowserApplicationComponent.java
+++ b/app/src/main/java/me/szaki/xkcd/xkcdbrowser/XKCDBrowserApplicationComponent.java
@@ -7,13 +7,17 @@ import dagger.Component;
 import me.szaki.xkcd.xkcdbrowser.database.DBModule;
 import me.szaki.xkcd.xkcdbrowser.interactor.InteractorModule;
 import me.szaki.xkcd.xkcdbrowser.interactor.comics.ComicsInteractor;
+import me.szaki.xkcd.xkcdbrowser.network.NetworkModule;
 import me.szaki.xkcd.xkcdbrowser.ui.UIModule;
 import me.szaki.xkcd.xkcdbrowser.ui.detail.DetailActivity;
+import me.szaki.xkcd.xkcdbrowser.ui.detail.DetailPresenter;
 import me.szaki.xkcd.xkcdbrowser.ui.favorites.FavoritesActivity;
+import me.szaki.xkcd.xkcdbrowser.ui.favorites.FavoritesPresenter;
 import me.szaki.xkcd.xkcdbrowser.ui.main.MainActivity;
+import me.szaki.xkcd.xkcdbrowser.ui.main.MainPresenter;
 
 @Singleton
-@Component(modules = {UIModule.class, InteractorModule.class, DBModule.class})
+@Component(modules = {UIModule.class, InteractorModule.class, NetworkModule.class, DBModule.class})
 public interface XKCDBrowserApplicationComponent {
 
     void inject(MainActivity mainActivity);
@@ -22,5 +26,11 @@ public interface XKCDBrowserApplicationComponent {
 
     void inject(FavoritesActivity favoritesActivity);
 
+    void inject(MainPresenter mainPresenter);
+
+    void inject(DetailPresenter detailPresenter);
+
+    void inject(FavoritesPresenter favoritesPresenter);
+
     void inject(ComicsInteractor comicsInteractor);
 }
diff --git a/app/src/main/java/me/szaki/xkcd/xkcdbrowser/interactor/comics/ComicsInteractor.java b/app/src/main/java/me/szaki/xkcd/xkcdbrowser/interactor/comics/ComicsInteractor.java
index 30603ccaa0156bc1197e5d322647ae854fa7d9d3..2fd12020b8373bde88711bbf1ffadab2e17f3e71 100644
--- a/app/src/main/java/me/szaki/xkcd/xkcdbrowser/interactor/comics/ComicsInteractor.java
+++ b/app/src/main/java/me/szaki/xkcd/xkcdbrowser/interactor/comics/ComicsInteractor.java
@@ -1,10 +1,23 @@
 package me.szaki.xkcd.xkcdbrowser.interactor.comics;
 
 
+import java.io.IOException;
+
+import javax.inject.Inject;
+
 import me.szaki.xkcd.xkcdbrowser.XKCDBrowserApplication;
+import me.szaki.xkcd.xkcdbrowser.network.api.ComicApi;
+import me.szaki.xkcd.xkcdbrowser.network.model.Comic;
 
 public class ComicsInteractor {
+    @Inject
+    ComicApi comicApi;
+
     public ComicsInteractor() {
         XKCDBrowserApplication.injector.inject(this);
     }
+
+    public Comic getComic(long id) throws IOException {
+        return this.comicApi.getComic(id).execute().body();
+    }
 }
diff --git a/app/src/main/java/me/szaki/xkcd/xkcdbrowser/network/ApiClient.java b/app/src/main/java/me/szaki/xkcd/xkcdbrowser/network/ApiClient.java
new file mode 100644
index 0000000000000000000000000000000000000000..f39724dbfdd172946a6a4a4260769798fa701246
--- /dev/null
+++ b/app/src/main/java/me/szaki/xkcd/xkcdbrowser/network/ApiClient.java
@@ -0,0 +1,337 @@
+package me.szaki.xkcd.xkcdbrowser.network;
+
+import java.io.IOException;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+import java.util.Date;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import org.apache.oltu.oauth2.client.request.OAuthClientRequest.AuthenticationRequestBuilder;
+import org.apache.oltu.oauth2.client.request.OAuthClientRequest.TokenRequestBuilder;
+
+import retrofit2.Converter;
+import retrofit2.Retrofit;
+import retrofit2.GsonConverterFactory;
+
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonParseException;
+import okhttp3.Interceptor;
+import okhttp3.OkHttpClient;
+import okhttp3.RequestBody;
+import okhttp3.ResponseBody;
+
+
+import me.szaki.xkcd.xkcdbrowser.network.auth.HttpBasicAuth;
+import me.szaki.xkcd.xkcdbrowser.network.auth.ApiKeyAuth;
+import me.szaki.xkcd.xkcdbrowser.network.auth.OAuth;
+import me.szaki.xkcd.xkcdbrowser.network.auth.OAuth.AccessTokenListener;
+import me.szaki.xkcd.xkcdbrowser.network.auth.OAuthFlow;
+
+
+public class ApiClient {
+
+    private Map<String, Interceptor> apiAuthorizations;
+    private OkHttpClient okClient;
+    private Retrofit.Builder adapterBuilder;
+
+    public ApiClient() {
+        apiAuthorizations = new LinkedHashMap<String, Interceptor>();
+        createDefaultAdapter();
+    }
+
+    public ApiClient(String[] authNames) {
+        this();
+        for(String authName : authNames) { 
+            throw new RuntimeException("auth name \"" + authName + "\" not found in available auth names");
+        }
+    }
+
+    /**
+     * Basic constructor for single auth name
+     * @param authName
+     */
+    public ApiClient(String authName) {
+        this(new String[]{authName});
+    }
+
+    /**
+     * Helper constructor for single api key
+     * @param authName
+     * @param apiKey
+     */
+    public ApiClient(String authName, String apiKey) {
+        this(authName);
+        this.setApiKey(apiKey);
+    }
+
+    /**
+     * Helper constructor for single basic auth or password oauth2
+     * @param authName
+     * @param username
+     * @param password
+     */
+    public ApiClient(String authName, String username, String password) {
+        this(authName);
+        this.setCredentials(username,  password);
+    }
+
+    /**
+     * Helper constructor for single password oauth2
+     * @param authName
+     * @param clientId
+     * @param secret
+     * @param username
+     * @param password
+     */
+    public ApiClient(String authName, String clientId, String secret, String username, String password) {
+        this(authName);
+        this.getTokenEndPoint()
+                .setClientId(clientId)
+                .setClientSecret(secret)
+                .setUsername(username)
+                .setPassword(password);
+    }
+    
+   public void createDefaultAdapter() {
+        Gson gson = new GsonBuilder()
+                .setDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ")
+                .create();
+
+        okClient = new OkHttpClient();
+        
+        String baseUrl = "https://xkcd.com/";
+        if(!baseUrl.endsWith("/"))
+        	baseUrl = baseUrl + "/";
+
+        adapterBuilder = new Retrofit
+                .Builder()
+                .baseUrl(baseUrl)
+                .client(okClient)
+                
+                .addConverterFactory(GsonCustomConverterFactory.create(gson));
+    }
+
+    public <S> S createService(Class<S> serviceClass) {
+        return adapterBuilder.build().create(serviceClass);
+        
+    }
+
+    /**
+     * Helper method to configure the first api key found
+     * @param apiKey
+     */
+    private void setApiKey(String apiKey) {
+        for(Interceptor apiAuthorization : apiAuthorizations.values()) {
+            if (apiAuthorization instanceof ApiKeyAuth) {
+                ApiKeyAuth keyAuth = (ApiKeyAuth) apiAuthorization;
+                keyAuth.setApiKey(apiKey);
+                return;
+            }
+        }
+    }
+
+    /**
+     * Helper method to configure the username/password for basic auth or password oauth
+     * @param username
+     * @param password
+     */
+    private void setCredentials(String username, String password) {
+        for(Interceptor apiAuthorization : apiAuthorizations.values()) {
+            if (apiAuthorization instanceof HttpBasicAuth) {
+                HttpBasicAuth basicAuth = (HttpBasicAuth) apiAuthorization;
+                basicAuth.setCredentials(username, password);
+                return;
+            }
+            if (apiAuthorization instanceof OAuth) {
+                OAuth oauth = (OAuth) apiAuthorization;
+                oauth.getTokenRequestBuilder().setUsername(username).setPassword(password);
+                return;
+            }
+        }
+    }
+
+    /**
+     * Helper method to configure the token endpoint of the first oauth found in the apiAuthorizations (there should be only one)
+     * @return
+     */
+    public TokenRequestBuilder getTokenEndPoint() {
+        for(Interceptor apiAuthorization : apiAuthorizations.values()) {
+            if (apiAuthorization instanceof OAuth) {
+                OAuth oauth = (OAuth) apiAuthorization;
+                return oauth.getTokenRequestBuilder();
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Helper method to configure authorization endpoint of the first oauth found in the apiAuthorizations (there should be only one)
+     * @return
+     */
+    public AuthenticationRequestBuilder getAuthorizationEndPoint() {
+        for(Interceptor apiAuthorization : apiAuthorizations.values()) {
+            if (apiAuthorization instanceof OAuth) {
+                OAuth oauth = (OAuth) apiAuthorization;
+                return oauth.getAuthenticationRequestBuilder();
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Helper method to pre-set the oauth access token of the first oauth found in the apiAuthorizations (there should be only one)
+     * @param accessToken
+     */
+    public void setAccessToken(String accessToken) {
+        for(Interceptor apiAuthorization : apiAuthorizations.values()) {
+            if (apiAuthorization instanceof OAuth) {
+                OAuth oauth = (OAuth) apiAuthorization;
+                oauth.setAccessToken(accessToken);
+                return;
+            }
+        }
+    }
+    
+    /**
+     * Helper method to configure the oauth accessCode/implicit flow parameters
+     * @param clientId
+     * @param clientSecret
+     * @param redirectURI
+     */
+    public void configureAuthorizationFlow(String clientId, String clientSecret, String redirectURI) {
+        for(Interceptor apiAuthorization : apiAuthorizations.values()) {
+            if (apiAuthorization instanceof OAuth) {
+                OAuth oauth = (OAuth) apiAuthorization;
+                oauth.getTokenRequestBuilder()
+                        .setClientId(clientId)
+                        .setClientSecret(clientSecret)
+                        .setRedirectURI(redirectURI);
+                oauth.getAuthenticationRequestBuilder()
+                        .setClientId(clientId)
+                        .setRedirectURI(redirectURI);
+                return;
+            }
+        }
+    }
+    
+    /**
+     * Configures a listener which is notified when a new access token is received.
+     * @param accessTokenListener
+     */
+    public void registerAccessTokenListener(AccessTokenListener accessTokenListener) {
+        for(Interceptor apiAuthorization : apiAuthorizations.values()) {
+            if (apiAuthorization instanceof OAuth) {
+                OAuth oauth = (OAuth) apiAuthorization;
+                oauth.registerAccessTokenListener(accessTokenListener);
+                return;
+            }
+        }
+    }
+
+    /**
+     * Adds an authorization to be used by the client
+     * @param authName
+     * @param authorization
+     */
+    public void addAuthorization(String authName, Interceptor authorization) {
+        if (apiAuthorizations.containsKey(authName)) {
+            throw new RuntimeException("auth name \"" + authName + "\" already in api authorizations");
+        }
+        apiAuthorizations.put(authName, authorization);
+        okClient.interceptors().add(authorization);
+    }
+
+    public Map<String, Interceptor> getApiAuthorizations() {
+        return apiAuthorizations;
+    }
+
+    public void setApiAuthorizations(Map<String, Interceptor> apiAuthorizations) {
+        this.apiAuthorizations = apiAuthorizations;
+    }
+
+    public Retrofit.Builder getAdapterBuilder() {
+        return adapterBuilder;
+    }
+
+    public void setAdapterBuilder(Retrofit.Builder adapterBuilder) {
+        this.adapterBuilder = adapterBuilder;
+    }
+
+    public OkHttpClient getOkClient() {
+        return okClient;
+    }
+    
+    public void addAuthsToOkClient(OkHttpClient okClient) {
+        for(Interceptor apiAuthorization : apiAuthorizations.values()) {
+            okClient.interceptors().add(apiAuthorization);
+        }
+    }
+
+    /**
+     * Clones the okClient given in parameter, adds the auth interceptors and uses it to configure the Retrofit
+     * @param okClient
+     */
+    public void configureFromOkclient(OkHttpClient okClient) {
+        OkHttpClient clone = okClient.newBuilder().build();
+        addAuthsToOkClient(clone);
+        adapterBuilder.client(clone);
+    }
+}
+
+/**
+ * This wrapper is to take care of this case:
+ * when the deserialization fails due to JsonParseException and the
+ * expected type is String, then just return the body string.
+ */
+class GsonResponseBodyConverterToString<T> implements Converter<ResponseBody, T> {
+	  private final Gson gson;
+	  private final Type type;
+
+	  GsonResponseBodyConverterToString(Gson gson, Type type) {
+	    this.gson = gson;
+	    this.type = type;
+	  }
+
+	  @Override public T convert(ResponseBody value) throws IOException {
+	    String returned = value.string();
+	    try {
+	      return gson.fromJson(returned, type);
+	    } 
+	    catch (JsonParseException e) {
+                return (T) returned;
+        } 
+	 }
+}
+
+class GsonCustomConverterFactory extends Converter.Factory 
+{
+	public static GsonCustomConverterFactory create(Gson gson) {
+	    return new GsonCustomConverterFactory(gson);
+	  }
+
+	  private final Gson gson;
+	  private final GsonConverterFactory gsonConverterFactory;
+
+	  private GsonCustomConverterFactory(Gson gson) {
+	    if (gson == null) throw new NullPointerException("gson == null");
+	    this.gson = gson;
+	    this.gsonConverterFactory = GsonConverterFactory.create(gson);
+	  }
+
+    @Override
+    public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
+        if(type.equals(String.class))
+            return new GsonResponseBodyConverterToString<Object>(gson, type);
+        else
+            return gsonConverterFactory.responseBodyConverter(type, annotations, retrofit);
+    }
+
+    @Override
+    public Converter<?, RequestBody> requestBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
+            return gsonConverterFactory.requestBodyConverter(type, annotations, retrofit);
+    }
+}
+
diff --git a/app/src/main/java/me/szaki/xkcd/xkcdbrowser/network/CollectionFormats.java b/app/src/main/java/me/szaki/xkcd/xkcdbrowser/network/CollectionFormats.java
new file mode 100644
index 0000000000000000000000000000000000000000..a08640283cf098cfaf246b75b9d1dfab5fb0ece2
--- /dev/null
+++ b/app/src/main/java/me/szaki/xkcd/xkcdbrowser/network/CollectionFormats.java
@@ -0,0 +1,95 @@
+package me.szaki.xkcd.xkcdbrowser.network;
+
+import java.util.Arrays;
+import java.util.List;
+
+public class CollectionFormats {
+    
+    public static class CSVParams {
+
+        protected List<String> params;
+        
+        public CSVParams() {
+        }
+        
+        public CSVParams(List<String> params) {
+            this.params = params;
+        }
+        
+        public CSVParams(String... params) {
+            this.params = Arrays.asList(params);
+        }
+
+        public List<String> getParams() {
+            return params;
+        }
+
+        public void setParams(List<String> params) {
+            this.params = params;
+        }
+        
+        @Override
+        public String toString() {
+            return StringUtil.join(params.toArray(new String[0]), ",");
+        }
+        
+    }
+    
+    public static class SSVParams extends CSVParams {
+        
+        public SSVParams() {
+        }
+        
+        public SSVParams(List<String> params) {
+            super(params);
+        }
+
+        public SSVParams(String... params) {
+            super(params);
+        }
+
+        @Override
+        public String toString() {
+            return StringUtil.join(params.toArray(new String[0]), " ");
+        }
+    }
+    
+    public static class TSVParams extends CSVParams {
+        
+        public TSVParams() {
+        }
+        
+        public TSVParams(List<String> params) {
+            super(params);
+        }
+        
+        public TSVParams(String... params) {
+            super(params);
+        }
+
+        @Override
+        public String toString() {
+            return StringUtil.join( params.toArray(new String[0]), "\t");
+        }
+    }
+    
+    public static class PIPESParams extends CSVParams {
+        
+        public PIPESParams() {
+        }
+        
+        public PIPESParams(List<String> params) {
+            super(params);
+        }
+        
+        public PIPESParams(String... params) {
+            super(params);
+        }
+
+        @Override
+        public String toString() {
+            return StringUtil.join(params.toArray(new String[0]), "|");
+        }
+    }
+    
+}
diff --git a/app/src/main/java/me/szaki/xkcd/xkcdbrowser/network/NetworkModule.java b/app/src/main/java/me/szaki/xkcd/xkcdbrowser/network/NetworkModule.java
new file mode 100644
index 0000000000000000000000000000000000000000..6773494490ebc430bd4fbc226a2a42115e41087b
--- /dev/null
+++ b/app/src/main/java/me/szaki/xkcd/xkcdbrowser/network/NetworkModule.java
@@ -0,0 +1,16 @@
+package me.szaki.xkcd.xkcdbrowser.network;
+
+import javax.inject.Singleton;
+
+import dagger.Module;
+import dagger.Provides;
+import me.szaki.xkcd.xkcdbrowser.network.api.ComicApi;
+
+@Module
+public class NetworkModule {
+    @Singleton
+    @Provides
+    public ComicApi provideComicApi() {
+        return new ApiClient().createService(ComicApi.class);
+    }
+}
diff --git a/app/src/main/java/me/szaki/xkcd/xkcdbrowser/network/StringUtil.java b/app/src/main/java/me/szaki/xkcd/xkcdbrowser/network/StringUtil.java
new file mode 100644
index 0000000000000000000000000000000000000000..86246b0d2770be802f531b256077fc5b326a8119
--- /dev/null
+++ b/app/src/main/java/me/szaki/xkcd/xkcdbrowser/network/StringUtil.java
@@ -0,0 +1,42 @@
+package me.szaki.xkcd.xkcdbrowser.network;
+
+@javax.annotation.Generated(value = "class io.swagger.codegen.languages.JavaClientCodegen", date = "2018-04-16T20:13:49.474+02:00")
+public class StringUtil {
+  /**
+   * Check if the given array contains the given value (with case-insensitive comparison).
+   *
+   * @param array The array
+   * @param value The value to search
+   * @return true if the array contains the value
+   */
+  public static boolean containsIgnoreCase(String[] array, String value) {
+    for (String str : array) {
+      if (value == null && str == null) return true;
+      if (value != null && value.equalsIgnoreCase(str)) return true;
+    }
+    return false;
+  }
+
+  /**
+   * Join an array of strings with the given separator.
+   * <p>
+   * Note: This might be replaced by utility method from commons-lang or guava someday
+   * if one of those libraries is added as dependency.
+   * </p>
+   *
+   * @param array     The array of strings
+   * @param separator The separator
+   * @return the resulting string
+   */
+  public static String join(String[] array, String separator) {
+    int len = array.length;
+    if (len == 0) return "";
+
+    StringBuilder out = new StringBuilder();
+    out.append(array[0]);
+    for (int i = 1; i < len; i++) {
+      out.append(separator).append(array[i]);
+    }
+    return out.toString();
+  }
+}
diff --git a/app/src/main/java/me/szaki/xkcd/xkcdbrowser/network/api/ComicApi.java b/app/src/main/java/me/szaki/xkcd/xkcdbrowser/network/api/ComicApi.java
new file mode 100644
index 0000000000000000000000000000000000000000..21e4029cc043aa84b3a80c8017fe35cb63f637cc
--- /dev/null
+++ b/app/src/main/java/me/szaki/xkcd/xkcdbrowser/network/api/ComicApi.java
@@ -0,0 +1,33 @@
+package me.szaki.xkcd.xkcdbrowser.network.api;
+
+import me.szaki.xkcd.xkcdbrowser.network.CollectionFormats.*;
+
+
+import retrofit2.Call;
+import retrofit2.http.*;
+
+import okhttp3.RequestBody;
+
+import me.szaki.xkcd.xkcdbrowser.network.model.Comic;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public interface ComicApi {
+  
+  /**
+   * Get a comic
+   * 
+   * @param id Number of the comic
+   * @return Call<Comic>
+   */
+  
+  @GET("{id}/info.0.json")
+  Call<Comic> getComic(
+    @Path("id") Long id
+  );
+
+  
+}
diff --git a/app/src/main/java/me/szaki/xkcd/xkcdbrowser/network/auth/ApiKeyAuth.java b/app/src/main/java/me/szaki/xkcd/xkcdbrowser/network/auth/ApiKeyAuth.java
new file mode 100644
index 0000000000000000000000000000000000000000..fb51edb02460e9b7b985b7880e0098c5eea184c8
--- /dev/null
+++ b/app/src/main/java/me/szaki/xkcd/xkcdbrowser/network/auth/ApiKeyAuth.java
@@ -0,0 +1,68 @@
+package me.szaki.xkcd.xkcdbrowser.network.auth;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+
+import okhttp3.Interceptor;
+import okhttp3.Request;
+import okhttp3.Response;
+
+public class ApiKeyAuth implements Interceptor {
+    private final String location;
+    private final String paramName;
+
+    private String apiKey;
+
+    public ApiKeyAuth(String location, String paramName) {
+        this.location = location;
+        this.paramName = paramName;
+    }
+
+    public String getLocation() {
+        return location;
+    }
+
+    public String getParamName() {
+        return paramName;
+    }
+
+    public String getApiKey() {
+        return apiKey;
+    }
+
+    public void setApiKey(String apiKey) {
+        this.apiKey = apiKey;
+    }
+
+    @Override
+    public Response intercept(Chain chain) throws IOException {
+        String paramValue;
+        Request request = chain.request();
+
+        if (location == "query") {
+            String newQuery = request.url().uri().getQuery();
+            paramValue = paramName + "=" + apiKey;
+            if (newQuery == null) {
+                newQuery = paramValue;
+            } else {
+                newQuery += "&" + paramValue;
+            }
+
+            URI newUri;
+            try {
+                newUri = new URI(request.url().uri().getScheme(), request.url().uri().getAuthority(),
+                    request.url().uri().getPath(), newQuery, request.url().uri().getFragment());
+            } catch (URISyntaxException e) {
+                throw new IOException(e);
+            }
+
+            request = request.newBuilder().url(newUri.toURL()).build();
+        } else if (location == "header") {
+            request = request.newBuilder()
+                    .addHeader(paramName, apiKey)
+                    .build();
+        }
+        return chain.proceed(request);
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/me/szaki/xkcd/xkcdbrowser/network/auth/HttpBasicAuth.java b/app/src/main/java/me/szaki/xkcd/xkcdbrowser/network/auth/HttpBasicAuth.java
new file mode 100644
index 0000000000000000000000000000000000000000..66093aa57601943ec133091edcb3bb5f0c610174
--- /dev/null
+++ b/app/src/main/java/me/szaki/xkcd/xkcdbrowser/network/auth/HttpBasicAuth.java
@@ -0,0 +1,50 @@
+package me.szaki.xkcd.xkcdbrowser.network.auth;
+
+import java.io.IOException;
+
+import okhttp3.Interceptor;
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.Response;
+import okhttp3.Credentials;
+
+public class HttpBasicAuth implements Interceptor {
+
+    private String username;
+    private String password;
+    
+    public String getUsername() {
+        return username;
+    }
+
+    public void setUsername(String username) {
+        this.username = username;
+    }
+
+    public String getPassword() {
+        return password;
+    }
+
+    public void setPassword(String password) {
+        this.password = password;
+    }
+
+    public void setCredentials(String username, String password) {
+        this.username = username;
+        this.password = password;
+    }
+
+    @Override
+    public Response intercept(Chain chain) throws IOException {
+        Request request = chain.request();
+
+        // If the request already have an authorization (eg. Basic auth), do nothing
+        if (request.header("Authorization") == null) {
+            String credentials = Credentials.basic(username, password);
+            request = request.newBuilder()
+                    .addHeader("Authorization", credentials)
+                    .build();
+        }
+        return chain.proceed(request);
+    }
+}
diff --git a/app/src/main/java/me/szaki/xkcd/xkcdbrowser/network/auth/OAuth.java b/app/src/main/java/me/szaki/xkcd/xkcdbrowser/network/auth/OAuth.java
new file mode 100644
index 0000000000000000000000000000000000000000..e4d4fe7a435eacf446b72874333b1ce577ae8407
--- /dev/null
+++ b/app/src/main/java/me/szaki/xkcd/xkcdbrowser/network/auth/OAuth.java
@@ -0,0 +1,161 @@
+package me.szaki.xkcd.xkcdbrowser.network.auth;
+
+import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED;
+
+import java.io.IOException;
+import java.util.Map;
+
+import org.apache.oltu.oauth2.client.OAuthClient;
+import org.apache.oltu.oauth2.client.request.OAuthBearerClientRequest;
+import org.apache.oltu.oauth2.client.request.OAuthClientRequest;
+import org.apache.oltu.oauth2.client.request.OAuthClientRequest.AuthenticationRequestBuilder;
+import org.apache.oltu.oauth2.client.request.OAuthClientRequest.TokenRequestBuilder;
+import org.apache.oltu.oauth2.client.response.OAuthJSONAccessTokenResponse;
+import org.apache.oltu.oauth2.common.exception.OAuthProblemException;
+import org.apache.oltu.oauth2.common.exception.OAuthSystemException;
+import org.apache.oltu.oauth2.common.message.types.GrantType;
+import org.apache.oltu.oauth2.common.token.BasicOAuthToken;
+
+import okhttp3.Interceptor;
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.Request.Builder;
+import okhttp3.Response;
+
+public class OAuth implements Interceptor {
+
+    public interface AccessTokenListener {
+        public void notify(BasicOAuthToken token);
+    }
+
+    private volatile String accessToken;
+    private OAuthClient oauthClient;
+
+    private TokenRequestBuilder tokenRequestBuilder;
+    private AuthenticationRequestBuilder authenticationRequestBuilder;
+
+    private AccessTokenListener accessTokenListener;
+
+    public OAuth( OkHttpClient client, TokenRequestBuilder requestBuilder ) {
+        this.oauthClient = new OAuthClient(new OAuthOkHttpClient(client));
+        this.tokenRequestBuilder = requestBuilder;
+    }
+
+    public OAuth(TokenRequestBuilder requestBuilder ) {
+        this(new OkHttpClient(), requestBuilder);
+    }
+
+    public OAuth(OAuthFlow flow, String authorizationUrl, String tokenUrl, String scopes) {
+        this(OAuthClientRequest.tokenLocation(tokenUrl).setScope(scopes));
+        setFlow(flow);
+        authenticationRequestBuilder = OAuthClientRequest.authorizationLocation(authorizationUrl);
+    }
+
+    public void setFlow(OAuthFlow flow) {
+        switch(flow) {
+        case accessCode:
+        case implicit:
+            tokenRequestBuilder.setGrantType(GrantType.AUTHORIZATION_CODE);
+            break;
+        case password:
+            tokenRequestBuilder.setGrantType(GrantType.PASSWORD);
+            break;
+        case application:
+            tokenRequestBuilder.setGrantType(GrantType.CLIENT_CREDENTIALS);
+            break;
+        default:
+            break;
+        }            
+    }
+
+    @Override
+    public Response intercept(Chain chain)
+            throws IOException {
+
+        Request request = chain.request();
+
+        // If the request already have an authorization (eg. Basic auth), do nothing
+        if (request.header("Authorization") != null) {
+            return chain.proceed(request);
+        }
+
+        // If first time, get the token
+        OAuthClientRequest oAuthRequest;
+        if (getAccessToken() == null) {
+            updateAccessToken(null);
+        }
+
+        // Build the request
+        Builder rb = request.newBuilder();
+
+        String requestAccessToken = new String(getAccessToken());
+        try {
+            oAuthRequest = new OAuthBearerClientRequest(request.url().toString())
+                .setAccessToken(requestAccessToken)
+                .buildHeaderMessage();
+        } catch (OAuthSystemException e) {
+            throw new IOException(e);
+        }
+
+        for ( Map.Entry<String, String> header : oAuthRequest.getHeaders().entrySet() ) {
+            rb.addHeader(header.getKey(), header.getValue());
+        }
+        rb.url( oAuthRequest.getLocationUri());
+
+        //Execute the request
+        Response response = chain.proceed(rb.build());
+
+        // 401 most likely indicates that access token has expired.
+        // Time to refresh and resend the request
+        if ( response.code() == HTTP_UNAUTHORIZED ) {
+            updateAccessToken(requestAccessToken);
+            return intercept( chain );
+        }
+        return response;    
+    }
+
+    public synchronized void updateAccessToken(String requestAccessToken) throws IOException {
+        if (getAccessToken() == null || getAccessToken().equals(requestAccessToken)) {    
+            try {
+                OAuthJSONAccessTokenResponse accessTokenResponse = oauthClient.accessToken(this.tokenRequestBuilder.buildBodyMessage());
+                setAccessToken(accessTokenResponse.getAccessToken());
+                if (accessTokenListener != null) {
+                    accessTokenListener.notify((BasicOAuthToken) accessTokenResponse.getOAuthToken());
+                }
+            } catch (OAuthSystemException e) {
+                throw new IOException(e);
+            } catch (OAuthProblemException e) {
+                throw new IOException(e);
+            }
+        }
+    }
+
+    public void registerAccessTokenListener(AccessTokenListener accessTokenListener) {
+        this.accessTokenListener = accessTokenListener;
+    }
+
+    public synchronized String getAccessToken() {
+        return accessToken;
+    }
+
+    public synchronized void setAccessToken(String accessToken) {
+        this.accessToken = accessToken;
+    }
+
+    public TokenRequestBuilder getTokenRequestBuilder() {
+        return tokenRequestBuilder;
+    }
+
+    public void setTokenRequestBuilder(TokenRequestBuilder tokenRequestBuilder) {
+        this.tokenRequestBuilder = tokenRequestBuilder;
+    }
+
+    public AuthenticationRequestBuilder getAuthenticationRequestBuilder() {
+        return authenticationRequestBuilder;
+    }
+
+    public void setAuthenticationRequestBuilder(AuthenticationRequestBuilder authenticationRequestBuilder) {
+        this.authenticationRequestBuilder = authenticationRequestBuilder;
+    }
+
+}
diff --git a/app/src/main/java/me/szaki/xkcd/xkcdbrowser/network/auth/OAuthFlow.java b/app/src/main/java/me/szaki/xkcd/xkcdbrowser/network/auth/OAuthFlow.java
new file mode 100644
index 0000000000000000000000000000000000000000..c0e1a6cd874cc2d0187f5d3690b1edc3331b0962
--- /dev/null
+++ b/app/src/main/java/me/szaki/xkcd/xkcdbrowser/network/auth/OAuthFlow.java
@@ -0,0 +1,5 @@
+package me.szaki.xkcd.xkcdbrowser.network.auth;
+
+public enum OAuthFlow {
+    accessCode, implicit, password, application
+}
\ No newline at end of file
diff --git a/app/src/main/java/me/szaki/xkcd/xkcdbrowser/network/auth/OAuthOkHttpClient.java b/app/src/main/java/me/szaki/xkcd/xkcdbrowser/network/auth/OAuthOkHttpClient.java
new file mode 100644
index 0000000000000000000000000000000000000000..690aece4f3aa719e0d6503dfe4100591632538eb
--- /dev/null
+++ b/app/src/main/java/me/szaki/xkcd/xkcdbrowser/network/auth/OAuthOkHttpClient.java
@@ -0,0 +1,72 @@
+package me.szaki.xkcd.xkcdbrowser.network.auth;
+
+import java.io.IOException;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.apache.oltu.oauth2.client.HttpClient;
+import org.apache.oltu.oauth2.client.request.OAuthClientRequest;
+import org.apache.oltu.oauth2.client.response.OAuthClientResponse;
+import org.apache.oltu.oauth2.client.response.OAuthClientResponseFactory;
+import org.apache.oltu.oauth2.common.exception.OAuthProblemException;
+import org.apache.oltu.oauth2.common.exception.OAuthSystemException;
+
+
+import okhttp3.Interceptor;
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.Request.Builder;
+import okhttp3.Response;
+import okhttp3.MediaType;
+import okhttp3.RequestBody;
+
+
+public class OAuthOkHttpClient implements HttpClient {
+
+    private OkHttpClient client;
+
+    public OAuthOkHttpClient() {
+        this.client = new OkHttpClient();
+    }
+
+    public OAuthOkHttpClient(OkHttpClient client) {
+        this.client = client;
+    }
+
+    public <T extends OAuthClientResponse> T execute(OAuthClientRequest request, Map<String, String> headers,
+            String requestMethod, Class<T> responseClass)
+                    throws OAuthSystemException, OAuthProblemException {
+
+        MediaType mediaType = MediaType.parse("application/json");
+        Request.Builder requestBuilder = new Request.Builder().url(request.getLocationUri());
+
+        if(headers != null) {
+            for (Entry<String, String> entry : headers.entrySet()) {
+                if (entry.getKey().equalsIgnoreCase("Content-Type")) {
+                    mediaType = MediaType.parse(entry.getValue());
+                } else {
+                    requestBuilder.addHeader(entry.getKey(), entry.getValue());
+                }
+            }
+        }
+
+        RequestBody body = request.getBody() != null ? RequestBody.create(mediaType, request.getBody()) : null;
+        requestBuilder.method(requestMethod, body);
+
+        try {
+            Response response = client.newCall(requestBuilder.build()).execute();
+            return OAuthClientResponseFactory.createCustomResponse(
+                    response.body().string(), 
+                    response.body().contentType().toString(),
+                    response.code(),
+                    responseClass);
+        } catch (IOException e) {
+            throw new OAuthSystemException(e);
+        }
+    }
+
+    public void shutdown() {
+        // Nothing to do here
+    }
+
+}
diff --git a/app/src/main/java/me/szaki/xkcd/xkcdbrowser/network/model/Comic.java b/app/src/main/java/me/szaki/xkcd/xkcdbrowser/network/model/Comic.java
new file mode 100644
index 0000000000000000000000000000000000000000..abe341b2261ab5c27fa4fe2569d828b2ce563e86
--- /dev/null
+++ b/app/src/main/java/me/szaki/xkcd/xkcdbrowser/network/model/Comic.java
@@ -0,0 +1,229 @@
+package me.szaki.xkcd.xkcdbrowser.network.model;
+
+import java.util.Objects;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+
+import com.google.gson.annotations.SerializedName;
+
+
+
+
+@ApiModel(description = "")
+public class Comic   {
+  
+  @SerializedName("month")
+  private String month = null;
+  
+  @SerializedName("num")
+  private Integer num = null;
+  
+  @SerializedName("link")
+  private String link = null;
+  
+  @SerializedName("year")
+  private String year = null;
+  
+  @SerializedName("news")
+  private String news = null;
+  
+  @SerializedName("safe_title")
+  private String safeTitle = null;
+  
+  @SerializedName("transcript")
+  private String transcript = null;
+  
+  @SerializedName("alt")
+  private String alt = null;
+  
+  @SerializedName("img")
+  private String img = null;
+  
+  @SerializedName("title")
+  private String title = null;
+  
+  @SerializedName("day")
+  private String day = null;
+  
+
+  
+  /**
+   **/
+  @ApiModelProperty(value = "")
+  public String getMonth() {
+    return month;
+  }
+  public void setMonth(String month) {
+    this.month = month;
+  }
+
+  
+  /**
+   **/
+  @ApiModelProperty(value = "")
+  public Integer getNum() {
+    return num;
+  }
+  public void setNum(Integer num) {
+    this.num = num;
+  }
+
+  
+  /**
+   **/
+  @ApiModelProperty(value = "")
+  public String getLink() {
+    return link;
+  }
+  public void setLink(String link) {
+    this.link = link;
+  }
+
+  
+  /**
+   **/
+  @ApiModelProperty(value = "")
+  public String getYear() {
+    return year;
+  }
+  public void setYear(String year) {
+    this.year = year;
+  }
+
+  
+  /**
+   **/
+  @ApiModelProperty(value = "")
+  public String getNews() {
+    return news;
+  }
+  public void setNews(String news) {
+    this.news = news;
+  }
+
+  
+  /**
+   **/
+  @ApiModelProperty(value = "")
+  public String getSafeTitle() {
+    return safeTitle;
+  }
+  public void setSafeTitle(String safeTitle) {
+    this.safeTitle = safeTitle;
+  }
+
+  
+  /**
+   **/
+  @ApiModelProperty(value = "")
+  public String getTranscript() {
+    return transcript;
+  }
+  public void setTranscript(String transcript) {
+    this.transcript = transcript;
+  }
+
+  
+  /**
+   **/
+  @ApiModelProperty(value = "")
+  public String getAlt() {
+    return alt;
+  }
+  public void setAlt(String alt) {
+    this.alt = alt;
+  }
+
+  
+  /**
+   **/
+  @ApiModelProperty(value = "")
+  public String getImg() {
+    return img;
+  }
+  public void setImg(String img) {
+    this.img = img;
+  }
+
+  
+  /**
+   **/
+  @ApiModelProperty(value = "")
+  public String getTitle() {
+    return title;
+  }
+  public void setTitle(String title) {
+    this.title = title;
+  }
+
+  
+  /**
+   **/
+  @ApiModelProperty(value = "")
+  public String getDay() {
+    return day;
+  }
+  public void setDay(String day) {
+    this.day = day;
+  }
+
+  
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+    Comic comic = (Comic) o;
+    return Objects.equals(month, comic.month) &&
+        Objects.equals(num, comic.num) &&
+        Objects.equals(link, comic.link) &&
+        Objects.equals(year, comic.year) &&
+        Objects.equals(news, comic.news) &&
+        Objects.equals(safeTitle, comic.safeTitle) &&
+        Objects.equals(transcript, comic.transcript) &&
+        Objects.equals(alt, comic.alt) &&
+        Objects.equals(img, comic.img) &&
+        Objects.equals(title, comic.title) &&
+        Objects.equals(day, comic.day);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(month, num, link, year, news, safeTitle, transcript, alt, img, title, day);
+  }
+
+  @Override
+  public String toString() {
+    StringBuilder sb = new StringBuilder();
+    sb.append("class Comic {\n");
+    
+    sb.append("    month: ").append(toIndentedString(month)).append("\n");
+    sb.append("    num: ").append(toIndentedString(num)).append("\n");
+    sb.append("    link: ").append(toIndentedString(link)).append("\n");
+    sb.append("    year: ").append(toIndentedString(year)).append("\n");
+    sb.append("    news: ").append(toIndentedString(news)).append("\n");
+    sb.append("    safeTitle: ").append(toIndentedString(safeTitle)).append("\n");
+    sb.append("    transcript: ").append(toIndentedString(transcript)).append("\n");
+    sb.append("    alt: ").append(toIndentedString(alt)).append("\n");
+    sb.append("    img: ").append(toIndentedString(img)).append("\n");
+    sb.append("    title: ").append(toIndentedString(title)).append("\n");
+    sb.append("    day: ").append(toIndentedString(day)).append("\n");
+    sb.append("}");
+    return sb.toString();
+  }
+
+  /**
+   * Convert the given object to string with each line indented by 4 spaces
+   * (except the first line).
+   */
+  private String toIndentedString(Object o) {
+    if (o == null) {
+      return "null";
+    }
+    return o.toString().replace("\n", "\n    ");
+  }
+}
diff --git a/app/src/main/java/me/szaki/xkcd/xkcdbrowser/ui/detail/DetailPresenter.java b/app/src/main/java/me/szaki/xkcd/xkcdbrowser/ui/detail/DetailPresenter.java
index 130f5a50d4e8ca7af4561320c5b6f7d70be59a63..19377a01936a6cd73755028d31cb615f6d3c8253 100644
--- a/app/src/main/java/me/szaki/xkcd/xkcdbrowser/ui/detail/DetailPresenter.java
+++ b/app/src/main/java/me/szaki/xkcd/xkcdbrowser/ui/detail/DetailPresenter.java
@@ -3,6 +3,7 @@ package me.szaki.xkcd.xkcdbrowser.ui.detail;
 
 import javax.inject.Inject;
 
+import me.szaki.xkcd.xkcdbrowser.XKCDBrowserApplication;
 import me.szaki.xkcd.xkcdbrowser.interactor.comics.ComicsInteractor;
 import me.szaki.xkcd.xkcdbrowser.ui.Presenter;
 
@@ -10,6 +11,10 @@ public class DetailPresenter extends Presenter<DetailScreen> {
     @Inject
     ComicsInteractor comicsInteractor;
 
+    public DetailPresenter () {
+        XKCDBrowserApplication.injector.inject(this);
+    }
+
     @Override
     public void attachScreen(DetailScreen screen) {
         super.attachScreen(screen);
diff --git a/app/src/main/java/me/szaki/xkcd/xkcdbrowser/ui/favorites/FavoritesPresenter.java b/app/src/main/java/me/szaki/xkcd/xkcdbrowser/ui/favorites/FavoritesPresenter.java
index 3aeda6ae2d58aff3d034d945a1646e73840b6351..55ab14b9bb8cd8f10b7f8bf6a99a0c23280d9721 100644
--- a/app/src/main/java/me/szaki/xkcd/xkcdbrowser/ui/favorites/FavoritesPresenter.java
+++ b/app/src/main/java/me/szaki/xkcd/xkcdbrowser/ui/favorites/FavoritesPresenter.java
@@ -3,6 +3,7 @@ package me.szaki.xkcd.xkcdbrowser.ui.favorites;
 
 import javax.inject.Inject;
 
+import me.szaki.xkcd.xkcdbrowser.XKCDBrowserApplication;
 import me.szaki.xkcd.xkcdbrowser.database.ComicsDatabase;
 import me.szaki.xkcd.xkcdbrowser.interactor.comics.ComicsInteractor;
 import me.szaki.xkcd.xkcdbrowser.ui.Presenter;
@@ -14,6 +15,10 @@ public class FavoritesPresenter extends Presenter<FavoritesScreen> {
     @Inject
     ComicsDatabase db;
 
+    public FavoritesPresenter () {
+        XKCDBrowserApplication.injector.inject(this);
+    }
+
     @Override
     public void attachScreen(FavoritesScreen screen) {
         super.attachScreen(screen);
diff --git a/app/src/main/java/me/szaki/xkcd/xkcdbrowser/ui/main/MainPresenter.java b/app/src/main/java/me/szaki/xkcd/xkcdbrowser/ui/main/MainPresenter.java
index b18988394bd36d314ec890f7a6acb6945db08cde..60beab22d796e881e38c34f187a5566c3411ebb2 100644
--- a/app/src/main/java/me/szaki/xkcd/xkcdbrowser/ui/main/MainPresenter.java
+++ b/app/src/main/java/me/szaki/xkcd/xkcdbrowser/ui/main/MainPresenter.java
@@ -4,6 +4,7 @@ package me.szaki.xkcd.xkcdbrowser.ui.main;
 import javax.inject.Inject;
 
 import me.szaki.xkcd.xkcdbrowser.database.ComicsDatabase;
+import me.szaki.xkcd.xkcdbrowser.XKCDBrowserApplication;
 import me.szaki.xkcd.xkcdbrowser.interactor.comics.ComicsInteractor;
 import me.szaki.xkcd.xkcdbrowser.ui.Presenter;
 
@@ -14,6 +15,10 @@ public class MainPresenter extends Presenter<MainScreen> {
     @Inject
     ComicsDatabase db;
 
+    public MainPresenter () {
+        XKCDBrowserApplication.injector.inject(this);
+    }
+
     @Override
     public void attachScreen(MainScreen screen) {
         super.attachScreen(screen);
diff --git a/swagger.yaml b/swagger.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..d7f89e101357f65fb58c89c2d0fe2ae36f88d835
--- /dev/null
+++ b/swagger.yaml
@@ -0,0 +1,73 @@
+swagger: "2.0"
+info:
+  description: "Android viewer for xkcd comics"
+  version: "1.0.0"
+  title: "XKCD Browser"
+  license:
+    name: "Creative Common"
+    url: "https://creativecommons.org"
+host: "xkcd.com"
+basePath: "/"
+tags:
+- name: "comic"
+  description: "A comic strip"
+  externalDocs:
+    description: "Find out more"
+    url: "https://xkcd.com/json.html"
+schemes:
+- "https"
+paths:
+  /{id}/info.0.json:
+    get:
+      tags:
+      - "comic"
+      summary: "Get a comic"
+      description: ""
+      operationId: "getComic"
+      consumes:
+      - "application/json"
+      produces:
+      - "application/json"
+      parameters:
+      - in: "path"
+        name: "id"
+        description: "Number of the comic"
+        required: true
+        type: "integer"
+        format: "int64"
+      responses:
+        200:
+          description: "successful operation"
+          schema:
+            $ref: "#/definitions/Comic"
+
+definitions:
+  Comic:
+    type: "object"
+    properties:
+      month:
+        type: "string"
+      num:
+        type: "integer"
+        format: "int32"
+      link:
+        type: "string"
+      year:
+        type: "string"
+      news:
+        type: "string"
+      safe_title:
+        type: "string"
+      transcript:
+        type: "string"
+      alt:
+        type: "string"
+      img:
+        type: "string"
+      title:
+        type: "string"
+      day:
+        type: "string"
+externalDocs:
+  description: "Find out more about Swagger"
+  url: "http://swagger.io"
\ No newline at end of file