workQueue;
26 | private final ThreadPoolExecutor threadPoolExecutor;
27 | private final ThreadFactory threadFactory;
28 |
29 | @Inject
30 | public JobExecutor() {
31 | this.workQueue = new LinkedBlockingQueue<>();
32 | this.threadFactory = new JobThreadFactory();
33 | this.threadPoolExecutor = new ThreadPoolExecutor(NUMBER_OF_CORES, NUMBER_OF_CORES,
34 | KEEP_ALIVE_TIME, KEEP_ALIVE_TIME_UNIT, this.workQueue, this.threadFactory);
35 | }
36 |
37 | /**
38 | * {@inheritDoc}
39 | *
40 | * @param runnable The class that implements {@link Runnable} interface.
41 | */
42 | @Override
43 | public void execute(Runnable runnable) {
44 | if (runnable == null) {
45 | throw new IllegalArgumentException("Runnable to execute cannot be null");
46 | }
47 | this.threadPoolExecutor.execute(runnable);
48 | }
49 |
50 | private static class JobThreadFactory implements ThreadFactory {
51 |
52 | private static final String THREAD_NAME = "android_";
53 | private int counter = 0;
54 |
55 | @Override
56 | public Thread newThread(@NonNull Runnable runnable) {
57 | return new Thread(runnable, THREAD_NAME + counter);
58 | }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jcminarro/authexample/internal/interactor/ReferenceRetainerDecorator.java:
--------------------------------------------------------------------------------
1 | package com.jcminarro.authexample.internal.interactor;
2 |
3 | import java.util.HashSet;
4 | import java.util.Set;
5 |
6 | import javax.inject.Inject;
7 |
8 | /**
9 | * This class is in charge of holding references to all the interactor callbacks during the lifetime of its holder,
10 | * primarily, the presenter.
11 | *
12 | * This is done in order to keep anonymous classes referenced either during the interactor execution lifetime or
13 | * the presenter lifetime, whichever lives less.
14 | */
15 | public class ReferenceRetainerDecorator implements ExecutorCallbackDecorator {
16 |
17 | private final Set> callbacks = new HashSet<>();
18 |
19 | @Inject
20 | public ReferenceRetainerDecorator() {
21 | }
22 |
23 | public InteractorExecutor.Callback decorate(InteractorExecutor.Callback callback) {
24 | CallbackDecorator decorator = new CallbackDecorator<>(
25 | callbacks,
26 | callback
27 | );
28 | callbacks.add(decorator);
29 | return decorator;
30 | }
31 |
32 | private static class CallbackDecorator implements InteractorExecutor.Callback {
33 |
34 | private final Set> callbacks;
35 | private final InteractorExecutor.Callback retainedCallback;
36 |
37 | private CallbackDecorator(
38 | Set> callbacks,
39 | InteractorExecutor.Callback retainedCallback) {
40 | this.callbacks = callbacks;
41 | this.retainedCallback = retainedCallback;
42 | }
43 |
44 | @Override
45 | public void onSuccess(O output) {
46 | retainedCallback.onSuccess(output);
47 | callbacks.remove(retainedCallback);
48 | }
49 |
50 | @Override
51 | public void onError(E exception) {
52 | retainedCallback.onError(exception);
53 | callbacks.remove(retainedCallback);
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jcminarro/authexample/internal/interactor/ThreadExecutor.java:
--------------------------------------------------------------------------------
1 | package com.jcminarro.authexample.internal.interactor;
2 |
3 | public interface ThreadExecutor {
4 |
5 | void execute(final Runnable runnable);
6 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/jcminarro/authexample/internal/localdatasource/SessionDatasource.java:
--------------------------------------------------------------------------------
1 | package com.jcminarro.authexample.internal.localdatasource;
2 |
3 | import com.jcminarro.authexample.internal.network.OAuth;
4 |
5 | import org.jetbrains.annotations.NotNull;
6 | import org.jetbrains.annotations.Nullable;
7 |
8 | public interface SessionDatasource {
9 |
10 | void storeOAuthSession(@NotNull OAuth auth);
11 |
12 | @Nullable
13 | OAuth getOAuthSession();
14 | }
15 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jcminarro/authexample/internal/localdatasource/SharedPreferenceSesssionDatasource.java:
--------------------------------------------------------------------------------
1 | package com.jcminarro.authexample.internal.localdatasource;
2 |
3 | import android.content.SharedPreferences;
4 |
5 | import com.jcminarro.authexample.internal.network.AccessTokenProvider;
6 | import com.jcminarro.authexample.internal.network.OAuth;
7 |
8 | import org.jetbrains.annotations.NotNull;
9 | import org.jetbrains.annotations.Nullable;
10 |
11 | public class SharedPreferenceSesssionDatasource implements SessionDatasource, AccessTokenProvider {
12 |
13 | private static final String ACCESS_TOKEN_KEY = "accessToken";
14 | private static final String REFRESH_TOKEN_KEY = "refreshToken";
15 | private final SharedPreferences sharedPreferences;
16 |
17 | public SharedPreferenceSesssionDatasource(SharedPreferences sharedPreferences) {
18 | this.sharedPreferences = sharedPreferences;
19 | }
20 |
21 | @Override
22 | public void storeOAuthSession(@NotNull OAuth auth) {
23 | sharedPreferences.edit()
24 | .putString(ACCESS_TOKEN_KEY, auth.getAccessToken())
25 | .putString(REFRESH_TOKEN_KEY, auth.getRefreshToken())
26 | .apply();
27 | }
28 |
29 | @Nullable
30 | @Override
31 | public OAuth getOAuthSession() {
32 | String accessToken = sharedPreferences.getString(ACCESS_TOKEN_KEY, null);
33 | String refreshToken = sharedPreferences.getString(REFRESH_TOKEN_KEY, null);
34 | if (accessToken != null && refreshToken != null) {
35 | return new OAuth(accessToken, refreshToken);
36 | } else {
37 | return null;
38 | }
39 | }
40 |
41 | @Override
42 | public String getAccessToken() {
43 | return sharedPreferences.getString(ACCESS_TOKEN_KEY, "");
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jcminarro/authexample/internal/model/Models.kt:
--------------------------------------------------------------------------------
1 | package com.jcminarro.authexample.internal.model
2 |
3 | data class Quote(val author: String, val message: String)
--------------------------------------------------------------------------------
/app/src/main/java/com/jcminarro/authexample/internal/navigator/Navigator.kt:
--------------------------------------------------------------------------------
1 | package com.jcminarro.authexample.internal.navigator
2 |
3 | import android.content.Context
4 | import android.content.Intent
5 | import android.support.v7.app.AppCompatActivity
6 | import com.jcminarro.authexample.login.LoginActivity
7 | import com.jcminarro.authexample.quote.QuoteActivity
8 | import java.lang.ref.WeakReference
9 | import javax.inject.Inject
10 |
11 | class Navigator @Inject constructor(var appCompactActivity: AppCompatActivity) {
12 | val appCompactActivityWeakReference = WeakReference(appCompactActivity)
13 |
14 | fun navigateToLogin() {
15 | startActivity { LoginActivity.getLaunchIntent(it) }
16 | }
17 |
18 | fun navigateToQuote() {
19 | startActivity { QuoteActivity.getLaunchIntent(it) }
20 | }
21 |
22 | private fun startActivity(getIntentFunction: (context: Context) -> Intent) =
23 | appCompactActivityWeakReference.get()?.let {
24 | it.startActivity(getIntentFunction(it))
25 | }
26 | }
27 |
28 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jcminarro/authexample/internal/network/APIIOException.kt:
--------------------------------------------------------------------------------
1 | package com.jcminarro.authexample.internal.network
2 |
3 | import java.io.IOException
4 |
5 | class APIIOException(response: okhttp3.Response) : IOException()
6 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jcminarro/authexample/internal/network/AccessTokenProvider.java:
--------------------------------------------------------------------------------
1 | package com.jcminarro.authexample.internal.network;
2 |
3 | public interface AccessTokenProvider {
4 |
5 | String getAccessToken();
6 | }
7 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jcminarro/authexample/internal/network/ApiClient.java:
--------------------------------------------------------------------------------
1 | package com.jcminarro.authexample.internal.network;
2 |
3 | import java.io.IOException;
4 |
5 | import retrofit2.Call;
6 | import retrofit2.Response;
7 |
8 | public class ApiClient {
9 |
10 | protected final T endpoint;
11 |
12 | public ApiClient(T endpoint) {
13 | this.endpoint = endpoint;
14 | }
15 |
16 | protected U evaluateCall(Call call) throws IOException {
17 | Response response = call.execute();
18 | if (!response.isSuccessful()) {
19 | throw new APIIOException(response.raw());
20 | }
21 | return response.body();
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jcminarro/authexample/internal/network/EndpointFactory.java:
--------------------------------------------------------------------------------
1 | package com.jcminarro.authexample.internal.network;
2 |
3 | import com.google.gson.Gson;
4 | import com.google.gson.GsonBuilder;
5 | import com.jcminarro.authexample.UtilsKt;
6 | import com.jcminarro.authexample.internal.network.authorizator.AuthorizatedApi;
7 | import com.jcminarro.authexample.internal.network.authorizator.AuthorizatedApiInterceptor;
8 | import com.jcminarro.authexample.internal.network.authorizator.UnauthorizatedApiInterceptor;
9 | import com.jcminarro.authexample.internal.network.authorizator.UnauthorizatedApi;
10 | import com.jcminarro.authexample.internal.network.reauthorizate.ReauthorizatedApi;
11 | import com.jcminarro.authexample.internal.network.reauthorizate.ReauthorizatedApiInterceptor;
12 | import com.moczul.ok2curl.CurlInterceptor;
13 | import com.moczul.ok2curl.logger.Loggable;
14 |
15 | import java.util.concurrent.TimeUnit;
16 |
17 | import okhttp3.ConnectionPool;
18 | import okhttp3.OkHttpClient;
19 | import okhttp3.logging.HttpLoggingInterceptor;
20 | import retrofit2.Converter;
21 | import retrofit2.Retrofit;
22 | import retrofit2.converter.gson.GsonConverterFactory;
23 |
24 | public class EndpointFactory {
25 |
26 | private static final int CONNECT_TIMEOUT_SECONDS = 60;
27 | private static final int READ_TIMEOUT_SECONDS = 60;
28 | private static final ConnectionPool CONNECTION_POOL = new ConnectionPool();
29 |
30 | private final String apiHost;
31 | private final AuthorizatedApiInterceptor authorizatedApiInterceptor;
32 | private final UnauthorizatedApiInterceptor unauthorizatedApiInterceptor;
33 | private final ReauthorizatedApiInterceptor reauthorizatedApiInterceptor;
34 |
35 | private EndpointFactory(String apiHost,
36 | AuthorizatedApiInterceptor authorizatedApiInterceptor,
37 | UnauthorizatedApiInterceptor unauthorizatedApiInterceptor,
38 | ReauthorizatedApiInterceptor reauthorizatedApiInterceptor) {
39 | this.apiHost = apiHost;
40 | this.authorizatedApiInterceptor = authorizatedApiInterceptor;
41 | this.unauthorizatedApiInterceptor = unauthorizatedApiInterceptor;
42 | this.reauthorizatedApiInterceptor = reauthorizatedApiInterceptor;
43 | }
44 |
45 | public T create(Class api) {
46 | return new Retrofit.Builder()
47 | .baseUrl(apiHost)
48 | .client(getDefaultHttpClient(api))
49 | .addConverterFactory(getConverterFactory())
50 | .build()
51 | .create(api);
52 | }
53 |
54 | private Converter.Factory getConverterFactory() {
55 | Gson gson = new GsonBuilder().create();
56 | return GsonConverterFactory.create(gson);
57 | }
58 |
59 | private OkHttpClient getDefaultHttpClient(Class api) {
60 | OkHttpClient.Builder builder = new OkHttpClient.Builder()
61 | .connectionPool(CONNECTION_POOL)
62 | .connectTimeout(CONNECT_TIMEOUT_SECONDS, TimeUnit.SECONDS)
63 | .readTimeout(READ_TIMEOUT_SECONDS, TimeUnit.SECONDS);
64 | addLoggingInterceptor(builder);
65 | addReauthorizatorInterceptorIfNeeded(api, builder);
66 | addAuthorizatorInterceptorIfNeeded(api, builder);
67 | addUnauthorizatorInterceptorIfNeeded(api, builder);
68 | return builder.build();
69 | }
70 |
71 | private void addLoggingInterceptor(OkHttpClient.Builder builder) {
72 | builder.addInterceptor(
73 | new HttpLoggingInterceptor(
74 | new HttpLoggingInterceptor.Logger() {
75 | @Override
76 | public void log(String message) {
77 | UtilsKt.log(message);
78 | }
79 | }).setLevel(HttpLoggingInterceptor.Level.BODY));
80 | builder.addNetworkInterceptor(new CurlInterceptor(new Loggable() {
81 | @Override
82 | public void log(String message) {
83 | UtilsKt.log("Curl", message);
84 | }
85 | }));
86 | }
87 |
88 | private void addAuthorizatorInterceptorIfNeeded(Class api, OkHttpClient.Builder builder) {
89 | if (api.isAnnotationPresent(AuthorizatedApi.class)) {
90 | if (authorizatedApiInterceptor == null) {
91 | throw new IllegalStateException("To build a " + api.getName() + " that is annotated with " +
92 | AuthorizatedApi.class.getName() + " you need to add a " +
93 | AuthorizatedApiInterceptor.class.getName() + " to the " + EndpointFactory.class.getName());
94 | }
95 | builder.addInterceptor(authorizatedApiInterceptor);
96 | }
97 | }
98 |
99 | private void addUnauthorizatorInterceptorIfNeeded(Class api, OkHttpClient.Builder builder) {
100 | if (api.isAnnotationPresent(UnauthorizatedApi.class)) {
101 | if (unauthorizatedApiInterceptor == null) {
102 | throw new IllegalStateException("To build a " + api.getName() + " that is annotated with " +
103 | UnauthorizatedApi.class.getName() + " you need to add a " +
104 | UnauthorizatedApiInterceptor.class.getName() + " to the " + EndpointFactory.class.getName());
105 | }
106 | builder.addInterceptor(unauthorizatedApiInterceptor);
107 | }
108 | }
109 |
110 | private void addReauthorizatorInterceptorIfNeeded(Class api, OkHttpClient.Builder builder) {
111 | if (api.isAnnotationPresent(ReauthorizatedApi.class)) {
112 | if (reauthorizatedApiInterceptor == null) {
113 | throw new IllegalStateException("To build a " + api.getName() + " that is annotated with " +
114 | ReauthorizatedApi.class.getName() + " you need to add a " +
115 | ReauthorizatedApiInterceptor.class.getName() + " to the " + EndpointFactory.class.getName());
116 | }
117 | builder.addInterceptor(reauthorizatedApiInterceptor);
118 | }
119 | }
120 |
121 | public static class Builder {
122 | private String apiHost;
123 | private AuthorizatedApiInterceptor authorizatedApiInterceptor;
124 | private UnauthorizatedApiInterceptor unauthorizatedApiInterceptor;
125 | private ReauthorizatedApiInterceptor reauthorizatedApiInterceptor;
126 |
127 | public Builder(String apiHost) {
128 | this.apiHost = apiHost;
129 | }
130 |
131 | public Builder withAuthorizatedApiInterceptor(AuthorizatedApiInterceptor authorizatedApiInterceptor) {
132 | this.authorizatedApiInterceptor = authorizatedApiInterceptor;
133 | return this;
134 | }
135 |
136 | public Builder withUnaouthorizatedApiInterceptor(UnauthorizatedApiInterceptor unauthorizatedApiInterceptor) {
137 | this.unauthorizatedApiInterceptor = unauthorizatedApiInterceptor;
138 | return this;
139 | }
140 |
141 | public Builder withReauthorizatedApiInterceptor(ReauthorizatedApiInterceptor reauthorizatedApiInterceptor) {
142 | this.reauthorizatedApiInterceptor = reauthorizatedApiInterceptor;
143 | return this;
144 | }
145 |
146 | public EndpointFactory build() {
147 | return new EndpointFactory(
148 | apiHost,
149 | authorizatedApiInterceptor,
150 | unauthorizatedApiInterceptor,
151 | reauthorizatedApiInterceptor);
152 | }
153 | }
154 | }
155 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jcminarro/authexample/internal/network/OAuth.kt:
--------------------------------------------------------------------------------
1 | package com.jcminarro.authexample.internal.network
2 |
3 | data class OAuth(val accessToken: String, val refreshToken: String)
4 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jcminarro/authexample/internal/network/SessionReauthorizer.java:
--------------------------------------------------------------------------------
1 | package com.jcminarro.authexample.internal.network;
2 |
3 | import com.jcminarro.authexample.internal.network.reauthorizate.Reauthorizer;
4 | import com.jcminarro.authexample.internal.repository.SessionRepository;
5 |
6 | import javax.inject.Inject;
7 |
8 | public class SessionReauthorizer implements Reauthorizer {
9 |
10 | private final SessionRepository sessionRepository;
11 |
12 | @Inject
13 | public SessionReauthorizer(SessionRepository sessionRepository) {
14 | this.sessionRepository = sessionRepository;
15 | }
16 |
17 | @Override
18 | public void reauthorize() {
19 | sessionRepository.refreshSession();
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jcminarro/authexample/internal/network/authorizator/AuthorizatedApi.java:
--------------------------------------------------------------------------------
1 | package com.jcminarro.authexample.internal.network.authorizator;
2 |
3 | import java.lang.annotation.ElementType;
4 | import java.lang.annotation.Retention;
5 | import java.lang.annotation.RetentionPolicy;
6 | import java.lang.annotation.Target;
7 |
8 | @Retention(RetentionPolicy.RUNTIME)
9 | @Target(ElementType.TYPE)
10 | public @interface AuthorizatedApi {
11 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/jcminarro/authexample/internal/network/authorizator/AuthorizatedApiInterceptor.java:
--------------------------------------------------------------------------------
1 | package com.jcminarro.authexample.internal.network.authorizator;
2 |
3 | import android.support.annotation.NonNull;
4 |
5 | import com.jcminarro.authexample.internal.network.AccessTokenProvider;
6 |
7 | import java.io.IOException;
8 |
9 | import javax.inject.Inject;
10 |
11 | import okhttp3.Interceptor;
12 | import okhttp3.Request;
13 | import okhttp3.Response;
14 |
15 | public class AuthorizatedApiInterceptor implements Interceptor {
16 |
17 | private static final String HEADER_AUTH_KEY = "X-access-token";
18 |
19 | private final AccessTokenProvider accessTokenProvider;
20 |
21 | @Inject
22 | public AuthorizatedApiInterceptor(AccessTokenProvider accessTokenProvider) {
23 | this.accessTokenProvider = accessTokenProvider;
24 | }
25 |
26 | @Override
27 | public Response intercept(@NonNull Chain chain) throws IOException {
28 | return chain.proceed(addAuthHeaderToken(chain.request()));
29 | }
30 |
31 | private Request addAuthHeaderToken(Request request) {
32 | return request
33 | .newBuilder()
34 | .addHeader(HEADER_AUTH_KEY, accessTokenProvider.getAccessToken())
35 | .build();
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jcminarro/authexample/internal/network/authorizator/UnauthorizatedApi.java:
--------------------------------------------------------------------------------
1 | package com.jcminarro.authexample.internal.network.authorizator;
2 |
3 | import java.lang.annotation.ElementType;
4 | import java.lang.annotation.Retention;
5 | import java.lang.annotation.RetentionPolicy;
6 | import java.lang.annotation.Target;
7 |
8 | @Retention(RetentionPolicy.RUNTIME)
9 | @Target(ElementType.TYPE)
10 | public @interface UnauthorizatedApi {
11 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/jcminarro/authexample/internal/network/authorizator/UnauthorizatedApiInterceptor.java:
--------------------------------------------------------------------------------
1 | package com.jcminarro.authexample.internal.network.authorizator;
2 |
3 | import android.support.annotation.NonNull;
4 |
5 | import java.io.IOException;
6 |
7 | import javax.inject.Inject;
8 |
9 | import okhttp3.Interceptor;
10 | import okhttp3.Response;
11 |
12 | public class UnauthorizatedApiInterceptor implements Interceptor {
13 |
14 | @Inject
15 | public UnauthorizatedApiInterceptor() { }
16 |
17 | @Override
18 | public Response intercept(@NonNull Chain chain) throws IOException {
19 | return chain.proceed(chain.request());
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jcminarro/authexample/internal/network/login/LoginApiClient.kt:
--------------------------------------------------------------------------------
1 | package com.jcminarro.authexample.internal.network.login
2 |
3 | import com.jcminarro.authexample.internal.network.APIIOException
4 | import com.jcminarro.authexample.internal.network.ApiClient
5 | import com.jcminarro.authexample.internal.network.OAuth
6 | import javax.inject.Inject
7 |
8 | class LoginApiClient @Inject
9 | constructor(endpoint: LoginEndpoint) : ApiClient(endpoint) {
10 |
11 | @Throws(APIIOException::class)
12 | fun login(username: String, password: String): OAuth =
13 | map(evaluateCall(endpoint.login(LoginBody(username, password))))
14 | }
15 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jcminarro/authexample/internal/network/login/LoginBody.java:
--------------------------------------------------------------------------------
1 | package com.jcminarro.authexample.internal.network.login;
2 |
3 | import com.google.gson.annotations.SerializedName;
4 |
5 | public class LoginBody {
6 | private final @SerializedName("username") String username;
7 | private final @SerializedName("password") String password;
8 |
9 | public LoginBody(String username, String password) {
10 | this.username = username;
11 | this.password = password;
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jcminarro/authexample/internal/network/login/LoginEndpoint.java:
--------------------------------------------------------------------------------
1 | package com.jcminarro.authexample.internal.network.login;
2 |
3 | import com.jcminarro.authexample.internal.network.authorizator.UnauthorizatedApi;
4 |
5 | import retrofit2.Call;
6 | import retrofit2.http.Body;
7 | import retrofit2.http.POST;
8 |
9 | @UnauthorizatedApi
10 | public interface LoginEndpoint {
11 |
12 | @POST("/login")
13 | Call login(@Body LoginBody loginBody);
14 | }
15 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jcminarro/authexample/internal/network/login/LoginResponse.java:
--------------------------------------------------------------------------------
1 | package com.jcminarro.authexample.internal.network.login;
2 |
3 | import com.google.gson.annotations.SerializedName;
4 |
5 | public class LoginResponse {
6 | private final @SerializedName("accessToken") String accessToken;
7 | private final @SerializedName("refreshToken") String refreshToken;
8 |
9 | public LoginResponse(String accessToken, String refreshToken) {
10 | this.accessToken = accessToken;
11 | this.refreshToken = refreshToken;
12 | }
13 |
14 | public String getAccessToken() {
15 | return accessToken;
16 | }
17 |
18 | public String getRefreshToken() {
19 | return refreshToken;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jcminarro/authexample/internal/network/login/Mapper.kt:
--------------------------------------------------------------------------------
1 | package com.jcminarro.authexample.internal.network.login
2 |
3 | import com.jcminarro.authexample.internal.network.OAuth
4 |
5 | fun map(loginResponse: LoginResponse): OAuth = OAuth(loginResponse.accessToken, loginResponse.refreshToken)
--------------------------------------------------------------------------------
/app/src/main/java/com/jcminarro/authexample/internal/network/quote/Mapper.kt:
--------------------------------------------------------------------------------
1 | package com.jcminarro.authexample.internal.network.quote
2 |
3 | import com.jcminarro.authexample.internal.model.Quote
4 |
5 | fun map(quoteResponse: QuoteResponse) = Quote(quoteResponse.author, quoteResponse.message)
--------------------------------------------------------------------------------
/app/src/main/java/com/jcminarro/authexample/internal/network/quote/QuoteApiClient.kt:
--------------------------------------------------------------------------------
1 | package com.jcminarro.authexample.internal.network.quote
2 |
3 | import com.jcminarro.authexample.internal.model.Quote
4 | import com.jcminarro.authexample.internal.network.APIIOException
5 | import com.jcminarro.authexample.internal.network.ApiClient
6 | import javax.inject.Inject
7 |
8 | class QuoteApiClient @Inject
9 | constructor(endpoint: QuoteEndpoint) : ApiClient(endpoint) {
10 |
11 | @Throws(APIIOException::class)
12 | fun getRandomQuote(): Quote =
13 | map(evaluateCall(endpoint.randomQuote))
14 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/jcminarro/authexample/internal/network/quote/QuoteEndpoint.java:
--------------------------------------------------------------------------------
1 | package com.jcminarro.authexample.internal.network.quote;
2 |
3 | import com.jcminarro.authexample.internal.network.authorizator.AuthorizatedApi;
4 | import com.jcminarro.authexample.internal.network.reauthorizate.ReauthorizatedApi;
5 |
6 | import retrofit2.Call;
7 | import retrofit2.http.GET;
8 | import retrofit2.http.Headers;
9 |
10 | @AuthorizatedApi
11 | @ReauthorizatedApi
12 | public interface QuoteEndpoint {
13 |
14 | @Headers("Content-Type: application/json")
15 | @GET("/quote")
16 | Call getRandomQuote();
17 | }
18 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jcminarro/authexample/internal/network/quote/QuoteResponse.java:
--------------------------------------------------------------------------------
1 | package com.jcminarro.authexample.internal.network.quote;
2 |
3 | import com.google.gson.annotations.SerializedName;
4 |
5 | public class QuoteResponse {
6 | private final @SerializedName("author") String author;
7 | private final @SerializedName("message") String message;
8 |
9 | public QuoteResponse(String author, String message) {
10 | this.author = author;
11 | this.message = message;
12 | }
13 |
14 | public String getAuthor() {
15 | return author;
16 | }
17 |
18 | public String getMessage() {
19 | return message;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jcminarro/authexample/internal/network/reauthorizate/ReauthorizatedApi.java:
--------------------------------------------------------------------------------
1 | package com.jcminarro.authexample.internal.network.reauthorizate;
2 |
3 | import java.lang.annotation.ElementType;
4 | import java.lang.annotation.Retention;
5 | import java.lang.annotation.RetentionPolicy;
6 | import java.lang.annotation.Target;
7 |
8 | @Retention(RetentionPolicy.RUNTIME)
9 | @Target(ElementType.TYPE)
10 | public @interface ReauthorizatedApi {
11 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/jcminarro/authexample/internal/network/reauthorizate/ReauthorizatedApiInterceptor.java:
--------------------------------------------------------------------------------
1 | package com.jcminarro.authexample.internal.network.reauthorizate;
2 |
3 | import android.support.annotation.NonNull;
4 |
5 | import java.io.IOException;
6 |
7 | import javax.inject.Inject;
8 |
9 | import okhttp3.Interceptor;
10 | import okhttp3.Response;
11 |
12 | public class ReauthorizatedApiInterceptor implements Interceptor {
13 |
14 | private static final int UNAUTHORIZED_HTTP_CODE = 401;
15 | private final Reauthorizer reauthorizer;
16 |
17 | @Inject
18 | public ReauthorizatedApiInterceptor(Reauthorizer reauthorizer) {
19 | this.reauthorizer = reauthorizer;
20 | }
21 |
22 | @Override
23 | public Response intercept(@NonNull Chain chain) throws IOException {
24 | Response response = chain.proceed(chain.request());
25 | if (response.code() == UNAUTHORIZED_HTTP_CODE) {
26 | reauthorizer.reauthorize();
27 | response = chain.proceed(chain.request());
28 | }
29 | return response;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jcminarro/authexample/internal/network/reauthorizate/Reauthorizer.java:
--------------------------------------------------------------------------------
1 | package com.jcminarro.authexample.internal.network.reauthorizate;
2 |
3 | public interface Reauthorizer {
4 |
5 | void reauthorize();
6 | }
7 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jcminarro/authexample/internal/network/refresh/Mapper.kt:
--------------------------------------------------------------------------------
1 | package com.jcminarro.authexample.internal.network.refresh
2 |
3 | import com.jcminarro.authexample.internal.network.OAuth
4 |
5 | fun map(refreshResponse: RefreshResponse): OAuth = OAuth(refreshResponse.accessToken, refreshResponse.refreshToken)
--------------------------------------------------------------------------------
/app/src/main/java/com/jcminarro/authexample/internal/network/refresh/RefreshApiClient.kt:
--------------------------------------------------------------------------------
1 | package com.jcminarro.authexample.internal.network.refresh
2 |
3 | import com.jcminarro.authexample.internal.network.APIIOException
4 | import com.jcminarro.authexample.internal.network.ApiClient
5 | import com.jcminarro.authexample.internal.network.OAuth
6 | import javax.inject.Inject
7 |
8 | class RefreshApiClient @Inject
9 | constructor(endpoint: RefreshEndpoint) : ApiClient(endpoint) {
10 |
11 | @Throws(APIIOException::class)
12 | fun refresh(refreshToken: String): OAuth =
13 | map(evaluateCall(endpoint.refreshTokens(RefreshBody(refreshToken))))
14 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/jcminarro/authexample/internal/network/refresh/RefreshBody.java:
--------------------------------------------------------------------------------
1 | package com.jcminarro.authexample.internal.network.refresh;
2 |
3 | import com.google.gson.annotations.SerializedName;
4 |
5 | public class RefreshBody {
6 | private final @SerializedName("refreshToken") String refreshToken;
7 |
8 | public RefreshBody(String refreshToken) {
9 | this.refreshToken = refreshToken;
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jcminarro/authexample/internal/network/refresh/RefreshEndpoint.java:
--------------------------------------------------------------------------------
1 | package com.jcminarro.authexample.internal.network.refresh;
2 |
3 | import com.jcminarro.authexample.internal.network.authorizator.UnauthorizatedApi;
4 |
5 | import retrofit2.Call;
6 | import retrofit2.http.Body;
7 | import retrofit2.http.POST;
8 |
9 | @UnauthorizatedApi
10 | public interface RefreshEndpoint {
11 |
12 | @POST("/refresh")
13 | Call refreshTokens(@Body RefreshBody refreshBody);
14 | }
15 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jcminarro/authexample/internal/network/refresh/RefreshResponse.java:
--------------------------------------------------------------------------------
1 | package com.jcminarro.authexample.internal.network.refresh;
2 |
3 | import com.google.gson.annotations.SerializedName;
4 |
5 | public class RefreshResponse {
6 | private final @SerializedName("accessToken") String accessToken;
7 | private final @SerializedName("refreshToken") String refreshToken;
8 |
9 | public RefreshResponse(String accessToken, String refreshToken) {
10 | this.accessToken = accessToken;
11 | this.refreshToken = refreshToken;
12 | }
13 |
14 | public String getAccessToken() {
15 | return accessToken;
16 | }
17 |
18 | public String getRefreshToken() {
19 | return refreshToken;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jcminarro/authexample/internal/presenter/BasePresenter.java:
--------------------------------------------------------------------------------
1 | package com.jcminarro.authexample.internal.presenter;
2 |
3 | import com.jcminarro.authexample.internal.interactor.AsyncInteractor;
4 | import com.jcminarro.authexample.internal.interactor.InteractorExecutor;
5 |
6 | import java.lang.reflect.InvocationHandler;
7 | import java.lang.reflect.Method;
8 | import java.lang.reflect.Proxy;
9 | import java.util.ArrayList;
10 | import java.util.Arrays;
11 | import java.util.LinkedHashSet;
12 | import java.util.List;
13 |
14 | public class BasePresenter {
15 |
16 | /**
17 | * Represents the View component inside the Model View Presenter pattern. This interface must be
18 | * used as base interface for every View interface declared.
19 | */
20 | public interface View {
21 |
22 | }
23 |
24 | private T view;
25 | private LinkedHashSet> viewImplementedInterfaces = new LinkedHashSet<>();
26 | private InteractorExecutor executor;
27 |
28 | /**
29 | * Method called in the presenter lifecycle. Invoked when the component containing the presenter
30 | * is initialized.
31 | */
32 | public void initialize() {
33 |
34 | }
35 |
36 | /**
37 | * Method called in the presenter lifecycle. Invoked when the component containing the presenter
38 | * is resumed.
39 | */
40 | public void update() {
41 |
42 | }
43 |
44 | /**
45 | * Method called in the presenter lifecycle. Invoked when the component containing the presenter
46 | * is paused.
47 | */
48 | public void pause() {
49 |
50 | }
51 |
52 | /**
53 | * Method called in the presenter lifecycle. Invoked when the component containing the presenter
54 | * is destroyed.
55 | */
56 | public void destroy() {
57 |
58 | }
59 |
60 | /**
61 | * Returns the view configured in the presenter which real implementation is an Activity or
62 | * Fragment using this presenter.
63 | */
64 | public final T getView() {
65 | return view;
66 | }
67 |
68 | /**
69 | * Configures the View instance used in this presenter as view.
70 | */
71 | public void setView(T view) {
72 | this.view = view;
73 | }
74 |
75 | public void addViewInterfaces(Class>[] interfaces) {
76 | viewImplementedInterfaces.addAll(Arrays.asList(interfaces));
77 | }
78 |
79 | public void setExecutor(InteractorExecutor executor) {
80 | this.executor = executor;
81 | }
82 |
83 | /**
84 | * Changes the current view instance with a dynamic proxy to avoid real UI updates.
85 | */
86 | public void resetView() {
87 | final List> viewClasses = getViewInterfaceClass();
88 | InvocationHandler emptyHandler = new InvocationHandler() {
89 | @Override
90 | public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
91 | return null;
92 | }
93 | };
94 | ClassLoader classLoader = viewClasses.get(0).getClassLoader();
95 | this.view = (T) Proxy.newProxyInstance(classLoader, viewClasses.toArray(new Class>[0]), emptyHandler);
96 | }
97 |
98 | protected void execute(
99 | AsyncInteractor interactor,
100 | I input,
101 | InteractorExecutor.Callback callback) {
102 | executor.execute(interactor, input, callback);
103 | }
104 |
105 | protected void execute(
106 | AsyncInteractor interactor,
107 | I input) {
108 | execute(interactor, input, new InteractorExecutor.NullCallback());
109 | }
110 |
111 | private List> getViewInterfaceClass() {
112 | Class> matchingClass = BasePresenter.View.class;
113 | List> interfaceClasses = new ArrayList<>();
114 |
115 | for (Class> interfaceCandidate : viewImplementedInterfaces) {
116 | if (matchingClass.isAssignableFrom(interfaceCandidate)) {
117 | interfaceClasses.add(interfaceCandidate);
118 | }
119 | }
120 |
121 | return interfaceClasses;
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jcminarro/authexample/internal/presenter/Presenter.java:
--------------------------------------------------------------------------------
1 | package com.jcminarro.authexample.internal.presenter;
2 |
3 | import java.lang.annotation.ElementType;
4 | import java.lang.annotation.Retention;
5 | import java.lang.annotation.RetentionPolicy;
6 | import java.lang.annotation.Target;
7 |
8 | /**
9 | * Annotation to define the designed presenter
10 | */
11 | @Retention(RetentionPolicy.RUNTIME)
12 | @Target(ElementType.FIELD)
13 | public @interface Presenter {
14 | }
15 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jcminarro/authexample/internal/presenter/lifecycle/PresenterAnnotationException.java:
--------------------------------------------------------------------------------
1 | package com.jcminarro.authexample.internal.presenter.lifecycle;
2 |
3 | class PresenterAnnotationException extends RuntimeException {
4 |
5 | PresenterAnnotationException(String detailMessage) {
6 | super(detailMessage);
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jcminarro/authexample/internal/presenter/lifecycle/PresenterLifecycleLinker.java:
--------------------------------------------------------------------------------
1 | package com.jcminarro.authexample.internal.presenter.lifecycle;
2 |
3 | import android.app.Activity;
4 | import android.support.v4.app.Fragment;
5 | import android.view.View;
6 |
7 | import com.jcminarro.authexample.internal.interactor.InteractorExecutor;
8 | import com.jcminarro.authexample.internal.presenter.BasePresenter;
9 | import com.jcminarro.authexample.internal.presenter.Presenter;
10 |
11 | import java.lang.reflect.Field;
12 | import java.lang.reflect.Modifier;
13 | import java.util.Arrays;
14 | import java.util.Collection;
15 | import java.util.Collections;
16 | import java.util.HashSet;
17 | import java.util.Set;
18 |
19 | import javax.inject.Inject;
20 |
21 | public class PresenterLifecycleLinker {
22 |
23 | private final Set presenters = new HashSet<>();
24 | private static final Collection> NON_PRESENTED_CLASSES =
25 | Collections.unmodifiableCollection(
26 | Arrays.asList(Activity.class, Fragment.class, View.class, Object.class));
27 | private final InteractorExecutor executor;
28 |
29 | @Inject
30 | public PresenterLifecycleLinker(InteractorExecutor executor) {
31 | this.executor = executor;
32 | }
33 |
34 | public void initialize(BasePresenter.View view) {
35 | addAnnotatedPresenters(view);
36 | setView(view);
37 | setExecutor();
38 | initializePresenters();
39 | }
40 |
41 | /**
42 | * Initializes all the already registered presenters lifecycle.
43 | */
44 | private void initializePresenters() {
45 | for (BasePresenter presenter : presenters) {
46 | presenter.initialize();
47 | }
48 | }
49 |
50 | /**
51 | * Updates all the already registered presenters lifecycle and updates the view instance
52 | * associated to these presenters.
53 | *
54 | * @param view to be updated for every registered presenter.
55 | */
56 | public void updatePresenters(BasePresenter.View view) {
57 | if (view == null) {
58 | throw new IllegalArgumentException(
59 | "The view instance used to update the presenters can't be null");
60 | }
61 | for (BasePresenter presenter : presenters) {
62 | presenter.setView(view);
63 | presenter.update();
64 | }
65 | }
66 |
67 | /**
68 | * Pauses all the already registered presenters lifecycle.
69 | */
70 | public void pausePresenters() {
71 | for (BasePresenter presenter : presenters) {
72 | presenter.pause();
73 | presenter.resetView();
74 | }
75 | }
76 |
77 | /**
78 | * Destroys all the already registered presenters lifecycle.
79 | */
80 | public void destroyPresenters() {
81 | for (BasePresenter presenter : presenters) {
82 | presenter.destroy();
83 | }
84 | }
85 |
86 | private void addAnnotatedPresenters(Object source) {
87 | addAnnotatedPresenters(source.getClass(), source);
88 | }
89 |
90 | private void addAnnotatedPresenters(Class clazz, Object source) {
91 | if (isOneOfOurNonPresentedClass(clazz)) {
92 | return;
93 | }
94 | addAnnotatedPresenters(clazz.getSuperclass(), source);
95 |
96 | for (Field field : clazz.getDeclaredFields()) {
97 | if (field.isAnnotationPresent(Presenter.class)) {
98 | if (Modifier.isPrivate(field.getModifiers())) {
99 | throw new PresenterNotAccessibleException(
100 | "Presenter must be accessible for this class. The visibility modifier used can't be"
101 | + " private");
102 | } else {
103 | try {
104 | field.setAccessible(true);
105 | BasePresenter presenter = (BasePresenter) field.get(source);
106 | registerPresenter(presenter);
107 | presenter.addViewInterfaces(clazz.getInterfaces());
108 | field.setAccessible(false);
109 | } catch (IllegalAccessException e) {
110 | PresenterNotAccessibleException exception = new PresenterNotAccessibleException(
111 | "The presenter " + field.getName() + " into class " + clazz.getCanonicalName()
112 | + " can not be accessed");
113 | exception.initCause(e);
114 | throw exception;
115 | } catch (ClassCastException e) {
116 | throw new PresenterAnnotationException(
117 | "The annotation " + Presenter.class.getCanonicalName() + " is being used on an object" +
118 | " that is not a " + BasePresenter.class.getCanonicalName() + " on the class " +
119 | clazz.getCanonicalName()
120 | );
121 | }
122 | }
123 | }
124 | }
125 | }
126 |
127 | private boolean isOneOfOurNonPresentedClass(Class clazz) {
128 | return NON_PRESENTED_CLASSES.contains(clazz);
129 | }
130 |
131 | private void registerPresenter(BasePresenter presenter) {
132 | if (presenter == null) {
133 | throw new IllegalArgumentException("The presenter instance to be registered can't be null");
134 | }
135 | presenters.add(presenter);
136 | }
137 |
138 | private void setView(BasePresenter.View view) {
139 | for (BasePresenter presenter : presenters) {
140 | presenter.setView(view);
141 | }
142 | }
143 |
144 | private void setExecutor() {
145 | for (BasePresenter presenter : presenters) {
146 | presenter.setExecutor(executor);
147 | }
148 | }
149 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/jcminarro/authexample/internal/presenter/lifecycle/PresenterNotAccessibleException.java:
--------------------------------------------------------------------------------
1 | package com.jcminarro.authexample.internal.presenter.lifecycle;
2 |
3 | class PresenterNotAccessibleException extends RuntimeException {
4 |
5 | PresenterNotAccessibleException(String detailMessage) {
6 | super(detailMessage);
7 | }
8 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/jcminarro/authexample/internal/repository/QuoteRepository.kt:
--------------------------------------------------------------------------------
1 | package com.jcminarro.authexample.internal.repository
2 |
3 | import com.jcminarro.authexample.internal.network.APIIOException
4 | import com.jcminarro.authexample.internal.network.quote.QuoteApiClient
5 | import javax.inject.Inject
6 |
7 | class QuoteRepository @Inject constructor(private val quoteApiClient: QuoteApiClient) {
8 |
9 | @Throws(APIIOException::class)
10 | fun getRandomQuote() = quoteApiClient.getRandomQuote()
11 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/jcminarro/authexample/internal/repository/SessionRepository.java:
--------------------------------------------------------------------------------
1 | package com.jcminarro.authexample.internal.repository;
2 |
3 | import com.jcminarro.authexample.internal.localdatasource.SessionDatasource;
4 | import com.jcminarro.authexample.internal.network.APIIOException;
5 | import com.jcminarro.authexample.internal.network.OAuth;
6 | import com.jcminarro.authexample.internal.network.login.LoginApiClient;
7 | import com.jcminarro.authexample.internal.network.refresh.RefreshApiClient;
8 |
9 | import javax.inject.Inject;
10 |
11 | public class SessionRepository {
12 |
13 | private final LoginApiClient loginApiClient;
14 | private final RefreshApiClient refreshApiClient;
15 | private final SessionDatasource sessionDatasource;
16 |
17 | @Inject
18 | public SessionRepository(
19 | LoginApiClient loginApiClient,
20 | RefreshApiClient refreshApiClient,
21 | SessionDatasource sessionDatasource) {
22 | this.loginApiClient = loginApiClient;
23 | this.refreshApiClient = refreshApiClient;
24 | this.sessionDatasource = sessionDatasource;
25 | }
26 |
27 | public boolean login(String username, String password) throws APIIOException {
28 | sessionDatasource.storeOAuthSession(loginApiClient.login(username, password));
29 | return true;
30 | }
31 |
32 | public boolean refreshSession() {
33 | OAuth oAuth = sessionDatasource.getOAuthSession();
34 | if (oAuth != null) {
35 | try {
36 | sessionDatasource.storeOAuthSession(refreshApiClient.refresh(oAuth.getRefreshToken()));
37 | return true;
38 | } catch (Exception e) {
39 | e.printStackTrace();
40 | }
41 | }
42 | return false;
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jcminarro/authexample/login/LoginActivity.java:
--------------------------------------------------------------------------------
1 | package com.jcminarro.authexample.login;
2 |
3 | import android.content.Context;
4 | import android.content.Intent;
5 | import android.view.KeyEvent;
6 | import android.view.View;
7 | import android.view.inputmethod.EditorInfo;
8 | import android.widget.AutoCompleteTextView;
9 | import android.widget.EditText;
10 | import android.widget.TextView;
11 | import android.widget.Toast;
12 |
13 | import com.jcminarro.authexample.R;
14 | import com.jcminarro.authexample.internal.di.component.DaggerLoginComponent;
15 | import com.jcminarro.authexample.internal.di.component.LoginComponent;
16 | import com.jcminarro.authexample.internal.di.injectablebase.BaseInjectionActivity;
17 | import com.jcminarro.authexample.internal.di.module.ActivityModule;
18 | import com.jcminarro.authexample.internal.presenter.Presenter;
19 |
20 | import javax.inject.Inject;
21 |
22 | import butterknife.BindView;
23 | import butterknife.OnClick;
24 |
25 | public class LoginActivity extends BaseInjectionActivity implements LoginPresenter.View {
26 |
27 | @BindView(R.id.username) AutoCompleteTextView username;
28 | @BindView(R.id.password) EditText password;
29 | @BindView(R.id.login_progress) View loading;
30 | @BindView(R.id.login_form) View loginForm;
31 |
32 | @Inject
33 | @Presenter LoginPresenter presenter;
34 |
35 | public static Intent getLaunchIntent(Context context) {
36 | return new Intent(context, LoginActivity.class);
37 | }
38 |
39 | @Override
40 | protected void onConfigureViews() {
41 | super.onConfigureViews();
42 | password.setOnEditorActionListener(new TextView.OnEditorActionListener() {
43 | @Override
44 | public boolean onEditorAction(TextView textView, int id, KeyEvent keyEvent) {
45 | if (id == EditorInfo.IME_ACTION_DONE || id == EditorInfo.IME_NULL) {
46 | onLogin();
47 | return true;
48 | }
49 | return false;
50 | }
51 | });
52 | }
53 |
54 | @Override
55 | protected void initDI() {
56 | activityComponent = DaggerLoginComponent
57 | .builder()
58 | .appComponent(getAppComponent())
59 | .activityModule(new ActivityModule(this))
60 | .build();
61 | activityComponent.inject(this);
62 | }
63 |
64 | private void onLogin() {
65 | presenter.login(username.getText().toString(),
66 | password.getText().toString());
67 | }
68 |
69 | @Override
70 | protected int getLayout() {
71 | return R.layout.activity_login;
72 | }
73 |
74 | @OnClick(R.id.signIn)
75 | public void onLoginClick() {
76 | onLogin();
77 | }
78 |
79 | @Override
80 | public void close() {
81 | onBackPressed();
82 | }
83 |
84 | @Override
85 | public void showError() {
86 | Toast.makeText(this, R.string.error_invalid_credential, Toast.LENGTH_LONG).show();
87 | }
88 |
89 | @Override
90 | public void showLogin() {
91 | loginForm.setVisibility(View.VISIBLE);
92 | }
93 |
94 | @Override
95 | public void hideLogin() {
96 | loginForm.setVisibility(View.GONE);
97 | }
98 |
99 | @Override
100 | public void showLoading() {
101 | loading.setVisibility(View.VISIBLE);
102 | }
103 |
104 | @Override
105 | public void hideLoading() {
106 | loading.setVisibility(View.GONE);
107 | }
108 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/jcminarro/authexample/login/LoginInteractor.java:
--------------------------------------------------------------------------------
1 | package com.jcminarro.authexample.login;
2 |
3 | import com.jcminarro.authexample.internal.interactor.AsyncInteractor;
4 | import com.jcminarro.authexample.internal.network.APIIOException;
5 | import com.jcminarro.authexample.internal.repository.SessionRepository;
6 |
7 | import javax.inject.Inject;
8 |
9 | public class LoginInteractor implements AsyncInteractor {
10 |
11 | private final SessionRepository sessionRepository;
12 |
13 | @Inject
14 | public LoginInteractor(SessionRepository sessionRepository) {
15 | this.sessionRepository = sessionRepository;
16 | }
17 |
18 | @Override
19 | public void execute(Input input, Callback callback) {
20 | try {
21 | callback.onSuccess(sessionRepository.login(input.username, input.password));
22 | } catch (APIIOException e) {
23 | callback.onError(e);
24 | }
25 | }
26 |
27 | public static class Input {
28 | private final String username;
29 | private final String password;
30 |
31 | public Input(String username, String password) {
32 | this.username = username;
33 | this.password = password;
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jcminarro/authexample/login/LoginPresenter.java:
--------------------------------------------------------------------------------
1 | package com.jcminarro.authexample.login;
2 |
3 | import android.text.TextUtils;
4 |
5 | import com.jcminarro.authexample.internal.interactor.InteractorExecutor;
6 | import com.jcminarro.authexample.internal.navigator.Navigator;
7 | import com.jcminarro.authexample.internal.presenter.BasePresenter;
8 |
9 | import javax.inject.Inject;
10 |
11 | public class LoginPresenter extends BasePresenter {
12 |
13 | private final LoginInteractor loginInteractor;
14 | private final Navigator navigator;
15 |
16 | @Inject
17 | public LoginPresenter(LoginInteractor loginInteractor, Navigator navigator) {
18 | this.loginInteractor = loginInteractor;
19 | this.navigator = navigator;
20 | }
21 |
22 | @Override
23 | public void update() {
24 | super.update();
25 | showLoginStatus();
26 | }
27 |
28 | private void showLoginStatus() {
29 | getView().showLogin();
30 | getView().hideLoading();
31 | }
32 |
33 | public void login(String username, String password) {
34 | if (TextUtils.isEmpty(username) || TextUtils.isEmpty(password)) {
35 | onLoginError();
36 | } else {
37 | performLogin(username, password);
38 | }
39 | }
40 |
41 | private void performLogin(String username, String password) {
42 | showLoadingStatus();
43 | execute(loginInteractor,
44 | new LoginInteractor.Input(username, password),
45 | new InteractorExecutor.Callback() {
46 | @Override
47 | public void onSuccess(Boolean isLoggedIn) {
48 | if (isLoggedIn) {
49 | onLoggedIn();
50 | } else {
51 | onLoginError();
52 | }
53 | }
54 |
55 | @Override
56 | public void onError(Exception error) {
57 | onLoginError();
58 | }
59 | });
60 | }
61 |
62 | private void showLoadingStatus() {
63 | getView().showLoading();
64 | getView().hideLogin();
65 | }
66 |
67 | private void onLoginError() {
68 | showLoginStatus();
69 | getView().showError();
70 | }
71 |
72 | private void onLoggedIn() {
73 | getView().close();
74 | navigator.navigateToQuote();
75 | }
76 |
77 | interface View extends BasePresenter.View {
78 |
79 | void close();
80 |
81 | void showError();
82 |
83 | void showLogin();
84 |
85 | void hideLogin();
86 |
87 | void showLoading();
88 |
89 | void hideLoading();
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jcminarro/authexample/quote/GetQuoteInteractor.java:
--------------------------------------------------------------------------------
1 | package com.jcminarro.authexample.quote;
2 |
3 | import com.jcminarro.authexample.internal.interactor.AsyncInteractor;
4 | import com.jcminarro.authexample.internal.model.Quote;
5 | import com.jcminarro.authexample.internal.network.APIIOException;
6 | import com.jcminarro.authexample.internal.repository.QuoteRepository;
7 |
8 | import javax.inject.Inject;
9 |
10 | public class GetQuoteInteractor implements AsyncInteractor {
11 |
12 | private final QuoteRepository quoteRepository;
13 |
14 | @Inject
15 | public GetQuoteInteractor(QuoteRepository quoteRepository) {
16 | this.quoteRepository = quoteRepository;
17 | }
18 |
19 | @Override
20 | public void execute(Void input, Callback callback) {
21 | try {
22 | callback.onSuccess(quoteRepository.getRandomQuote());
23 | } catch (APIIOException e) {
24 | callback.onError(e);
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jcminarro/authexample/quote/QuoteActivity.java:
--------------------------------------------------------------------------------
1 | package com.jcminarro.authexample.quote;
2 |
3 | import android.content.Context;
4 | import android.content.Intent;
5 | import android.view.View;
6 | import android.widget.TextView;
7 | import android.widget.Toast;
8 |
9 | import com.jcminarro.authexample.R;
10 | import com.jcminarro.authexample.internal.di.component.DaggerQuoteComponent;
11 | import com.jcminarro.authexample.internal.di.component.QuoteComponent;
12 | import com.jcminarro.authexample.internal.di.injectablebase.BaseInjectionActivity;
13 | import com.jcminarro.authexample.internal.di.module.ActivityModule;
14 | import com.jcminarro.authexample.internal.presenter.Presenter;
15 |
16 | import javax.inject.Inject;
17 |
18 | import butterknife.BindView;
19 | import butterknife.OnClick;
20 |
21 | public class QuoteActivity extends BaseInjectionActivity implements QuotePresenter.View {
22 |
23 | @BindView(R.id.loading) View loading;
24 | @BindView(R.id.author) TextView authorView;
25 | @BindView(R.id.message) TextView messageView;
26 |
27 | @Inject
28 | @Presenter QuotePresenter presenter;
29 |
30 | public static Intent getLaunchIntent(Context context) {
31 | return new Intent(context, QuoteActivity.class);
32 | }
33 |
34 | @Override
35 | protected void initDI() {
36 | activityComponent = DaggerQuoteComponent
37 | .builder()
38 | .appComponent(getAppComponent())
39 | .activityModule(new ActivityModule(this))
40 | .build();
41 | activityComponent.inject(this);
42 | }
43 |
44 | @Override
45 | protected int getLayout() {
46 | return R.layout.activity_quote;
47 | }
48 |
49 | @Override
50 | public void showQuote(String author, String message) {
51 | authorView.setText(author);
52 | messageView.setText(message);
53 | }
54 |
55 | @Override
56 | public void showError() {
57 | Toast.makeText(this, R.string.generic_error, Toast.LENGTH_LONG).show();
58 | }
59 |
60 | @Override
61 | public void showLoading() {
62 | loading.setVisibility(View.VISIBLE);
63 | }
64 |
65 | @Override
66 | public void hideLoading() {
67 | loading.setVisibility(View.GONE);
68 | }
69 |
70 | @OnClick(R.id.getQuote)
71 | public void onGetQuoteClick() {
72 | presenter.getRandomQuote();
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jcminarro/authexample/quote/QuotePresenter.java:
--------------------------------------------------------------------------------
1 | package com.jcminarro.authexample.quote;
2 |
3 | import com.jcminarro.authexample.internal.interactor.InteractorExecutor;
4 | import com.jcminarro.authexample.internal.model.Quote;
5 | import com.jcminarro.authexample.internal.presenter.BasePresenter;
6 |
7 | import javax.inject.Inject;
8 |
9 | public class QuotePresenter extends BasePresenter {
10 |
11 | private final GetQuoteInteractor getQuoteInteractor;
12 |
13 | @Inject
14 | public QuotePresenter(GetQuoteInteractor getQuoteInteractor) {
15 | this.getQuoteInteractor = getQuoteInteractor;
16 | }
17 |
18 | @Override
19 | public void initialize() {
20 | super.initialize();
21 | getRandomQuote();
22 | }
23 |
24 | public void getRandomQuote() {
25 | getView().showLoading();
26 | execute(getQuoteInteractor, null, new InteractorExecutor.Callback() {
27 | @Override
28 | public void onSuccess(Quote quote) {
29 | renderQuote(quote);
30 | }
31 |
32 | @Override
33 | public void onError(Exception error) {
34 | renderError();
35 | }
36 | });
37 | }
38 |
39 | private void renderError() {
40 | getView().hideLoading();
41 | getView().showError();
42 | }
43 |
44 | private void renderQuote(Quote quote) {
45 | getView().hideLoading();
46 | getView().showQuote(quote.getAuthor(), quote.getMessage());
47 | }
48 |
49 | interface View extends BasePresenter.View {
50 |
51 | void showQuote(String author, String message);
52 |
53 | void showError();
54 |
55 | void showLoading();
56 |
57 | void hideLoading();
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jcminarro/authexample/startup/RefreshSessionInteractor.java:
--------------------------------------------------------------------------------
1 | package com.jcminarro.authexample.startup;
2 |
3 | import com.jcminarro.authexample.internal.interactor.AsyncInteractor;
4 | import com.jcminarro.authexample.internal.repository.SessionRepository;
5 |
6 | import javax.inject.Inject;
7 |
8 | public class RefreshSessionInteractor implements AsyncInteractor {
9 |
10 | private final SessionRepository sessionRepository;
11 |
12 | @Inject
13 | public RefreshSessionInteractor(SessionRepository sessionRepository) {
14 | this.sessionRepository = sessionRepository;
15 | }
16 |
17 | @Override
18 | public void execute(Void input, Callback callback) {
19 | callback.onSuccess(sessionRepository.refreshSession());
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jcminarro/authexample/startup/StartUpActivity.java:
--------------------------------------------------------------------------------
1 | package com.jcminarro.authexample.startup;
2 |
3 | import com.jcminarro.authexample.internal.di.component.DaggerStartUpComponent;
4 | import com.jcminarro.authexample.internal.di.component.StartUpComponent;
5 | import com.jcminarro.authexample.internal.di.injectablebase.BaseInjectionActivity;
6 | import com.jcminarro.authexample.internal.di.module.ActivityModule;
7 | import com.jcminarro.authexample.internal.presenter.Presenter;
8 |
9 | import javax.inject.Inject;
10 |
11 | public class StartUpActivity extends BaseInjectionActivity
12 | implements StartUpPresenter.View {
13 |
14 | @Inject
15 | @Presenter StartUpPresenter presenter;
16 |
17 | @Override
18 | protected void initDI() {
19 | activityComponent = DaggerStartUpComponent
20 | .builder()
21 | .appComponent(getAppComponent())
22 | .activityModule(new ActivityModule(this))
23 | .build();
24 | activityComponent.inject(this);
25 | }
26 |
27 | @Override
28 | protected int getLayout() {
29 | return 0;
30 | }
31 |
32 | @Override
33 | public void close() {
34 | onBackPressed();
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jcminarro/authexample/startup/StartUpPresenter.java:
--------------------------------------------------------------------------------
1 | package com.jcminarro.authexample.startup;
2 |
3 | import com.jcminarro.authexample.internal.interactor.InteractorExecutor;
4 | import com.jcminarro.authexample.internal.navigator.Navigator;
5 | import com.jcminarro.authexample.internal.presenter.BasePresenter;
6 |
7 | import javax.inject.Inject;
8 |
9 | public class StartUpPresenter extends BasePresenter {
10 |
11 | private final RefreshSessionInteractor refreshSessionInteractor;
12 | private final Navigator navigator;
13 |
14 | @Inject
15 | public StartUpPresenter(RefreshSessionInteractor refreshSessionInteractor, Navigator navigator) {
16 | this.refreshSessionInteractor = refreshSessionInteractor;
17 | this.navigator = navigator;
18 | }
19 |
20 | @Override
21 | public void update() {
22 | super.update();
23 | execute(refreshSessionInteractor,
24 | null,
25 | new InteractorExecutor.SuccessCallback() {
26 |
27 | @Override
28 | public void onSuccess(Boolean isRefreshedSession) {
29 | if (isRefreshedSession) {
30 | navigateToMain();
31 | } else {
32 | navigateToLogin();
33 | }
34 | getView().close();
35 | }
36 | });
37 | }
38 |
39 | private void navigateToLogin() {
40 | navigator.navigateToLogin();
41 | }
42 |
43 | private void navigateToMain() {
44 | navigator.navigateToQuote();
45 | }
46 |
47 | interface View extends BasePresenter.View {
48 |
49 | void close();
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
12 |
13 |
19 |
22 |
25 |
26 |
27 |
28 |
34 |
35 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
11 |
16 |
21 |
26 |
31 |
36 |
41 |
46 |
51 |
56 |
61 |
66 |
71 |
76 |
81 |
86 |
91 |
96 |
101 |
106 |
111 |
116 |
121 |
126 |
131 |
136 |
141 |
146 |
151 |
156 |
161 |
166 |
171 |
172 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_login.xml:
--------------------------------------------------------------------------------
1 |
13 |
14 |
22 |
23 |
28 |
29 |
35 |
36 |
40 |
41 |
49 |
50 |
51 |
55 |
56 |
68 |
69 |
70 |
79 |
80 |
81 |
82 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_quote.xml:
--------------------------------------------------------------------------------
1 |
2 |
13 |
20 |
21 |
30 |
31 |
39 |
40 |
49 |
50 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JcMinarro/AuthExample-Android/4240164a450e91aeaf12134e7afac55b1860d856/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JcMinarro/AuthExample-Android/4240164a450e91aeaf12134e7afac55b1860d856/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JcMinarro/AuthExample-Android/4240164a450e91aeaf12134e7afac55b1860d856/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JcMinarro/AuthExample-Android/4240164a450e91aeaf12134e7afac55b1860d856/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JcMinarro/AuthExample-Android/4240164a450e91aeaf12134e7afac55b1860d856/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JcMinarro/AuthExample-Android/4240164a450e91aeaf12134e7afac55b1860d856/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JcMinarro/AuthExample-Android/4240164a450e91aeaf12134e7afac55b1860d856/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JcMinarro/AuthExample-Android/4240164a450e91aeaf12134e7afac55b1860d856/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JcMinarro/AuthExample-Android/4240164a450e91aeaf12134e7afac55b1860d856/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JcMinarro/AuthExample-Android/4240164a450e91aeaf12134e7afac55b1860d856/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 16dp
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | AuthExample
3 | Sign in
4 | Username
5 | Password
6 | Sign in or register
7 | Sign in
8 | This credential are invalid
9 | Get Random Quote
10 | Something was wrong
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/release/java/com/jcminarro/authexample/Environment.java:
--------------------------------------------------------------------------------
1 | package com.jcminarro.authexample;
2 |
3 | public class Environment {
4 | public static final String ENDPOINT_BASE_URL = "https://jcminarro-auth-example.herokuapp.com";
5 | }
6 |
--------------------------------------------------------------------------------
/app/src/release/java/com/jcminarro/authexample/Utils.kt:
--------------------------------------------------------------------------------
1 | package com.jcminarro.authexample
2 |
3 | import android.util.Log
4 |
5 | val DEBUG_TAG = "debugTag"
6 |
7 | @JvmOverloads
8 | fun log(tag: String = DEBUG_TAG, text: String) {}
--------------------------------------------------------------------------------
/app/src/test/java/android/util/Log.java:
--------------------------------------------------------------------------------
1 | package android.util;
2 |
3 | public class Log {
4 |
5 | public static int d(String tag, String message) {
6 | return 0;
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/app/src/test/java/com/jcminarro/authexample/EndpointMother.kt:
--------------------------------------------------------------------------------
1 | package com.jcminarro.authexample
2 |
3 | import com.jcminarro.authexample.EndpointMother.TOKENS_PROVIDER_MOTHER_accessToken
4 | import com.jcminarro.authexample.internal.network.AccessTokenProvider
5 | import com.jcminarro.authexample.internal.network.EndpointFactory
6 | import com.jcminarro.authexample.internal.network.authorizator.AuthorizatedApiInterceptor
7 | import com.jcminarro.authexample.internal.network.authorizator.UnauthorizatedApiInterceptor
8 | import com.jcminarro.authexample.internal.network.login.LoginEndpoint
9 | import com.jcminarro.authexample.internal.network.quote.QuoteEndpoint
10 | import com.jcminarro.authexample.internal.network.reauthorizate.ReauthorizatedApiInterceptor
11 | import com.jcminarro.authexample.internal.network.reauthorizate.Reauthorizer
12 | import com.jcminarro.authexample.internal.network.refresh.RefreshEndpoint
13 |
14 | object EndpointMother {
15 |
16 | const val DEFAULT_API_HOST = "http://localhost:"
17 | const val TOKENS_PROVIDER_MOTHER_accessToken = "accessToken"
18 | }
19 |
20 | fun createTokensProviders(accessToken: String = TOKENS_PROVIDER_MOTHER_accessToken) = AccessTokenProvider { accessToken }
21 |
22 | fun createReauthorizer() = Reauthorizer {}
23 |
24 | fun createReauthorizedApiInterceptor(reauthorizer: Reauthorizer = createReauthorizer()) =
25 | ReauthorizatedApiInterceptor(reauthorizer)
26 |
27 | fun createAuthorizatedApiInterceptor(accessTokenProvider: AccessTokenProvider = createTokensProviders()) =
28 | AuthorizatedApiInterceptor(accessTokenProvider)
29 |
30 | fun createUnathorizatedApiInterceptor() = UnauthorizatedApiInterceptor()
31 |
32 | fun createLoginEndpoint(apiHost: String,
33 | unauthorizatedApiInterceptor: UnauthorizatedApiInterceptor = createUnathorizatedApiInterceptor()) =
34 | EndpointFactory.Builder(apiHost)
35 | .withUnaouthorizatedApiInterceptor(unauthorizatedApiInterceptor)
36 | .build()
37 | .create(LoginEndpoint::class.java)
38 |
39 | fun createRefeshEndpoint(apiHost: String,
40 | unauthorizatedApiInterceptor: UnauthorizatedApiInterceptor = createUnathorizatedApiInterceptor()) =
41 | EndpointFactory.Builder(apiHost)
42 | .withUnaouthorizatedApiInterceptor(unauthorizatedApiInterceptor)
43 | .build()
44 | .create(RefreshEndpoint::class.java)
45 |
46 | fun createQuoteEndpoint(apiHost: String,
47 | authorizatedApiInterceptor: AuthorizatedApiInterceptor = createAuthorizatedApiInterceptor(),
48 | reauthorizatedApiInterceptor: ReauthorizatedApiInterceptor = createReauthorizedApiInterceptor()) =
49 | EndpointFactory.Builder(apiHost)
50 | .withAuthorizatedApiInterceptor(authorizatedApiInterceptor)
51 | .withReauthorizatedApiInterceptor(reauthorizatedApiInterceptor)
52 | .build()
53 | .create(QuoteEndpoint::class.java)
--------------------------------------------------------------------------------
/app/src/test/java/com/jcminarro/authexample/EndpointPath.kt:
--------------------------------------------------------------------------------
1 | package com.jcminarro.authexample
2 |
3 | object EndpointPath{
4 |
5 | const val LOGIN = "/login"
6 | const val REFRESH = "/refresh"
7 | const val QUOTE = "/quote"
8 | }
--------------------------------------------------------------------------------
/app/src/test/java/com/jcminarro/authexample/ResponseJsonMother.kt:
--------------------------------------------------------------------------------
1 | package com.jcminarro.authexample
2 |
3 | import com.jcminarro.authexample.internal.network.login.LoginResponse
4 | import com.jcminarro.authexample.internal.network.quote.QuoteResponse
5 | import com.jcminarro.authexample.internal.network.refresh.RefreshResponse
6 |
7 | fun createLoginResponseJson(loginResponse: LoginResponse): String =
8 | """
9 | {
10 | "accessToken": "${loginResponse.accessToken}",
11 | "refreshToken": "${loginResponse.refreshToken}"
12 | }
13 | """.trimIndent()
14 |
15 | fun createRefreshResponseJson(refreshResponse: RefreshResponse): String =
16 | """
17 | {
18 | "accessToken": "${refreshResponse.accessToken}",
19 | "refreshToken": "${refreshResponse.refreshToken}"
20 | }
21 | """.trimIndent()
22 |
23 | fun createQuoteResponseJson(quoteResponse: QuoteResponse): String =
24 | """
25 | {
26 | "author": "${quoteResponse.author}",
27 | "message": "${quoteResponse.message}"
28 | }
29 | """.trimIndent()
--------------------------------------------------------------------------------
/app/src/test/java/com/jcminarro/authexample/ResponseMother.kt:
--------------------------------------------------------------------------------
1 | package com.jcminarro.authexample
2 |
3 | import com.jcminarro.authexample.ResponseMother.QUOTE_MOTHERR_author
4 | import com.jcminarro.authexample.ResponseMother.QUOTE_MOTHERR_message
5 | import com.jcminarro.authexample.internal.network.login.LoginResponse
6 | import com.jcminarro.authexample.internal.network.quote.QuoteResponse
7 | import com.jcminarro.authexample.internal.network.refresh.RefreshResponse
8 |
9 | object ResponseMother{
10 | const val LOGIN_MOTHERR_accessToken = "accessToken"
11 | const val LOGIN_MOTHERR_refreshToken = "refreshToken"
12 | const val REFRESH_MOTHERR_accessToken = "accessToken"
13 | const val REFRESH_MOTHERR_refreshToken = "refreshToken"
14 | const val QUOTE_MOTHERR_author = "author"
15 | const val QUOTE_MOTHERR_message = "message"
16 | }
17 |
18 | fun createLoginResponse(accessToken: String = ResponseMother.LOGIN_MOTHERR_accessToken,
19 | refreshToken: String = ResponseMother.LOGIN_MOTHERR_refreshToken) =
20 | LoginResponse(accessToken, refreshToken)
21 |
22 | fun createRefreshResponse(accessToken: String = ResponseMother.REFRESH_MOTHERR_accessToken,
23 | refreshToken: String = ResponseMother.REFRESH_MOTHERR_refreshToken) =
24 | RefreshResponse(accessToken, refreshToken)
25 |
26 | fun createQuoteResponse(author: String = QUOTE_MOTHERR_author,
27 | message: String = QUOTE_MOTHERR_message) =
28 | QuoteResponse(author, message)
--------------------------------------------------------------------------------
/app/src/test/java/com/jcminarro/authexample/StringExtensions.kt:
--------------------------------------------------------------------------------
1 | package com.jcminarro.authexample
2 |
3 | fun String.removeAllSpaces() =
4 | replace(" ", "")
5 | .replace("\n", "")
--------------------------------------------------------------------------------
/app/src/test/java/com/jcminarro/authexample/internal/network/authorizator/AuthorizatedApiInterceptorTest.kt:
--------------------------------------------------------------------------------
1 | package com.jcminarro.authexample.internal.network.authorizator
2 |
3 | import com.jcminarro.authexample.internal.network.AccessTokenProvider
4 | import com.nhaarman.mockito_kotlin.check
5 | import com.nhaarman.mockito_kotlin.doReturn
6 | import okhttp3.Interceptor
7 | import okhttp3.Request
8 | import okhttp3.Response
9 | import org.amshove.kluent.*
10 | import org.junit.Before
11 | import org.junit.Test
12 |
13 | class AuthorizatedApiInterceptorTest {
14 |
15 | private val HEADER_AUTH_KEY = "X-access-token"
16 | private val response: Response = mock()
17 | private val request: Request = Request.Builder().url("http://jc.com").build()
18 | private val chain: Interceptor.Chain = mock()
19 | private val accessTokenProvider: AccessTokenProvider = mock()
20 | private val accessToken = "accessToken"
21 | private val authorizatedApiInterceptor = AuthorizatedApiInterceptor(accessTokenProvider)
22 |
23 | @Before
24 | fun setUp() {
25 | When calling chain.request() doReturn request
26 | When calling chain.proceed(any()) doReturn response
27 | When calling accessTokenProvider.accessToken doReturn accessToken
28 | }
29 |
30 | @Test
31 | fun `Should add a header with the access token`() {
32 | authorizatedApiInterceptor.intercept(chain)
33 |
34 | Verify on chain that chain.proceed(check {
35 | it.header(HEADER_AUTH_KEY) `should equal to` accessToken
36 | }) was called
37 | }
38 | }
--------------------------------------------------------------------------------
/app/src/test/java/com/jcminarro/authexample/internal/network/login/LoginApiClientTest.kt:
--------------------------------------------------------------------------------
1 | package com.jcminarro.authexample.internal.network.login
2 |
3 | import com.github.tomakehurst.wiremock.client.WireMock.aResponse
4 | import com.github.tomakehurst.wiremock.client.WireMock.equalToJson
5 | import com.github.tomakehurst.wiremock.client.WireMock.post
6 | import com.github.tomakehurst.wiremock.client.WireMock.stubFor
7 | import com.github.tomakehurst.wiremock.core.WireMockConfiguration
8 | import com.github.tomakehurst.wiremock.junit.WireMockRule
9 | import com.jcminarro.authexample.EndpointMother
10 | import com.jcminarro.authexample.EndpointPath
11 | import com.jcminarro.authexample.ResponseMother
12 | import com.jcminarro.authexample.createLoginEndpoint
13 | import com.jcminarro.authexample.createLoginResponse
14 | import com.jcminarro.authexample.createLoginResponseJson
15 | import com.jcminarro.authexample.internal.network.APIIOException
16 | import org.amshove.kluent.`should equal to`
17 | import org.junit.Before
18 | import org.junit.Rule
19 | import org.junit.Test
20 |
21 | class LoginApiClientTest {
22 |
23 | val VALID_USERNAME = "Jc Miñarro"
24 | val VALID_PASSWORD = "password"
25 | val INVALID_USERNAME = "username"
26 | val INVALID_PASSWORD = "1234"
27 |
28 | private lateinit
29 | var loginApiClient: LoginApiClient
30 |
31 | @Rule
32 | @JvmField
33 | val wiremockRule = WireMockRule(WireMockConfiguration.options().dynamicPort())
34 |
35 | @Before
36 | fun setUp() {
37 | loginApiClient = LoginApiClient(
38 | createLoginEndpoint(EndpointMother.DEFAULT_API_HOST + wiremockRule.port()))
39 | stubFor(post(EndpointPath.LOGIN)
40 | .withRequestBody(equalToJson(createRequestBodyJson(VALID_USERNAME, VALID_PASSWORD)))
41 | .willReturn(aResponse().withBody(
42 | createLoginResponseJson(
43 | createLoginResponse()))))
44 | stubFor(post(EndpointPath.LOGIN)
45 | .withRequestBody(equalToJson(createRequestBodyJson(VALID_USERNAME, INVALID_PASSWORD)))
46 | .willReturn(aResponse().withStatus(401)))
47 | stubFor(post(EndpointPath.LOGIN)
48 | .withRequestBody(equalToJson(createRequestBodyJson(INVALID_USERNAME, VALID_PASSWORD)))
49 | .willReturn(aResponse().withStatus(401)))
50 | }
51 |
52 | @Test
53 | fun `Should return an OAuth when login with valid credential`() {
54 | val oAuth = loginApiClient.login(VALID_USERNAME, VALID_PASSWORD)
55 |
56 | oAuth.accessToken `should equal to` ResponseMother.LOGIN_MOTHERR_accessToken
57 | oAuth.refreshToken `should equal to` ResponseMother.LOGIN_MOTHERR_refreshToken
58 | }
59 |
60 | @Test(expected = APIIOException::class)
61 | fun `Should throw an exception when try to login with an invalid password`() {
62 | loginApiClient.login(VALID_USERNAME, INVALID_PASSWORD)
63 | }
64 |
65 | @Test(expected = APIIOException::class)
66 | fun `Should throw an exception when try to login with an invalid username`() {
67 | loginApiClient.login(INVALID_USERNAME, VALID_PASSWORD)
68 | }
69 |
70 | fun createRequestBodyJson(username: String, password: String) =
71 | """
72 | {
73 | "username": "$username",
74 | "password": "$password"
75 | }
76 | """.trimIndent()
77 | }
--------------------------------------------------------------------------------
/app/src/test/java/com/jcminarro/authexample/internal/network/login/LoginApiClientWithMockWebServerTest.kt:
--------------------------------------------------------------------------------
1 | package com.jcminarro.authexample.internal.network.login
2 |
3 | import com.jcminarro.authexample.EndpointPath
4 | import com.jcminarro.authexample.ResponseMother
5 | import com.jcminarro.authexample.createLoginEndpoint
6 | import com.jcminarro.authexample.createLoginResponse
7 | import com.jcminarro.authexample.createLoginResponseJson
8 | import com.jcminarro.authexample.internal.network.APIIOException
9 | import com.jcminarro.authexample.removeAllSpaces
10 | import okhttp3.mockwebserver.Dispatcher
11 | import okhttp3.mockwebserver.MockResponse
12 | import okhttp3.mockwebserver.MockWebServer
13 | import okhttp3.mockwebserver.RecordedRequest
14 | import org.amshove.kluent.`should equal to`
15 | import org.junit.Before
16 | import org.junit.Test
17 |
18 | class LoginApiClientWithMockWebServerTest {
19 |
20 | val VALID_USERNAME = "JcMiñarro"
21 | val VALID_PASSWORD = "password"
22 | val INVALID_USERNAME = "username"
23 | val INVALID_PASSWORD = "1234"
24 |
25 | private lateinit var loginApiClient: LoginApiClient
26 | private lateinit var server: MockWebServer
27 |
28 | @Before
29 | fun setUp() {
30 | server = MockWebServer()
31 | loginApiClient = LoginApiClient(
32 | createLoginEndpoint(server.url("/").toString()))
33 | val dispatcher = object : Dispatcher() {
34 | override fun dispatch(request: RecordedRequest): MockResponse = when {
35 | isInvalidUsernameRequest(request) -> MockResponse().setResponseCode(401)
36 | isInvalidPasswordRequest(request) -> MockResponse().setResponseCode(401)
37 | isValidLoginRequest(request) -> MockResponse()
38 | .setResponseCode(200)
39 | .setBody(createLoginResponseJson(createLoginResponse()))
40 | else -> throw InterruptedException("Request not supported")
41 | }
42 |
43 | private fun isInvalidUsernameRequest(request: RecordedRequest): Boolean =
44 | request.method == "POST" &&
45 | request.path == EndpointPath.LOGIN &&
46 | request.body.clone().readUtf8() == createRequestBodyJson(INVALID_USERNAME, VALID_PASSWORD).removeAllSpaces()
47 |
48 | private fun isInvalidPasswordRequest(request: RecordedRequest): Boolean =
49 | request.method == "POST" &&
50 | request.path == EndpointPath.LOGIN &&
51 | request.body.clone().readUtf8() == createRequestBodyJson(VALID_USERNAME, INVALID_PASSWORD).removeAllSpaces()
52 |
53 | private fun isValidLoginRequest(request: RecordedRequest): Boolean =
54 | request.method == "POST" &&
55 | request.path == EndpointPath.LOGIN &&
56 | request.body.clone().readUtf8() == createRequestBodyJson(VALID_USERNAME, VALID_PASSWORD).removeAllSpaces()
57 | }
58 | server.setDispatcher(dispatcher)
59 | }
60 |
61 | @Test
62 | fun `Should return an OAuth when login with valid credential`() {
63 | val oAuth = loginApiClient.login(VALID_USERNAME, VALID_PASSWORD)
64 |
65 | oAuth.accessToken `should equal to` ResponseMother.LOGIN_MOTHERR_accessToken
66 | oAuth.refreshToken `should equal to` ResponseMother.LOGIN_MOTHERR_refreshToken
67 | }
68 |
69 | @Test(expected = APIIOException::class)
70 | fun `Should throw an exception when try to login with an invalid password`() {
71 | loginApiClient.login(VALID_USERNAME, INVALID_PASSWORD)
72 | }
73 |
74 | @Test(expected = APIIOException::class)
75 | fun `Should throw an exception when try to login with an invalid username`() {
76 | loginApiClient.login(INVALID_USERNAME, VALID_PASSWORD)
77 | }
78 |
79 | fun createRequestBodyJson(username: String, password: String) =
80 | """
81 | {
82 | "username": "$username",
83 | "password": "$password"
84 | }
85 | """.trimIndent()
86 | }
--------------------------------------------------------------------------------
/app/src/test/java/com/jcminarro/authexample/internal/network/quote/QuoteApiClientTest.kt:
--------------------------------------------------------------------------------
1 | package com.jcminarro.authexample.internal.network.quote
2 |
3 | import com.github.tomakehurst.wiremock.client.WireMock.*
4 | import com.github.tomakehurst.wiremock.core.WireMockConfiguration
5 | import com.github.tomakehurst.wiremock.junit.WireMockRule
6 | import com.jcminarro.authexample.*
7 | import com.jcminarro.authexample.internal.network.APIIOException
8 | import org.amshove.kluent.`should equal to`
9 | import org.junit.Before
10 | import org.junit.Rule
11 | import org.junit.Test
12 |
13 | class QuoteApiClientTest {
14 |
15 | private lateinit
16 | var quoteApiClient: QuoteApiClient
17 |
18 | @Rule
19 | @JvmField
20 | val wiremockRule = WireMockRule(WireMockConfiguration.options().dynamicPort())
21 |
22 | @Before
23 | fun setUp() {
24 | quoteApiClient = QuoteApiClient(
25 | createQuoteEndpoint(EndpointMother.DEFAULT_API_HOST + wiremockRule.port()))
26 | }
27 |
28 | @Test
29 | fun `Should return a Quote when asking for a random quote`() {
30 | stubFor(get(EndpointPath.QUOTE)
31 | .willReturn(aResponse().withBody(
32 | createQuoteResponseJson(
33 | createQuoteResponse()))))
34 |
35 | val quote = quoteApiClient.getRandomQuote()
36 |
37 | quote.author `should equal to` ResponseMother.QUOTE_MOTHERR_author
38 | quote.message `should equal to` ResponseMother.QUOTE_MOTHERR_message
39 | }
40 |
41 | @Test(expected = APIIOException::class)
42 | fun `Should throw an exception when try to get a quote and there is a network error`() {
43 | stubFor(get(EndpointPath.QUOTE)
44 | .willReturn(aResponse().withStatus(400)))
45 |
46 | quoteApiClient.getRandomQuote()
47 | }
48 | }
--------------------------------------------------------------------------------
/app/src/test/java/com/jcminarro/authexample/internal/network/quote/QuoteApiClientWithMockWebServerTest.kt:
--------------------------------------------------------------------------------
1 | package com.jcminarro.authexample.internal.network.quote
2 |
3 | import com.jcminarro.authexample.*
4 | import com.jcminarro.authexample.internal.network.APIIOException
5 | import okhttp3.mockwebserver.MockResponse
6 | import okhttp3.mockwebserver.MockWebServer
7 | import org.amshove.kluent.`should equal to`
8 | import org.junit.Before
9 | import org.junit.Test
10 |
11 | class QuoteApiClientWithMockWebServerTest {
12 |
13 | private lateinit var server: MockWebServer
14 | private lateinit var quoteApiClient: QuoteApiClient
15 |
16 | @Before
17 | fun setUp() {
18 | server = MockWebServer()
19 | quoteApiClient = QuoteApiClient(
20 | createQuoteEndpoint(server.url("/").toString()))
21 | }
22 |
23 | @Test
24 | fun `Should return a Quote when asking for a random quote`() {
25 | server.enqueue(MockResponse()
26 | .setResponseCode(200)
27 | .setBody(createQuoteResponseJson(
28 | createQuoteResponse())))
29 |
30 | val quote = quoteApiClient.getRandomQuote()
31 |
32 | quote.author `should equal to` ResponseMother.QUOTE_MOTHERR_author
33 | quote.message `should equal to` ResponseMother.QUOTE_MOTHERR_message
34 | }
35 |
36 | @Test(expected = APIIOException::class)
37 | fun `Should throw an exception when try to get a quote and there is a network error`() {
38 | server.enqueue(MockResponse().setResponseCode(400))
39 |
40 | quoteApiClient.getRandomQuote()
41 | }
42 | }
--------------------------------------------------------------------------------
/app/src/test/java/com/jcminarro/authexample/internal/network/reauthorizate/ReauthorizatedApiInterceptorTest.kt:
--------------------------------------------------------------------------------
1 | package com.jcminarro.authexample.internal.network.reauthorizate
2 |
3 | import com.nhaarman.mockito_kotlin.doReturn
4 | import com.nhaarman.mockito_kotlin.inOrder
5 | import okhttp3.Interceptor
6 | import okhttp3.Request
7 | import okhttp3.Response
8 | import org.amshove.kluent.*
9 | import org.junit.Before
10 | import org.junit.Test
11 |
12 | class ReauthorizatedApiInterceptorTest {
13 |
14 | private val validRequest: Request = Request.Builder().url("http://jc.com").build()
15 | private val invalidRequest: Request = Request.Builder().url("http://jc.com").build()
16 | private val chain: Interceptor.Chain = mock()
17 | private val validResponse: Response = mock()
18 | private val invalidResponse: Response = mock()
19 | private val reauthorizer: Reauthorizer = mock()
20 | private val reauthorizatedApiInterceptor = ReauthorizatedApiInterceptor(reauthorizer)
21 |
22 | @Before
23 | fun setUp() {
24 | When calling validResponse.code() doReturn 200
25 | When calling invalidResponse.code() doReturn 401
26 | When calling chain.proceed(validRequest) doReturn validResponse
27 | When calling chain.proceed(invalidRequest) doReturn invalidResponse
28 | }
29 |
30 | @Test
31 | fun `Shouldn't call to reauthorize receiving a valid response`() {
32 | When calling chain.request() doReturn validRequest
33 |
34 | reauthorizatedApiInterceptor.intercept(chain)
35 |
36 | inOrder(chain){
37 | verify(chain).request()
38 | verify(chain).proceed(validRequest)
39 | }
40 | `Verify no further interactions` on chain
41 | `Verify no interactions` on reauthorizer
42 | }
43 |
44 | @Test
45 | fun `Should call to reauthorize receiving a invalid response`() {
46 | When calling chain.request() doReturn listOf(invalidRequest, validRequest)
47 |
48 | reauthorizatedApiInterceptor.intercept(chain)
49 |
50 | inOrder(chain) {
51 | verify(chain).request()
52 | verify(chain).proceed(invalidRequest)
53 | verify(chain).request()
54 | verify(chain).proceed(validRequest)
55 | }
56 | `Verify no further interactions` on chain
57 | Verify on reauthorizer that reauthorizer.reauthorize() was called
58 | }
59 | }
--------------------------------------------------------------------------------
/app/src/test/java/com/jcminarro/authexample/internal/network/refresh/RefreshApiClientTest.kt:
--------------------------------------------------------------------------------
1 | package com.jcminarro.authexample.internal.network.refresh
2 |
3 | import com.github.tomakehurst.wiremock.client.WireMock.*
4 | import com.github.tomakehurst.wiremock.core.WireMockConfiguration
5 | import com.github.tomakehurst.wiremock.junit.WireMockRule
6 | import com.jcminarro.authexample.*
7 | import com.jcminarro.authexample.internal.network.APIIOException
8 | import org.amshove.kluent.`should equal to`
9 | import org.junit.Before
10 | import org.junit.Rule
11 | import org.junit.Test
12 |
13 | class RefreshApiClientTest{
14 |
15 | val VALID_REFRESH_TOKEN = "valid_refresh_token"
16 | val INVALID_REFRESH_TOKEN = "invalid_refresh_token"
17 |
18 | private lateinit
19 | var refreshApiClient: RefreshApiClient
20 |
21 | @Rule
22 | @JvmField
23 | val wiremockRule = WireMockRule(WireMockConfiguration.options().dynamicPort())
24 |
25 | @Before
26 | fun setUp() {
27 | refreshApiClient = RefreshApiClient(
28 | createRefeshEndpoint(EndpointMother.DEFAULT_API_HOST + wiremockRule.port()))
29 | }
30 |
31 | @Test
32 | fun `Should return an OAuth when refreshing token with a valid refresh token`() {
33 | stubFor(post(EndpointPath.REFRESH)
34 | .withRequestBody(equalToJson(createRequestBodyJson(VALID_REFRESH_TOKEN)))
35 | .willReturn(aResponse().withBody(
36 | createRefreshResponseJson(
37 | createRefreshResponse()))))
38 |
39 | val oAuth = refreshApiClient.refresh(VALID_REFRESH_TOKEN)
40 |
41 | oAuth.accessToken `should equal to` ResponseMother.REFRESH_MOTHERR_accessToken
42 | oAuth.refreshToken `should equal to` ResponseMother.REFRESH_MOTHERR_refreshToken
43 | }
44 |
45 | @Test(expected = APIIOException::class)
46 | fun `Should throw an exception when try to refresh token with an invalid refresh token`() {
47 | stubFor(post(EndpointPath.REFRESH)
48 | .withRequestBody(equalToJson(createRequestBodyJson(INVALID_REFRESH_TOKEN)))
49 | .willReturn(aResponse().withStatus(401)))
50 |
51 | refreshApiClient.refresh(INVALID_REFRESH_TOKEN)
52 | }
53 |
54 | private fun createRequestBodyJson(refreshToken: String) =
55 | """
56 | {
57 | "refreshToken": "$refreshToken"
58 | }
59 | """.trimIndent()
60 | }
--------------------------------------------------------------------------------
/app/src/test/java/com/jcminarro/authexample/internal/repository/QuoteRepositoryTest.kt:
--------------------------------------------------------------------------------
1 | package com.jcminarro.authexample.internal.repository
2 |
3 | import com.jcminarro.authexample.internal.model.Quote
4 | import com.jcminarro.authexample.internal.network.APIIOException
5 | import com.jcminarro.authexample.internal.network.quote.QuoteApiClient
6 | import com.nhaarman.mockito_kotlin.doReturn
7 | import com.nhaarman.mockito_kotlin.doThrow
8 | import org.amshove.kluent.*
9 | import org.junit.Test
10 |
11 | class QuoteRepositoryTest {
12 |
13 | private val quote = Quote("Linus Torvalds", "If you think your users are idiots only idiots will use it.")
14 | private val quoteApiClient: QuoteApiClient = mock()
15 | private val quoteRepository = QuoteRepository(quoteApiClient)
16 |
17 | @Test
18 | fun `Should return a random quote`() {
19 | When calling quoteApiClient.getRandomQuote() doReturn quote
20 |
21 | val result = quoteRepository.getRandomQuote()
22 |
23 | result.author `should equal to` quote.author
24 | result.message `should equal to` quote.message
25 | Verify on quoteApiClient that quoteApiClient.getRandomQuote() was called
26 | }
27 |
28 | @Test(expected = APIIOException::class)
29 | fun `Should throw an exception asking for a random quote`() {
30 | When calling quoteApiClient.getRandomQuote() doThrow APIIOException(mock())
31 |
32 | quoteRepository.getRandomQuote()
33 |
34 | Verify on quoteApiClient that quoteApiClient.getRandomQuote() was called
35 | }
36 | }
--------------------------------------------------------------------------------
/app/src/test/java/com/jcminarro/authexample/internal/repository/SessionRepositoryTest.kt:
--------------------------------------------------------------------------------
1 | package com.jcminarro.authexample.internal.repository
2 |
3 | import com.jcminarro.authexample.internal.localdatasource.SessionDatasource
4 | import com.jcminarro.authexample.internal.network.APIIOException
5 | import com.jcminarro.authexample.internal.network.OAuth
6 | import com.jcminarro.authexample.internal.network.login.LoginApiClient
7 | import com.jcminarro.authexample.internal.network.refresh.RefreshApiClient
8 | import com.nhaarman.mockito_kotlin.doReturn
9 | import com.nhaarman.mockito_kotlin.doThrow
10 | import org.amshove.kluent.*
11 | import org.junit.Before
12 | import org.junit.Test
13 |
14 | class SessionRepositoryTest {
15 |
16 | private val VALID_USERNAME = "Jc Miñarro"
17 | private val VALID_PASSWORD = "password"
18 | private val INVALID_USERNAME = "username"
19 | private val INVALID_PASSWORD = "1234"
20 | private val ACCESS_TOKEN = "ACCESS_TOKEN"
21 | private val VALID_REFRESH_TOKEN = "VALID_REFRESH_TOKEN"
22 | private val INVALID_REFRESH_TOKEN = "INVALID_REFRESH_TOKEN"
23 | private val sessionDatasource: SessionDatasource = mock()
24 | private val loginApiClient: LoginApiClient = mock()
25 | private val refreshApiClient: RefreshApiClient = mock()
26 | private val sessionRepository = SessionRepository(loginApiClient, refreshApiClient, sessionDatasource)
27 | private val validOAuth = OAuth(ACCESS_TOKEN, VALID_REFRESH_TOKEN)
28 | private val invalidOAuth = OAuth(ACCESS_TOKEN, INVALID_REFRESH_TOKEN)
29 | private val apiIoException = APIIOException(mock())
30 |
31 | @Before
32 | fun setUp() {
33 | When calling loginApiClient.login(VALID_USERNAME, VALID_PASSWORD) doReturn validOAuth
34 | When calling loginApiClient.login(INVALID_USERNAME, VALID_PASSWORD) doThrow apiIoException
35 | When calling loginApiClient.login(VALID_USERNAME, INVALID_PASSWORD) doThrow apiIoException
36 | When calling refreshApiClient.refresh(VALID_REFRESH_TOKEN) doReturn validOAuth
37 | When calling refreshApiClient.refresh(INVALID_REFRESH_TOKEN) doThrow apiIoException
38 | }
39 |
40 | @Test
41 | fun `Should store an OAuth when login with valid credential`() {
42 | val result = sessionRepository.login(VALID_USERNAME, VALID_PASSWORD)
43 |
44 | result.`should be true`()
45 | Verify on sessionDatasource that sessionDatasource.storeOAuthSession(validOAuth) was called
46 | }
47 |
48 | @Test(expected = APIIOException::class)
49 | fun `Should throw an exception when try to login with an invalid password`() {
50 | sessionRepository.login(VALID_USERNAME, INVALID_PASSWORD)
51 |
52 | `Verify no further interactions` on sessionDatasource
53 | }
54 |
55 | @Test(expected = APIIOException::class)
56 | fun `Should throw an exception when try to login with an invalid username`() {
57 | sessionRepository.login(INVALID_USERNAME, VALID_PASSWORD)
58 |
59 | `Verify no further interactions` on sessionDatasource
60 | }
61 |
62 | @Test
63 | fun `Should store an OAuth when refreshing with a valid refresh token receive an OAuth`() {
64 | When calling sessionDatasource.oAuthSession doReturn validOAuth
65 |
66 | val result = sessionRepository.refreshSession()
67 |
68 | result.`should be true`()
69 | Verify on sessionDatasource that sessionDatasource.oAuthSession was called
70 | Verify on refreshApiClient that refreshApiClient.refresh(VALID_REFRESH_TOKEN) was called
71 | Verify on sessionDatasource that sessionDatasource.storeOAuthSession(validOAuth) was called
72 | }
73 |
74 | @Test
75 | fun `Shouldn't store an OAuth when refreshing with an invalid refresh token receive an exception`() {
76 | When calling sessionDatasource.oAuthSession doReturn invalidOAuth
77 |
78 | val result = sessionRepository.refreshSession()
79 |
80 | result.`should be false`()
81 | Verify on sessionDatasource that sessionDatasource.oAuthSession was called
82 | Verify on refreshApiClient that refreshApiClient.refresh(INVALID_REFRESH_TOKEN) was called
83 | `Verify no further interactions` on sessionDatasource
84 | }
85 |
86 | @Test
87 | fun `Shouldn't call to refresh access token if there is no stored OAuth`() {
88 | val result = sessionRepository.refreshSession()
89 |
90 | result.`should be false`()
91 | Verify on sessionDatasource that sessionDatasource.oAuthSession was called
92 | `Verify no further interactions` on sessionDatasource
93 | `Verify no interactions` on refreshApiClient
94 | }
95 | }
--------------------------------------------------------------------------------
/app/src/test/java/com/jcminarro/authexample/login/LoginInteractorTest.kt:
--------------------------------------------------------------------------------
1 | package com.jcminarro.authexample.login
2 |
3 | import com.jcminarro.authexample.internal.interactor.AsyncInteractor
4 | import com.jcminarro.authexample.internal.network.APIIOException
5 | import com.jcminarro.authexample.internal.repository.SessionRepository
6 | import com.nhaarman.mockito_kotlin.doReturn
7 | import com.nhaarman.mockito_kotlin.doThrow
8 | import com.nhaarman.mockito_kotlin.spy
9 | import org.amshove.kluent.*
10 | import org.junit.Before
11 | import org.junit.Test
12 |
13 | class LoginInteractorTest {
14 |
15 | private val VALID_USERNAME = "Jc Miñarro"
16 | private val VALID_PASSWORD = "password"
17 | private val INVALID_USERNAME = "username"
18 | private val INVALID_PASSWORD = "1234"
19 | private val sessionRepository: SessionRepository = mock()
20 | private val loginInteractor = LoginInteractor(sessionRepository)
21 | private val callback: AsyncInteractor.Callback = spy()
22 | private val apiIoException = APIIOException(mock())
23 |
24 | @Before
25 | fun setUp() {
26 | When calling sessionRepository.login(VALID_USERNAME, VALID_PASSWORD) doReturn true
27 | When calling sessionRepository.login(INVALID_USERNAME, VALID_PASSWORD) doThrow apiIoException
28 | When calling sessionRepository.login(VALID_USERNAME, INVALID_PASSWORD) doThrow apiIoException
29 | }
30 |
31 | @Test
32 | fun `Should login successful with valid credential`() {
33 | loginInteractor.execute(LoginInteractor.Input(VALID_USERNAME, VALID_PASSWORD), callback)
34 |
35 | Verify on sessionRepository that sessionRepository.login(VALID_USERNAME, VALID_PASSWORD) was called
36 | Verify on callback that callback.onSuccess(true) was called
37 | `Verify no further interactions` on sessionRepository
38 | `Verify no further interactions` on callback
39 | }
40 |
41 | @Test
42 | fun `Should notify an error when try to login with an invalid password`() {
43 | loginInteractor.execute(LoginInteractor.Input(VALID_USERNAME, INVALID_PASSWORD), callback)
44 |
45 | Verify on sessionRepository that sessionRepository.login(VALID_USERNAME, INVALID_PASSWORD) was called
46 | Verify on callback that callback.onError(apiIoException) was called
47 | `Verify no further interactions` on sessionRepository
48 | `Verify no further interactions` on callback
49 | }
50 |
51 | @Test
52 | fun `Should notify an error when try to login with an invalid username`() {
53 | loginInteractor.execute(LoginInteractor.Input(INVALID_USERNAME, VALID_PASSWORD), callback)
54 |
55 | Verify on sessionRepository that sessionRepository.login(INVALID_USERNAME, VALID_PASSWORD) was called
56 | Verify on callback that callback.onError(apiIoException) was called
57 | `Verify no further interactions` on sessionRepository
58 | `Verify no further interactions` on callback
59 | }
60 | }
--------------------------------------------------------------------------------
/app/src/test/java/com/jcminarro/authexample/quote/GetQuoteInteractorTest.kt:
--------------------------------------------------------------------------------
1 | package com.jcminarro.authexample.quote
2 |
3 | import com.jcminarro.authexample.internal.interactor.AsyncInteractor
4 | import com.jcminarro.authexample.internal.model.Quote
5 | import com.jcminarro.authexample.internal.network.APIIOException
6 | import com.jcminarro.authexample.internal.repository.QuoteRepository
7 | import com.nhaarman.mockito_kotlin.check
8 | import com.nhaarman.mockito_kotlin.doReturn
9 | import com.nhaarman.mockito_kotlin.doThrow
10 | import com.nhaarman.mockito_kotlin.spy
11 | import org.amshove.kluent.*
12 | import org.junit.Test
13 |
14 | class GetQuoteInteractorTest {
15 |
16 | private val quoteRepository: QuoteRepository = mock()
17 | private val getQuoteInteractor = GetQuoteInteractor(quoteRepository)
18 | private val callback: AsyncInteractor.Callback = spy()
19 | private val apiIoException = APIIOException(mock())
20 | private val quote = Quote("Linus Torvalds", "If you think your users are idiots only idiots will use it.")
21 |
22 | @Test
23 | fun `Should return a random quote`() {
24 | When calling quoteRepository.getRandomQuote() doReturn quote
25 |
26 | getQuoteInteractor.execute(null, callback)
27 |
28 | Verify on callback that callback.onSuccess(check {
29 | it.message `should equal to` quote.message
30 | it.author `should equal to` quote.author
31 | }) was called
32 | `Verify no further interactions` on callback
33 | Verify on quoteRepository that quoteRepository.getRandomQuote() was called
34 | `Verify no further interactions` on quoteRepository
35 | }
36 |
37 | @Test
38 | fun `Should notify an error asking for a random quote`() {
39 | When calling quoteRepository.getRandomQuote() doThrow apiIoException
40 |
41 | getQuoteInteractor.execute(null, callback)
42 |
43 | Verify on callback that callback.onError(apiIoException) was called
44 | `Verify no further interactions` on callback
45 | Verify on quoteRepository that quoteRepository.getRandomQuote() was called
46 | `Verify no further interactions` on quoteRepository
47 | }
48 | }
--------------------------------------------------------------------------------
/app/src/test/java/com/jcminarro/authexample/startup/RefreshSessionInteractorTest.kt:
--------------------------------------------------------------------------------
1 | package com.jcminarro.authexample.startup
2 |
3 | import com.jcminarro.authexample.internal.interactor.AsyncInteractor
4 | import com.jcminarro.authexample.internal.repository.SessionRepository
5 | import com.nhaarman.mockito_kotlin.doReturn
6 | import com.nhaarman.mockito_kotlin.spy
7 | import org.amshove.kluent.*
8 | import org.junit.Test
9 |
10 | class RefreshSessionInteractorTest {
11 |
12 | private val sessionRepository: SessionRepository = mock()
13 | private val refreshSessionInteractor = RefreshSessionInteractor(sessionRepository)
14 | private val callback: AsyncInteractor.Callback = spy()
15 |
16 | @Test
17 | fun `Should refresh session`() {
18 | When calling sessionRepository.refreshSession() doReturn true
19 |
20 | refreshSessionInteractor.execute(null, callback)
21 |
22 | Verify on sessionRepository that sessionRepository.refreshSession() was called
23 | Verify on callback that callback.onSuccess(true) was called
24 | `Verify no further interactions` on sessionRepository
25 | `Verify no further interactions` on callback
26 | }
27 |
28 | @Test
29 | fun `Shouldn't refresh session`() {
30 | When calling sessionRepository.refreshSession() doReturn false
31 |
32 | refreshSessionInteractor.execute(null, callback)
33 |
34 | Verify on sessionRepository that sessionRepository.refreshSession() was called
35 | Verify on callback that callback.onSuccess(false) was called
36 | `Verify no further interactions` on sessionRepository
37 | `Verify no further interactions` on callback
38 | }
39 | }
--------------------------------------------------------------------------------
/app/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker:
--------------------------------------------------------------------------------
1 | mock-maker-inline
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | import com.jcminarro.Dependencies
2 |
3 | buildscript {
4 | repositories {
5 | google()
6 | jcenter()
7 | }
8 | dependencies {
9 | classpath Dependencies.androidBuildToolGradlePlugin
10 | classpath Dependencies.kotlinGradlePlugin
11 |
12 | // NOTE: Do not place your application dependencies here; they belong
13 | // in the individual module build.gradle files
14 | }
15 | }
16 |
17 | allprojects {
18 | repositories {
19 | google()
20 | jcenter()
21 | repositories {
22 | maven { url "https://jitpack.io" }
23 | }
24 | }
25 | }
26 |
27 | subprojects {
28 | apply from: new File(rootDir, "gradle/checkstyle.gradle")
29 | afterEvaluate {
30 | tasks.findByName('check').dependsOn('checkstyle')
31 | }
32 | }
33 |
34 | task clean(type: Delete) {
35 | delete rootProject.buildDir
36 | }
37 |
--------------------------------------------------------------------------------
/buildSrc/src/main/groovy/com/jcminarro/Dependencies.groovy:
--------------------------------------------------------------------------------
1 | package com.jcminarro;
2 |
3 | class Dependencies {
4 |
5 | private static String KOTLIN_VERSION = '1.1.51'
6 | private static String ANDROID_BUILD_TOOL_VERSION = '3.0.0'
7 | private static String APP_COMPAT_VERSION = '26.1.0'
8 | private static String JUNIT_VERSION = '4.12'
9 | private static String ANDROID_TEST_RUNNER_VERSION = '1.0.1'
10 | private static String ESPRESSO_VERSION = '3.0.1'
11 | private static String DAGGER_VERSION = '2.8'
12 | private static String BUTTERKNIFE_VERSION = '8.5.1'
13 | private static String RETROFIT_VERSION = '2.1.0'
14 | private static String OK_HTTP_VERSION = '3.5.0'
15 | private static String OK2CURL_VERSION = '0.3.2'
16 | private static String MOKITO_VERSION = '2.9.0'
17 | private static String KLUENT_VERSION = '1.28'
18 | private static String MOKITO_KOTLIN_VERSION = '1.5.0'
19 | private static String WIREMOCK_VERSION = '2.8.0'
20 | private static String MOCKWEBSERVER_VERSION = OK_HTTP_VERSION
21 | private static String ANDROID_SUPPORT_VERSION = '26.1.0'
22 |
23 | static String kotlinGradlePlugin = "org.jetbrains.kotlin:kotlin-gradle-plugin:$KOTLIN_VERSION"
24 | static String androidBuildToolGradlePlugin = "com.android.tools.build:gradle:$ANDROID_BUILD_TOOL_VERSION"
25 | static String kotlinSTDLib = "org.jetbrains.kotlin:kotlin-stdlib-jre7:$KOTLIN_VERSION"
26 | static String appCompat = "com.android.support:appcompat-v7:$APP_COMPAT_VERSION"
27 | static String jUnit = "junit:junit:$JUNIT_VERSION"
28 | static String AndroidTestRunner = "com.android.support.test:runner:$ANDROID_TEST_RUNNER_VERSION"
29 | static String espresso = "com.android.support.test.espresso:espresso-core:$ESPRESSO_VERSION"
30 | static String dagger = "com.google.dagger:dagger:$DAGGER_VERSION"
31 | static String daggerCompiler = "com.google.dagger:dagger-compiler:$DAGGER_VERSION"
32 | static String butterKnife = "com.jakewharton:butterknife:$BUTTERKNIFE_VERSION"
33 | static String butterKnifeCompiler = "com.jakewharton:butterknife-compiler:$BUTTERKNIFE_VERSION"
34 | static String retrofit = "com.squareup.retrofit2:retrofit:$RETROFIT_VERSION"
35 | static String retrofitGsonConverter = "com.squareup.retrofit2:converter-gson:$RETROFIT_VERSION"
36 | static String okHttpLogger = "com.squareup.okhttp3:logging-interceptor:$OK_HTTP_VERSION"
37 | static String ok2curl = "com.github.mrmike:Ok2Curl:$OK2CURL_VERSION"
38 | static String mockito = "org.mockito:mockito-core:$MOKITO_VERSION"
39 | static String kotlinTestJunit = "org.jetbrains.kotlin:kotlin-test-junit:$KOTLIN_VERSION"
40 | static String kluent = "org.amshove.kluent:kluent:$KLUENT_VERSION"
41 | static String mockitoKotlin = "com.nhaarman:mockito-kotlin:$MOKITO_KOTLIN_VERSION"
42 | static String wiremock = "com.github.tomakehurst:wiremock:$WIREMOCK_VERSION"
43 | static String mockwebserver = "com.squareup.okhttp3:mockwebserver:$MOCKWEBSERVER_VERSION"
44 | static String supportDesign = "com.android.support:design:$ANDROID_SUPPORT_VERSION"
45 | }
--------------------------------------------------------------------------------
/config/checkstyle/checkstyle.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
--------------------------------------------------------------------------------
/config/checkstyle/suppressions.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/extras/settings.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JcMinarro/AuthExample-Android/4240164a450e91aeaf12134e7afac55b1860d856/extras/settings.jar
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | org.gradle.jvmargs=-Xmx1536m
13 |
14 | # When configured, Gradle will run in incubating parallel mode.
15 | # This option should only be used with decoupled projects. More details, visit
16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
17 | # org.gradle.parallel=true
18 |
--------------------------------------------------------------------------------
/gradle/checkstyle.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'checkstyle'
2 |
3 | checkstyle {
4 | toolVersion = "8.1"
5 | ignoreFailures = false
6 | }
7 |
8 | task('checkstyle', type: Checkstyle) {
9 | configFile = rootProject.file('config/checkstyle/checkstyle.xml')
10 | configProperties.checkstyleSuppressionsPath = rootProject.file("config/checkstyle/suppressions.xml").absolutePath
11 | source 'src'
12 | showViolations false
13 | include '**/*.java'
14 | classpath = files()
15 | }
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JcMinarro/AuthExample-Android/4240164a450e91aeaf12134e7afac55b1860d856/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.8-bin.zip
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------