I have the following code,
#Singleton
class LoginHandler {
private Observable<Response<User>> loginObservable;
private void attemptLogin(LoginRequest loginRequest) {
LoginSubscriber loginSubscriber = new LoginSubscriber();
loginObservable = api.login(loginRequest);
loginObservable
.observeOn(AndroidSchedulers.mainThread())
.subscribe(loginSubscriber);
}
}
with
#POST(PATH_AUTH)
Observable<Response<User>> login(#Body LoginRequest loginRequest);
This gets executed each time the user wants to login from a deep link.
I can log in and log out two times, but on the third time, the request just does not get executed. I also checked with the Charles Proxy tool, if the request is actually sent, but there was nothing to see.
Logging the loginObservable object shows that each api.login() call returns a different Observable object, which is correct for the subscription to be executed. Logging at doOnSubscribe(() -> ...) is also executed, just not the request itself.
I've browsed through SO and the only solution I found was to add a timeout(...) so that the user is able to get back to the login mask, once the timeout strikes. However when retrying to log in, the problem is still present.
So my question is, how could I debug this and find out, what is happening under the hood of retrofit and why is the request not executed after the third attempt?
Thank you very much in advance
Related
So I have these two parallel calls using zip operator. I am making two network calls. I have the following questions:
How can I handle the individual errors correctly
If the first call fails I want to be able to exit the session but if the second network call fails I want to allow the user to still go through the session. I am seeing a 404 in my second network call in the zip and the entire chain fails with an error. I want it to be able to handle success and failure
valid session
response 1: success
response 2: failure
invalid session
response 1: failure
response 2: success
invalid session
both endpoints fail
Single.zip(
api1.getData().doOnError {
// handle error : exit right away
},
api2.getData().doOnError {
// handle error: Set profile data to be empty but when user tries to see the profile information show error at a later point in time based oaths response
// got 404
},
{ response1: String, response2: CustomObject ->
Pair(response1, response2)
}
)
.subscribeOn(Schedulers.io())
.subscribe(
{
handleResponse1(it.first)
handleRespone2(it.second)
},
{
Timber.d("it : $it")
// api1 use success response: is it even possibel to get that in the iterator
// api 2 throwing 404 here
}
)
From your question, I see that you want to continue even if one of the API fails without failing the whole chain. This can be done in the following way
If you want to exit on the first API call no need to handle any Error there.(You will get an error in throwable)
Whenever the second API fails use onErrorReturnItem to return some empty response
Single.zip(
api1.getData().subscribeOn(Schedulers.io()),
api2.getData().subscribeOn(Schedulers.io())
.onErrorReturnItem(new Response())
.......
The new Response() here is just an empty object of the response of type that you were expecting. Even if the second API fails here you will get whatever you are returing
If the first API fails here you will get a callback in Throwable or you can continue to handle error in doOnError
Before coroutines I used callbacks and debugging always gave me a lot of information. I could get the url my API call is going to, Headers I put into Request, interceptors I used, etc..
Now I use coroutines. All I can get when debugging is the final result of request (fail/sucess) with the result data. Nothing of all these valuable info I need is not there.
shortened example of my code:
restService.getVersionInfo().getResult(
success = {
when {
checkIsMandatory(it.lastMandatoryVersion) -> status.postValue(
Status.NewVersionInfo(it.description, true, it.url)
)
else -> initialization()
}
},
error = {
initialization()
}
I pout breakpoints to error or sucess. Am I missing something or coroutines really have this disadvantage. Please inform me
I have an Android application in which I'm using Azure AD B2C to authenticate users. Users login and logout of the application as needed. I would like to give the user the option to delete their own account.
I understand that I need to use the Azure AD Graph API to delete the user. This is what I have so far:
According to this link, it looks like deleting a user from a personal account (which is what the B2C users are using) is not possible. Is that correct?
Here's my code snippet for the Graph API call. Feel free to ignore it if I'm off track and there is a better way to solve this.
I believe I need a separate access token than what my app currently has (as the graph API requires other API consent). So, I'm getting the access token as follows:
AcquireTokenParameters parameters = new AcquireTokenParameters.Builder()
.startAuthorizationFromActivity(getActivity())
.fromAuthority(B2CConfiguration.getAuthorityFromPolicyName(B2CConfiguration.Policies.get("SignUpSignIn")))
.withScopes(B2CConfiguration.getGraphAPIScopes())
.withPrompt(Prompt.CONSENT)
.withCallback(getGraphAPIAuthCallback())
.build();
taxApp.acquireToken(parameters);
In the getGraphAPIAuthCallback() method, I'm calling the Graph API using a separate thread (in the background):
boolean resp = new DeleteUser().execute(authenticationResult.getAccessToken()).get();
Finally, in my DeleterUser() AsyncTask, I'm doing the following:
#Override
protected Boolean doInBackground(String... aToken) {
final String asToken = aToken[0];
//this method will be running on background thread so don't update UI from here
//do your long running http tasks here,you dont want to pass argument and u can access the parent class' variable url over here
IAuthenticationProvider mAuthenticationProvider = new IAuthenticationProvider() {
#Override
public void authenticateRequest(final IHttpRequest request) {
request.addHeader("Authorization",
"Bearer " + asToken);
}
};
final IClientConfig mClientConfig = DefaultClientConfig
.createWithAuthenticationProvider(mAuthenticationProvider);
final IGraphServiceClient graphClient = new GraphServiceClient.Builder()
.fromConfig(mClientConfig)
.buildClient();
try {
graphClient.getMe().buildRequest().delete();
} catch (Exception e) {
Log.d(AccountSettingFragment.class.toString(), "Error deleting user. Error Details: " + e.getStackTrace());
}
return true;
}
Currently, my app fails when trying to get an access token with a null pointer exception:
com.microsoft.identity.client.exception.MsalClientException: Attempt to invoke virtual method 'long java.lang.Long.longValue()' on a null object reference
Any idea what I need to do to provide the user the option to users to delete their own account? Thank you!
Thanks for the help, #allen-wu. Due to his help, this azure feedback request and this azure doc, I was able to figure out how to get and delete users silently (without needing intervention).
As #allen-wu stated, you cannot have a user delete itself. So, I decided to have the mobile app call my server-side NodeJS API when the user clicks the 'Delete Account' button (as I do not want to store the client secret in the android app) and have the NodeJS API call the Azure AD endpoint to delete the user silently. The one caveat is that admin consent is needed the first time you try to auth. Also, I have only tested this for Graph API. I'm not a 100% sure if it works for other APIs as well.
Here are the steps:
Create your application in your AAD B2C tenant. Create a client secret and give it the following API permissions: Directory.ReadWrite.All ;
AuditLog.Read.All (I'm not a 100% sure if we need the AuditLog permission. I haven't tested without it yet).
In a browser, paste the following link:
GET https://login.microsoftonline.com/{tenant}/adminconsent?
client_id=6731de76-14a6-49ae-97bc-6eba6914391e
&state=12345
&redirect_uri=http://localhost/myapp/permissions
Login using an existing admin account and provide the consent to the app.
Once you've given admin consent, you do not have to repeat steps 1-3 again. Next, make the following call to get an access token:
POST https://login.microsoftonline.com/{B2c_tenant_name}.onmicrosoft.com/oauth2/v2.0/token
In the body, include your client_id, client_secret, grant_type (the value for which should be client_credentials) and scope (value should be 'https://graph.microsoft.com/.default')
Finally, you can call the Graph API to manage your users, including deleting them:
DELETE https://graph.microsoft.com/v1.0/users/{upn}
Don't forget to include the access token in the header. I noticed that in Postman, the graph api had a bug and returned an error if I include the word 'Bearer' at the start of the Authorization header. Try without it and it works. I haven't tried it in my NodeJS API yet, so, can't comment on it so far.
#allen-wu also suggested using the ROPC flow, which I have not tried yet, so, cannot compare the two approaches.
I hope this helps!
There is a line of code: graphClient.getUsers("").buildRequest().delete();
It seems that you didn't put the user object id in it.
However, we can ignore this problem because Microsoft Graph doesn't allow a user to delete itself.
Here is the error when I try to do it.
{
"error": {
"code": "Request_BadRequest",
"message": "The principal performing this request cannot delete itself.",
"innerError": {
"request-id": "8f44118f-0e49-431f-a0a0-80bdd954a7f0",
"date": "2020-06-04T06:41:14"
}
}
}
I have a Retrofit call where I want to handle HTTP and Retrofit errors when calling an API.
So when a failure happens, I need to cache the request into a RoomDB/SQLite for when the API comes back online, or connection improves there is a routine that sends all those requests to the API.
x.enqueue(object : Callback<PayloadResponse> {
override fun onResponse(
call: Call<PayloadResponse>,
response: Response<PayloadResponse>
) {
...
val errorMessage = when {
response.code() != HttpURLConnection.HTTP_OK -> {
// Need the original Payload here so I can insert that data into RoomDB/SQLite
"An error occured duing API Call (NOT OK) &{response.code()}"
}
...
Same situation I need for the onFailure() callback.
Can I access the original request in these contexts? If so how?
Once I have encountered the same problem. My approach was debugging first parameter of retrofit callback's onResponse and onFailure.
call: Call<PayloadResponse>
call.request()
It contains all kind of information about your call / request e.g url, parameters, method. However, retrieving request data is not straightforward, consistent, and prone to bugs.
Then, I started using Kotlin coroutine which gives async process with sync nature of coding.
interface RetrofitApi{
#GET("your-route")
suspend fun sampleApiMethod(request:SampleRequest):Response<SampleCustomObject>
}
Your retrofit api methods may look like above.
CoroutineScope(Dispatchers.Main).launch {
val request = SampleRequest()
val deferred = async { apiService.sampleApiMethod(request) }
val result = deferred.await()
if(result.code() == 200){
// Do something
}else{
// Cache SampleRequest()
}
}
You can modify as much as to make it look better and utilize optimized light weight threads. It is one of the ways for me to handle such situations. It's just a hint, you may modify to have structurally and architecturally correct design in your project.
I am using mockwebserver to mock request and response for my android app. I am testing a login feature which goes through a series of 4 service calls.
Get access token
Re-direct
Get user info (different base url)
Get some other stuff (original base url)
I am trying to mock the response of the redirected call. Here is my code:
#Test
public void testSuccessfulLogin() throws Exception {
// Post
server.enqueue(new MockResponse()
.setResponseCode(HTTP_OK)
.setBody(getStringFromFile(getInstrumentation().getContext(), "access_token.json")));
// Redirect
server.enqueue(new MockResponse().setResponseCode(HTTP_MOVED_TEMP));
// GET user info
server.enqueue(new MockResponse().setResponseCode(HTTP_OK).setBody(getStringFromFile(getInstrumentation().getContext(), "userinfo.json")));
// GET some other stuff
server.enqueue(new MockResponse().setResponseCode(HTTP_OK)
.setBody(getStringFromFile(getInstrumentation().getContext(), "sts.json")));
// Init call
loginWithoutWaiting(Data.serviceLoginUsername, Data.serviceLoginPassword);
// Debug (need to loop 4 times to get all 4 call paths)
RecordedRequest request = server.takeRequest();
request.getPath();
}
My test fails at the Redirect code. I cannot login. I have found some hints here but I do not fully understand what is going on, thus can't make it work at the moment.
It turned out to be quite easy. In the call that makes redirect, create a new mocked response with response code 302 and header with location url. The next call will use that location url.
case "/userinfo":
return new MockResponse().setResponseCode(HTTP_MOVED_TEMP).setHeader("Location", "/api-test.com/users");
case "/api-test.com/users":
return new MockResponse().setBody("{}")).setResponseCode(HTTP_OK);