Lately I have been facing a problem that I wasn't able to came out with a solution, I am building an android application following the Clean Architecture and everything was going fine until I had to think about the authentication role.
I have this structure (layers) on my app:
[ui](activities and fragments) -> [presentation](view models) -> [domain](use case) -> data -> [remote, cache, database].
Now, let's suppose that I want to log in into my app, first I'll go through the login screen and put users credentials, after that I'll call the LoginViewModel and then the LoginUseCase passing the email and password. In its turn, the use case will call the repository, let's say, authenticate and then I'll make a request to the backend with the credentials, if everything's ok then I'll receive back a token that I should store in some fashion, the problem start here, I've created a interceptor that is responsible to get the token from the header, but I have to save it and for that I need to access the shared preferences, is correct to have access to it inside my interceptor? And in every request I had to send it to my backend, what's the best approach ?
I also saw this tutorial https://medium.com/#tsaha.cse/advanced-retrofit2-part-2-authorization-handling-ea1431cb86be but I think that it's not correct to have access to database inside your application class, am I wrong?
Thank you all for reading this, I'm struggling to find out the best approach, so any help are welcome.
it's not really related to CleanArchitecture. Here're answers for your questions:
I have to save it and for that I need to access the shared preferences, is correct to have access to it inside my interceptor?
-> Yes, you should save it to SharedPreferences, but you should not access SharedPreferences inside your interceptor. You should make your interceptor Singleton and create a new setHeaderToken(String token) function in your interceptor. After authorizing, you can set header token to your interceptor. Something like:
class MyInterceptor{
String token = null;
public void setHeaderToken(String token){
//do set...
};
#Override
public Response intercept(Chain chain) throws IOException {
if(token == null) //do nothing
else // do add header
}
}
// add the singleton Interceptor to your OkHttp Client Builder and use it.
You should not connect Application class with your database directly. You should use a domain layer to do so instead.
Related
The Premise
I'm working on a simple app where I want to list out a user's GitHub repos in a RecyclerView. I'm using this as my endpoint while building this.
The Problem
The problem I'm facing is that the GitHub API returns only 30 repos in one go. To get more, I can append a per_page=100 (100 is the maximum) to my query string; But, what do we do about users with more than 100 repos?
The solution the API docs provide is to get the next; url from the "Link" response header to make a second API call.
How does one go about this? Thanks!
The Response class will let you access the headers. The GitHub client would have a method like:
#GET("/search/code")
Observable<Response<T>> getUser(...)
Inside onNext(), you'd do something like:
#Override
public void onNext(Response<T> response) {
String next = response.headers().get("next");
}
I have an app that authenticates using OAuth2 and fetches data from a RESTful service using Retrofit. Now, I have the token retrieval and refreshing up and running. The token is refreshed like so (schedulers omitted):
// Each Retrofit call observable is "wrapper" using this method
protected <T> Observable<T> wrap(#NonNull final Observable<T> page) {
return authenticate()
.concatMap(token -> page)
.onErrorResumeNext(throwable -> {
Log.w(TAG, "wrap: ErrorResumeNext", throwable);
return refreshAccessToken()
.flatMap(accessToken -> page);
}));
}
// Retrieves the access token if necessary
Observable<AccessToken> authenticate() {
// Already have token
if(accessToken != null) return Observable.just(accessToken);
// No token yet, fetch it
return api.getAccessToken(...);
}
// Refreshes the token
Observable<AccessToken> refreshAccessToken() {
return api.refreshToken(...);
}
This works, but in some cases, multiple requests are sent at once and they both invoke the refreshing process - basically my app ends up refreshing the token as many times as there were requests at the moment.
So, the question is: How do I ensure that when the token needs to be refreshed, it is done only once, no matter how many ongoing requests require the token to be refreshed? Can I somehow make the other requests "wait" until the first request sucessfully invoked and retrieved the new token?
We have accomplished this behavior using a hot observable for refreshing the token and providing access to its instance for all requests that failed to authenticate.
Use share operator to turn your basic cold observable for refreshing a token into a hot one, so every other subscriber shares its result. Once the request comes back, all awaiting observers gets notifies and in that moment (in the operator chain it comes right before share() into a callback for doOnUnsubscribe) destroy the refreshing observable instance so the next subscriber will create new one. All this can be easily achieved by a singleton pattern, where you wrap the refreshing observable into a singleton wrapper class and just ask for it through getInstance(). If there is no request going on -- the instance is null -- getInstance should create a new one.
There are some other things you need to take care of, error during refreshing and invalidating the token all together for example, but these are the basics.
I don't have much time right now to elaborate more on this, but if you will encounter some trouble implementing this by your own, leave a comment and I will post some code examples by tomorrow. They wouldn't make much sense without a context.
I am using RxJava on an Android project and want to make sure I'm implementing something correctly.
I am using an Observable to login to a server. After the login occurs I may want to save the username on the client side so I'm using the doOnNext() operator to do this. Here's an example of what this looks like:
Observable<Response<Void>> doLogin(Observable<Response<Void>> retainedObservable, Subscriber subscriber, String username, String password, boolean saveUsername) {
if (retainedObservable == null) {
retainedObservable = networkLayer.loginWithcredentials(username, password)
.doOnNext(new Action1<Response<Void>>() {
#Override
public void call(Response<Void> voidResponse) {
if (saveUsername) {
databaseLayer.saveUsername(username).subscribe();
} else {
databaseLayer.saveUsername(null).subscribe();
}
}
})
.cache();
}
mSubscription = retainedObservable.subscribeOn().observeOn().subscribe(subscriber);
return retainedObservable;
}
If the device goes through a configuration change before the login finishes I want to be able to resubscribe to the observable that was running so that I don't have to ask the user to reenter their credentials of press the login button again.
To accomplish this I initially call doLogin(...) and temporarily save the returned observable in a data holder class. If the device goes through a configuration change I can grab the already created observable from the data holder class and resubscribe to it. This works great, my only hang up is that I need to also temporarily save off the username, password and saveUsername flag. This is easy enough to do, however I'm wondering if there's a way I can leverage RxJava to hold these values in the Observable and still have access to them when I need them in doOnNext(...). If I don't do this and just resubscribe to the Observable then when doOnNext(...) runs I won't have values for the saveUsername flag or the username which is obviously bad.
It would be really nice to only have to retain the Observable and somehow have it retain the username, password and saveUsername flag. Is there a way to accomplish this in RxJava or is the way I'm currently doing it the way it needs to be done if I want those variables to be retained during a configuration change?
Essentially, yes. RxJava uses a functional, stateless API, so you're not really able (or supposed) to attach additional data to an observable aside from the values it returns.
As with functional languages, there are generally two ways you can go about this:
You can either capture additional values in a closure, similar to how
you are doing it now. But obviously those values will only be
accessible within the scope of said closure, so you can't return an
Observable from a method and still access variables that were used to
create it later.
If you do need to access that data at a later point, the usual approach is to create some kind of result object that contains all your data. For example a "LoginResult" class, that contains your response as well as the original login data.
I am new at android testing and I'm running into a problem. I am using RxJava and to test the UI I am using an IdlingResource. While idling resource is busy i cannot test UI.
For example: I have a button. onClick I'm doing a request. While requesting the button disables. After request the button is in enabled state. I want to test the following 3 steps:
Button is enabled before request
Button is disabled while requesting (onCLick)
Button is enabled when requesting ends and response message returns
I would be very very happy if you can help me in this issue...
If you need more information about my issue let me know it. I will edit my post
As I understood, you're trying to test your UI. If so, please, make sure, that you do it in right way:
1). You don't do REAL request.
Please, understand, that your test must always have same behaviour in similar situations. In other words, it must give same result, you're passing same input parameters.
Your input parameters for now:
1.1). Button is enabled before request
1.2). Button disabled during the request
1.3). Buttons enabled after request
As you can see from this list, you don't need to do a real request. It doesn't matter for you, what server will return you (error or success). You even don't need a server for this. All what you need, is just "something", that behaves like a real server. In other words, you have to mock your API client.
I suppose that you're using retrofit. If no, you have to create the interface wrapper for your client. If you're using retrofit, you just need to mock your interface.
Let's suppose, you have next interface:
public interface ApiClient{
#GET("/items")
Observable<MyResponse> doSomeRequest();
}
How do you usually create your API client:
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com/")
.build();
ApiClient service = retrofit.create(ApiClient.class);
How you should do it in tests:
import static org.mockito.Mockito.*;
and in test method:
ApiClient apiMock = mock(ApiClient.class);
when(apiMock.doSomeRequest())
.thenReturn(Observable.just(fakeResponse));
or
ApiClient apiMock = mock(ApiClient.class);
when(apiMock.doSomeRequest())
.thenReturn(Observable.defer(new Func0<Observable<MyResponse>>() {
#Override
public Observable<MyResponse> call() {
try{
Thread.sleep(2 * 1000) //2 seconds
}catch(Exception e){
return Observable.error(e);
}
return Observable.just(fakeResponse);
}
}));
P.S. Retrofit adds .subscribeOn(Schedulers.io()) to all Observable's by default. This mocked object doesn't do it. So, please, don't forget to add .subscribeOn(Schedulers.io()) in your code, or apply it to the result of Observable.defer(...)
In code above it will look like:
when(apiMock.doSomeRequest())
.thenReturn(Observable.defer(...).subscribeOn(Schedulers.io()));
And you should pass apiMock to Activity / Fragment which you try to test.
How to do it? See #2.
2). Use DI (dependency injection)
I will not write a lot about it.
I just recomend you to read the documentation on http://google.github.io/dagger/
And especially, how to organise project in way, when you can use real implementaions for production, and mock implementations for testing:
http://google.github.io/dagger/testing.html
In other words, when you're going to build app for usage, you provide real dependencies(in your case it will be real implementation of ApiClient), and when you're going to test some UI or business logic, you pass mock dependencies, which have behaviour specified before the test by you.
This is all, what I wanted to tell you. Hope this helped, and let me know if you have any other questions.
Small addition to Alexander's answer. I would use a Subject for "mocked" api. This allows you to control execution.
//setup your test
Subject<Response,Response> stubResponse = AsyncSubject.create();
ApiClient apiMock = mock(ApiClient.class);
when(apiMock.doSomeRequest()).thenReturn(stubResponse.asObservable());
//check first condition that button is enabled before executing action
//click on button
//test your second condition that button is disabled while waiting for response
stubResponse.onNext(fakeResponse); //return fake response
stubResponse.onCompleted();
//test your third condition that button is enabled when you get response back
Remark. Never use sleep in your test. It will slow down your tests and add flakiness.
I am creating app that consumes REST Api. My API has ability to login/logout in order to access private data. I am creating consumer(client Android app) with Retrofit + Robospice + Jackson.
Everything was okey, but than authorization part came into play.
I need to provide token and other credentials in my request Authorization Header.
This can be easily done by using RequestInterceptor. Here is complete tutorial how to use basic Authentication.
Basic Authentication with Retrofit .
It is clear how to implement this. But in my case I also have resources that can be accessed without credentials.
Here is part of my API declared with Retrofit Annotations
public interface MalibuRestApi {
//Doesn't need credentials
#GET("/store/categories")
Category.List categoryList();
//Doesn't need credentials
#GET("/store/categories/{id}/products")
Product.List productList(#Path("id")int categoryId);
// Needs credentials
#POST("/store/users/{id}/logout")
// Needs credentials
User logout(#Path("id") int id,#Body Credentials userCredentials);
// Needs credentials !!!!!!!!!!!!!!!!!!!!
#POST("/store/users/{id}/orders/")
void makeAnOrder(#Path("id") int userId,#Body Order order,Callback<Void> callback);
}
Please have a look on makeAnOrder method. It uses POST body to pass details about order. So combining credentials and order seems horrible and not efficient, and I won't use it under no circumstances.
It is possible to use interceptor.
builder.setRequestInterceptor(new RequestInterceptor() {
#Override
public void intercept(RequestFacade request) {
if
String token = .... // getting token.
request.addHeader("Accept", "application/json");
request.addHeader("Authorization",token);
}
});
I can filter requests and add Auth headers where I need them according to request URL, but ......
According to the discussion here.
#JakeWharton
The relative URL is not exposed in the request interceptor because it
may not be fully resolved yet. A request interceptor has the ability
to perform path replacements and append query parameter
There is one possible workaround.
#powerje
I'm not sure how to access the relative URL in RequestInterceptor but
the solution I used in a similar situation was to check my UserManager
(a global which manages the currently logged in user) to see if a user
is logged in, if they are I add the auth header, otherwise I don't.
I have already similar class Session manager that is created in custom Application class, so I assume that it will live until application(linux dalvik/art process) is destroyed.
So it is possible to do something like this.
builder.setRequestInterceptor(new RequestInterceptor() {
#Override
public void intercept(RequestFacade request) {
sessionManager =(SessionProvider) getApplicationContext().getSessionManager();
if(sessionManager.userLoggedIn) {
String token = .... // getting token.
request.addHeader("Accept", "application/json");
request.addHeader("Authorization",token);
}
}
I haven't tested this yet, but it pretends to work.
In this case redundant headers are passed to public resource requests, that don't really require them.
So maybe this can be a sort of solution not a question, but I really need some advice about any other ways (maybe not better) to solve this problem.
I will be grateful for any help.