I want to authenticate a user (using his username and password) in an Android App using aerogear with a server using Keycloak. I haven't been able to do it, help me please.
I currently can authenticate the user without aerogear, but I want to use this library since it can help me to refresh the token when is needed.
I authenticate the user making a POST call to the server like this (but from android):
curl -X POST http://127.0.0.1:8080/auth/realms/example/protocol/openid-connect/token
-H "Content-Type: application/x-www-form-urlencoded" -d "username=auser" -d 'password=apassword' -d 'grant_type=password'
-d 'client_id=clientId' -d 'client_secret=secret'
So the information I have is:
Authentication URL, ie http://127.0.0.1:8080/auth/realms/example/protocol/openid-connect/token
username, the username of the user
password, the password of the user
client_id, and client_secret of the Keycloak server
What I have tried with Aerogear is this:
private void authz() {
try {
AuthzModule authzModule = AuthorizationManager.config("KeyCloakAuthz", OAuth2AuthorizationConfiguration.class)
.setBaseURL(new URL("http://127.0.0.1:8080/"))
.setAuthzEndpoint("/realms/example/protocol/openid-connect/auth")
.setAccessTokenEndpoint("/realms/example/protocol/openid-connect/token")
.setAccountId("keycloak-token")
.setClientId("clientId")
.setClientSecret("secret")
.setRedirectURL("http://oauth2callback")
.setScopes(Arrays.asList("openid"))
.addAdditionalAuthorizationParam((Pair.create("grant_type", "password")))
.addAdditionalAuthorizationParam((Pair.create("username", "aUserName")))
.addAdditionalAuthorizationParam((Pair.create("password", "aPassword")))
.asModule();
authzModule.requestAccess(this, new Callback<String>() {
#Override
public void onSuccess(String o) {
Log.d("TOKEN ", o);
}
#Override
public void onFailure(Exception e) {
System.err.println("Error!!");
Toast.makeText(getApplicationContext(), e.getMessage(), Toast.LENGTH_LONG).show();
}
});
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
However this doesn't do anything. What I don't understand is:
How can I specify that I'm doing and OpenID Connect with Keycloak in Aerogear?
How and where can I send the username and password?
How can I specify the grant_type? (My HTTP POST to the server does not work if I don't include this, so it's important)
Any help would be very much appreciated
If you go with the standard Authorization Code flow with access type = public client (no clientSecret) then you may take a look at my example Android native app.
In short, you could open up a browser window in a WebView, get the authorization code by parsing the query parameter from the returned url and exchange it (the code) for the token via a POST request.
If you use Retrofit, then here is the REST interface:
interface IKeycloakRest {
#POST("token")
#FormUrlEncoded
fun grantNewAccessToken(
#Field("code") code: String,
#Field("client_id") clientId: String,
#Field("redirect_uri") uri: String,
#Field("grant_type") grantType: String = "authorization_code"
): Observable<KeycloakToken>
#POST("token")
#FormUrlEncoded
fun refreshAccessToken(
#Field("refresh_token") refreshToken: String,
#Field("client_id") clientId: String,
#Field("grant_type") grantType: String = "refresh_token"
): Observable<KeycloakToken>
#POST("logout")
#FormUrlEncoded
fun logout(
#Field("client_id") clientId: String,
#Field("refresh_token") refreshToken: String
): Completable
}
data class KeycloakToken(
#SerializedName("access_token") var accessToken: String? = null,
#SerializedName("expires_in") var expiresIn: Int? = null,
#SerializedName("refresh_expires_in") var refreshExpiresIn: Int? = null,
#SerializedName("refresh_token") var refreshToken: String? = null
)
And its instantiation:
val rest: IKeycloakRest = Retrofit.Builder()
.baseUrl("https://[KEYCLOAK-URL]/auth/realms/[REALM]/protocol/openid-connect/")
.addConverterFactory(GsonConverterFactory.create(GsonBuilder().setLenient().create()))
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build()
.create(IKeycloakRest::class.java)
I am using AeroGear as well, and I notice I had the same problem. Then what I did was (beside other configuration information as Tolis Emmanouilidis said) to add the Auth service in your Manifest.
Try adding <service android:name="org.jboss.aerogear.android.authorization.oauth2.OAuth2AuthzService"/> in your manifest
Once you do that, it is working properly and you can retrieve the Bearer token.
i have implemented it on my project inside my keycloakHelper class.
public class KeycloakHelper {
static
{
try
{
AuthorizationManager
.config("KeyCloakAuthz", OAuth2AuthorizationConfiguration.class)
.setBaseURL(new URL(EndPoints.HTTP.AUTH_BASE_URL))
.setAuthzEndpoint("/auth/realms/***/protocol/openid-connect/auth")
.setAccessTokenEndpoint("/auth/realms/ujuzy/protocol/openid-connect/token")
.setRefreshEndpoint("/auth/realms/***/protocol/openid-connect/token")
.setAccountId("account")
.setClientId("account")
.setRedirectURL("your base url")
.addAdditionalAuthorizationParam((Pair.create("grant_type", "password")))
.asModule();
PipeManager.config("kc-upload", RestfulPipeConfiguration.class)
.module(AuthorizationManager.getModule("KeyCloakAuthz"))
.requestBuilder(new MultipartRequestBuilder());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static void connect(final Activity activity, final Callback callback)
{
if (!DetectConnection.checkInternetConnection(activity))
return;
try {
final AuthzModule authzModule = AuthorizationManager.getModule("KeyCloakAuthz");
authzModule.requestAccess(activity, new Callback<String>()
{
#SuppressWarnings("unchecked")
#Override
public void onSuccess(String s)
{
callback.onSuccess(s);
}
#Override
public void onFailure(Exception e)
{
// authzModule.refreshAccess();
authzModule.isAuthorized();
if (!e.getMessage().matches(OAuthWebViewDialog.OAuthReceiver.DISMISS_ERROR))
{
//authzModule.refreshAccess();
authzModule.deleteAccount();
}
callback.onFailure(e);
}
});
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
public static boolean isConnected()
{
return AuthorizationManager.getModule("KeyCloakAuthz").isAuthorized();
}
}
When you want user to login (input email & password)
public void LoginUser() {
KeycloakHelper.connect(getActivity(), new Callback() {
#Override
public void onSuccess(Object o) {
//YOU WILL GET YOUR TOKEN HERE IF USER IS ALREADY SIGNED IN.
//IF USER IS NOT SIGNED IN, AEROGEAR WILL PROMPT A WEBVIEW DIALOG
//WHERE THE USER WILL INPUT THERE EMAIL AND PASSWORD
}
#Override
public void onFailure(Exception e) {
}
});
}
Happy coding :)
Related
I want to design API calls in such a way that it will be easy to handle success and failure responses easily from one place (instead of writing same code of call function for all APIs)
Here are the scenarios which I want to consider.
Handle success / failure and error responses like 4xx, 5xx etc of all APIs at one central place.
Want to cancel enqueue requests and also stop processing response if request is already sent in case of logout (because response parsing will modify some global data of app)
If access token has expired and 401 response received from cloud, it should get new token and then call API again automatically with new token.
My current implementation is not satisfying above requirements.
Is there any way to implement API calls which satisfy above requirements using Retrofit ?
Please suggest me a good design for this.
Here is my current implementation :
ApiInterface.java - It is an interface which contains different API calls definitions.
ApiClient.java - To get retrofit client object to call APIs.
ApiManager.java - It has methods to call APIs and parse their responses.
ApiInterface.java
public interface ApiInterface {
// Get Devices
#GET("https://example-base-url.com" + "/devices")
Call<ResponseBody> getDevices(#Header("Authorization) String token);
// Other APIs......
}
ApiClient.java
public class ApiClient {
private static Retrofit retrofitClient = null;
static Retrofit getClient(Context context) {
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.sslSocketFactory(sslContext.getSocketFactory(), systemDefaultTrustManager())
.connectTimeout(15, TimeUnit.SECONDS)
.writeTimeout(15, TimeUnit.SECONDS)
.readTimeout(15, TimeUnit.SECONDS)
.build();
retrofitClient = new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.client(okHttpClient)
.build();
}
}
ApiManager.java
public class ApiManager {
private static ApiManager apiManager;
public static ApiManager getInstance(Context context) {
if (apiManager == null) {
apiManager = new ApiManager(context);
}
return apiManager;
}
private ApiManager(Context context) {
this.context = context;
apiInterface = ApiClient.getClient(context).create(ApiInterface.class);
}
public void getDevices(ResponseListener listener) {
// API call and response handling
}
// Other API implementation
}
Update :
For 1st point, interceptor will be helpful to handle 4xx, 5xx responses globally according to this.
But interceptor will be in the ApiClient file and to inform UI or API caller component, need to pass success or failure result in callback I mean response listener.
How can I do that ? Any idea ?
For 3rd point, I know little bit about Retrofit Authenticator. I think for this point it is suitable but it requires synchronous call to get new token using refresh token.
How can I make asynchronous call to synchronous ? (Note : this call is not retrofit call)
By handling the success/failure responses at a central place I'll assume you want to get rid of repeated boilerplate based on the error parsing logic and how it may create UI side-effects for your app.
I'd probably suggest keeping things really simple by creating a custom abstraction for Callback which invokes your APIs for success/failure according to your domain logic.
Here's something fairly simple implementation for use-case (1) :
abstract class CustomCallback<T> implements Callback<T> {
abstract void onSuccess(T response);
abstract void onFailure(Throwable throwable);
#Override
public void onResponse(Call<T> call, Response<T> response) {
if (response.isSuccessful()) {
onSuccess(response.body());
} else {
onFailure(new HttpException(response));
}
}
#Override
public void onFailure(Call<T> call, Throwable t) {
onFailure(t);
}
}
For use-case (2), to be able to cancel all enqueued calls upon a global event like logout you'd have to keep a reference to all such objects. Fortunately, Retrofit supports plugging in a custom call factory okhttp3.Call.Factory
You could use your implementation as a singleton to hold a collection of calls and in the event of a logout notify it to cancel all requests in-flight. Word of caution, do use weak references of such calls in the collection to avoid leaks/references to dead calls. (also you might want to brainstorm on the right collection to use or a periodic cleanup of weak references based on the transactions)
For use-case (3), Authenticator should work out fine since you've already figured out the usage there are 2 options -
Migrate the refresh token call to OkHttp/Retrofit and fire it synchronously
Use a count-down latch to make the authenticator wait for the async call to finish (with a timeout set to connection/read/write timeout for the refresh token API call)
Here's a sample implementation:
abstract class NetworkAuthenticator implements Authenticator {
private final SessionRepository sessionRepository;
public NetworkAuthenticator(SessionRepository repository) {
this.sessionRepository = repository;
}
public Request authenticate(#Nullable Route route, #NonNull Response response) {
String latestToken = getLatestToken(response);
// Refresh token failed, trigger a logout for the user
if (latestToken == null) {
logout();
return null;
}
return response
.request()
.newBuilder()
.header("AUTHORIZATION", latestToken)
.build();
}
private synchronized String getLatestToken(Response response) {
String currentToken = sessionRepository.getAccessToken();
// For a signed out user latest token would be empty
if (currentToken.isEmpty()) return null;
// If other calls received a 401 and landed here, pass them through with updated token
if (!getAuthToken(response.request()).equals(currentToken)) {
return currentToken;
} else {
return refreshToken();
}
}
private String getAuthToken(Request request) {
return request.header("AUTHORIZATION");
}
#Nullable
private String refreshToken() {
String result = null;
CountDownLatch countDownLatch = new CountDownLatch(1);
// Make async call to fetch token and update result in the callback
// Wait up to 10 seconds for the refresh token to succeed
try {
countDownLatch.await(10, TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
return result;
}
abstract void logout();
}
I hope this helps with your network layer implementation
So, With the help of official sample in the retrofit github repository here: https://github.com/square/retrofit/blob/fbf1225e28e2094bec35f587b8933748b705d167/samples/src/main/java/com/example/retrofit/ErrorHandlingAdapter.java
The ErrorHandlingAdapter is the closest you can get to your requirement because it lets you control enqueuing of the call, creating the error callbacks, calling error callbacks on your own. Whether you want the caller to do some action or you want to handle it yourself in one place or just both.
So this is how you can create it. Do read the inline comments to understand.
public final class ErrorHandlingAdapter {
/**
* Here you'll decide how many methods you want the caller to have.
*/
interface MyCallback<T> {
void success(Response<T> response);
void error(String s);
}
/**
* This is your call type
*/
interface MyCall<T> {
void cancel();
void enqueue(MyCallback<T> callback);
#NotNull
MyCall<T> clone();
}
public static class ErrorHandlingCallAdapterFactory extends CallAdapter.Factory {
#Override
public #Nullable
CallAdapter<?, ?> get(
#NotNull Type returnType, #NotNull Annotation[] annotations, #NotNull Retrofit retrofit) {
if (getRawType(returnType) != MyCall.class) {
return null;
}
if (!(returnType instanceof ParameterizedType)) {
throw new IllegalStateException(
"MyCall must have generic type (e.g., MyCall<ResponseBody>)");
}
Type responseType = getParameterUpperBound(0, (ParameterizedType) returnType);
Executor callbackExecutor = retrofit.callbackExecutor();
return new ErrorHandlingCallAdapter<>(responseType, callbackExecutor);
}
private static final class ErrorHandlingCallAdapter<R> implements CallAdapter<R, MyCall<R>> {
private final Type responseType;
private final Executor callbackExecutor;
ErrorHandlingCallAdapter(Type responseType, Executor callbackExecutor) {
this.responseType = responseType;
this.callbackExecutor = callbackExecutor;
}
#NotNull
#Override
public Type responseType() {
return responseType;
}
#Override
public MyCall<R> adapt(#NotNull Call<R> call) {
return new MyCallAdapter<>(call, callbackExecutor);
}
}
}
static class MyCallAdapter<T> implements MyCall<T> {
private final Call<T> call;
private final Executor callbackExecutor;
MyCallAdapter(Call<T> call, Executor callbackExecutor) {
this.call = call;
this.callbackExecutor = callbackExecutor;
}
#Override
public void cancel() {
call.cancel();
}
#Override
public void enqueue(final MyCallback<T> callback) {
if (!SomeCondition.myCondition) {
// Don't enqueue the call if my condition doesn't satisfy
// it could be a flag in preferences like user isn't logged in or
// some static flag where you don't want to allow calls
return;
}
call.clone().enqueue(
new Callback<T>() {
#Override
public void onResponse(#NotNull Call<T> call, #NotNull Response<T> response) {
callbackExecutor.execute(() -> {
int code = response.code();
if (code >= 200 && code < 300) {
//success response
callback.success(response);
} else if (code == 401) {
// Unauthenticated so fetch the token again
getTheTokenAgain(callback);
} else if (code >= 400 && code < 500) {
//handle error the way you want
callback.error("Client error");
} else if (code >= 500 && code < 600) {
//handle error the way you want
callback.error("Server error");
} else {
//handle error the way you want
callback.error("Something went wrong");
}
});
}
#Override
public void onFailure(#NotNull Call<T> call, #NotNull Throwable t) {
callbackExecutor.execute(() -> {
if (t instanceof IOException) {
callback.error("IOException");
} else {
callback.error("Some exception");
}
});
}
});
}
private void getTheTokenAgain(MyCallback<T> callback) {
// Make the call to get the token & when token arrives enqueue it again
// Don't forget to put termination condition like 3 times, if still not successful
// then just log user out or show error
// This is just dummy callback, you'll need to make a
// call to fetch token
new MyTokenCallback() {
#Override
public void onTokenArrived(String token) {
//enqueue(callback); here
}
#Override
public void onTokenFetchFailed() {
callbackExecutor.execute(() -> {
callback.error("Counld't fetch token");
});
}
};
// This is for demo you should put it in success callback
SomeCondition.callCount++;
Log.d("MG-getTheTokenAgain", "Method called");
if (SomeCondition.callCount < 3) {
enqueue(callback);
} else {
callbackExecutor.execute(() -> {
callback.error("Counld't fetch token");
});
}
}
#NotNull
#Override
public MyCall<T> clone() {
return new MyCallAdapter<>(call.clone(), callbackExecutor);
}
}
}
This is how you'll plug this adapter:
private void makeApiCall() {
//This is just for demo to generate 401 error you won't need this
OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
httpClient.addInterceptor(chain -> {
Request request = chain.request().newBuilder()
.addHeader("Accept","application/json")
.addHeader("Authorization", "cdsc").build();
return chain.proceed(request);
});
Retrofit retrofit =
new Retrofit.Builder()
.baseUrl("http://httpbin.org/")
.addCallAdapterFactory(new ErrorHandlingAdapter.ErrorHandlingCallAdapterFactory())
.addConverterFactory(GsonConverterFactory.create())
.client(httpClient.build())
.build();
HttpBinService service = retrofit.create(HttpBinService.class);
ErrorHandlingAdapter.MyCall<Ip> ip = service.getIp();
ip.enqueue(
new ErrorHandlingAdapter.MyCallback<Ip>() {
#Override
public void success(Response<Ip> response) {
Log.d("MG-success", response.toString());
}
#Override
public void error(String s) {
Log.d("MG-error", s);
}
});
}
You might have to bend some things to your needs, but I think this could be good reference because it's in the official sample.
1. Handle success / failure and error responses like 4xx, 5xx etc of
all APIs at one central place.
Create following two classes:
ApiResponse.kt
class ApiResponse<T : Any> {
var status: Boolean = true
var message: String = ""
var data: T? = null
}
ApiCallback.kt
abstract class ApiCallback<T : Any> : Callback<ApiResponse<T>> {
abstract fun onSuccess(response: ApiResponse<T>)
abstract fun onFailure(response: ApiResponse<T>)
override fun onResponse(call: Call<ApiResponse<T>>, response: Response<ApiResponse<T>>) {
if (response.isSuccessful && response.body() != null && response.code() == 200) {
onSuccess(response.body()!!)
} else { // handle 4xx & 5xx error codes here
val resp = ApiResponse<T>()
resp.status = false
resp.message = response.message()
onFailure(resp)
}
}
override fun onFailure(call: Call<ApiResponse<T>>, t: Throwable) {
val response = ApiResponse<T>()
response.status = false
response.message = t.message.toString()
onFailure(response)
}
}
Now use the above ApiCallback class instead of Retrofit's Callback class to enqueue
2. Want to cancel enqueue requests and also stop processing response if request is already sent in case of logout (because response parsing will modify some global data of app)
You cannot stop processing a response midway, but what you can do is not update the ui or activity if the activity in question is not in foreground, which can be done with the help of LiveData from the MVVM Architecture.
3. If access token has expired and 401 response received from cloud, it should get new token and then call API again automatically with new token.
Create a TokenAuthenticator.java class like this
public class TokenAuthenticator implements Authenticator {
#Override
public Request authenticate(Proxy proxy, Response response) throws IOException {
// Refresh your access_token using a synchronous api request
newAccessToken = service.refreshToken();
// Add new header to rejected request and retry it
return response.request().newBuilder()
.header(AUTHORIZATION, newAccessToken)
.build();
}
#Override
public Request authenticateProxy(Proxy proxy, Response response) throws IOException {
// Null indicates no attempt to authenticate.
return null;
}
}
Attach an instance of the above authenticator to OkHttpClient like this
OkHttpClient okHttpClient = new OkHttpClient();
okHttpClient.setAuthenticator(authAuthenticator);
Then finally, attach okHttpClient to the Retrofit instance as you have already done
More info regarding the authenticator part can be found in this answer here
I integrated the witter login in android. But on response it always returning me in the logcat:
code=400, message=Bad Request
and
"data":"{\"+clicked_branch_link\":false,\"+is_first_session\":false}",
I am to see the token, secret value in logcat if I printed it. But response returning 400 always. I used here branch IO for deep linking.
public void attachTwitter(String token, String secret) {
apiService.attachTwitterAccount(PreferenceHandler.readString(EditProfileActivity.this, SIGNIN_ID, ""),
token, secret, "twitter").enqueue(new Callback<Object>() {
#Override
public void onResponse(Call<Object> call, Response<Object> response) {
Log.i("accessToken", "onResponse");
if (!response.isSuccessful()) {
try {
JSONObject object = new JSONObject(response.errorBody().string());
JSONObject error = object.optJSONObject("error");
String code = error.optString("code");
String description = error.optString("description");
if (code.equalsIgnoreCase("338")) {
showCustomDialog(EditProfileActivity.this, string(R.string.server_error_description_338));
switchTwitter.setChecked(false);
}
} catch (Exception e) {
}
}
}
#Override
public void onFailure(Call<Object> call, Throwable t) {
Log.i("accessToken", "onFailure");
switchTwitter.setChecked(false);
}
});
}
attachTwitterAccount methods code is:
#FormUrlEncoded
fun attachTwitterAccount(#Field("id") id: String,
#Field("authToken") token: String,
#Field("authSecret") authSecret: String,
#Field("typeAttach") typeAttach: String): Call<Any>
Can anyone please advise how I can fix this issue?
A Bad request means that the request that you are sending to the server is not in the way or form it is expecting. What do the docs say about that request? is it a Post? a Get?. If it is a POST then you should send a Body.
To send a body and a POST you first need to create a class for the body. In Kotlin it would be something like this:
class Body(var id: String,
var authToken: String,
var authSecret: String,
var accomplished: Double,
var typeAttach: String
)
Then you call the body in your request:
#POST("post value which")
fun attachTwitterAccount(#Body body:Body): Call<Any>
Finally you call it from your attach Twitter function. You create your Body instance and then pass it as argument.
If you are still getting the same error, check the Twitter docs, you might be missing something.
Its always a good idea to try that same call to the server in other enviroment (such as Postman) and see if it works there? this is a good way of determining if the problem is in the server side or in your application side.
Let me know if it works
I was trying to send string and images with a retrofit.
while I could get pass response with x-www-form-urlencoded & hashmap, but I need to send it with the image. so I use form-data, but I couldn't get the same response with the same name and value, tested it on postman and it goes passed the same as my x-www-form.
so here is the postman
Postman request that got pass response
Method that doesn't goes through
with form-data
#Multipart
#POST("report")
fun push(
#HeaderMap headers: Map<String, String>,
#Part("store") string: RequestBody
): Call<ReportingResponse>
RequestBody.create(MediaType.parse("multipart/form-data"), "testing") //#1 fail
RequestBody.create(MediaType.parse("text/plain"), "testing") //#2 fail
I tried both but couldn't get the same response as the postman, and what it looks is just like this Retrofit request interceptor on Android Studio
Method that goes Through with x-www-form
#FormUrlEncoded
#POST("report")
fun push(
#HeaderMap headers: Map<String, String>,
#FieldMap form: MutableMap<String, Any>
): Call<ReportingResponse>
What am I suppose to do?
Step-1 : Create on interface method for call retrofit api
#POST(Const.Task_Ans_FILE_NAME)
Call<TaskInfoBean> verifyTaskAns(#Body RequestBody file);
Step-2: Use below code to send multipart image data along with other field in body.
RetroFitService retroFitService = RetrofitClient.getAppData();
MultipartBody.Builder builder = new MultipartBody.Builder().setType(MultipartBody.FORM);
if (answer_type.equals("1")) {
builder.addFormDataPart(Const.ANSWER, answer);
} else {
try {
builder.addFormDataPart(Const.ANSWER, Const.SelectedFileName, RequestBody.create(MultipartBody.FORM, Const.BOS.toByteArray()));
}catch (Exception e){
Log.e(TAG, "doInBackground: "+e.getMessage() );
}
}
builder.addFormDataPart(Const.LOGIN_ID, login_id)
.addFormDataPart(Const.USER_ID, user_id)
.addFormDataPart(Const.PLAY_ID, play_id)
.addFormDataPart(Const.TASK_ID, task_id)
.addFormDataPart(Const.SCENARIO, scenario)
.addFormDataPart(Const.ANSWER_TYPE,answer_type)
.addFormDataPart(Const.SKIP, skip);
final RequestBody requestBody = builder.build();
Call<TaskInfoBean> call = retroFitService.verifyTaskAns(requestBody);
call.enqueue(new Callback<TaskInfoBean>() {
#Override
public void onResponse(Call<TaskInfoBean> call, Response<TaskInfoBean> response) {
if(response.code()==200) {
TaskInfoBean taskInfoBean = response.body();
listener.OnVerifyTaskAns(taskInfoBean);
}else{
Log.e(TAG, "onResponse: "+response.toString() );
}
}
#Override
public void onFailure(Call<TaskInfoBean> call, Throwable t) {
Log.e(TAG, "onFailure: " + t.toString());
}
});
return null;
}
Step-3: Call this method in your activity/fragment.
Am trying to parse json string from php via gson in android but keep on getting an error
in php script i have
return json_encode(["valid"=>true,"token"=>$tokenUser->token]);
IN my android on response method i have
void onApiResponse(String response){
Log.i("test", response) //gives "{\"valid\":false,\"token\":null}"
//using gson below
VerificationResponse verificationResponse = new Gson().fromJson(response,VerificationResponse.class);
}
And my verification response class has
private class VerificationResponse{
private Boolean valid;
private String token;
public Boolean getValid() {
return valid;
}
public String getToken() {
return token;
}
}
Whenever i try accessing the isValid getter via verificationResponse.getValid() am getting an error
com.google.gson.JsonSyntaxException: java.lang.IllegalStateException:
Expected BEGIN_OBJECT but was STRING
What am i missing in this?
String Json_Header = "Content-Type: application/json;charset=UTF-8";
String Query_Header = "Content-Type: application/x-www-form-urlencoded";
ApiInterface
#POST("Get_Watch_List")
#Headers({Query_Header})
Call<String> GetWatchList(#Query("Token") String Token);
Call Service
ApiInterface apiService = ApiClient.getClient().create(ApiInterface.class);
Call<String> call = apiService.GetWatchList(GlobalApp.Token);
call.enqueue(new Callback<String>() {
#Override
public void onResponse(Call<String> call, Response<String> response) {
try {
Gson gson = new Gson();
VerificationResponse verificationResponse = gson.fromJson(response.body(), VerificationResponse.class);
if (verificationResponse.getValid().equals("true")) {
// here your code
}
} catch (Exception e) {
e.printStackTrace();
}
}
#Override
public void onFailure(Call<String> call, Throwable t) {
}
});
To Serialize an object TO Json (not FROM Json) and to verify if your model/json are working together, it's very easy to try to see how an object is first serialized and compare that.
So (in Kotlin because it's shorter):
import android.util.Log
import com.google.gson.Gson
data class Thing(val value: Boolean, val token: String)
class SerializeMyThing {
init {
val thing = Thing(true, "token2")
val gson = Gson()
val thingAsJsonString = gson.toJson(thing, Thing::class.java)
Log.d("THING", thingAsJsonString)
}
}
Thing is my model, it takes a value (Boolean) and a String called token, like yours.
Then I simply created a Gson instance and called toJson...
This is the output of the above:
D/THING: {"token":"token2","value":true}
If you wanted to go BACK to an object (which is what you're trying to do), add this after the first log to verify it's working:
// back to Object from the thingAsJson:
val newThing = gson.fromJson(thingAsJsonString, Thing::class.java)
Log.d("THING", "Value: " + newThing.value + " | token: " + newThing.token)
Prints (as expected):
D/THING: Value: true | token: token2
What I think you're having issues with is the format of your Json gotten from the server:
It looks like this according to your question:
"{\"valid\":false,\"token\":null}"
Where I think it should be more like:
{"valid":false,"token":null}
I am building an android app and i am using Retrofit to retrieve data from API. In this app i have to make 3 calls. The first one is working fine. The code for the first one is below. I have one class
public class APIClient {
private static Retrofit retrofit = null;
static Retrofit getClient(){
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient client = new OkHttpClient.Builder().addInterceptor(interceptor).build();
retrofit = new Retrofit.Builder()
.baseUrl("https://api_app.com")
.addConverterFactory(GsonConverterFactory.create())
.client(client)
.build();
return retrofit;
}
}
Also i have this interface
#Headers({
"AppId: 3a97b932a9d449c981b595",
"Content-Type: application/json",
"appVersion: 5.10.0",
"apiVersion: 3.0.0"
})
#POST("/users/login")
Call<MainUserLogin> logInUser(#Body LoginBody loginBody);
The code of the Actvity is this
call.enqueue(object : Callback<MainUserLogin> {
override fun onResponse(call: Call<MainUserLogin>, response: Response<MainUserLogin>) {
if (response.code().toString().equals("200")){
val resource = response.body()
bearerToken = resource.session.bearerToken
if (bearerToken.isNotEmpty() && bearerToken.isNotBlank()){
val sharedPreferences = getSharedPreferences("Settings", Context.MODE_PRIVATE)
val editor = sharedPreferences.edit()
editor.putString("bearerToken", bearerToken)
editor.commit()
BearerToken.bearerToken = bearerToken
val i = Intent(this#LoginActivity, UserAccountsActivity::class.java)
i.putExtra("bearerToken", bearerToken)
startActivity(i)
}else{
Toast.makeText(applicationContext, "Please try again.", Toast.LENGTH_LONG).show()
}
}else{
println("edwedw "+response.errorBody().string())
Toast.makeText(applicationContext, "Incorrect email address or password. Please check and try again.", Toast.LENGTH_LONG).show()
}
}
override fun onFailure(call: Call<MainUserLogin>, t: Throwable) {
call.cancel()
}
})
This call is working fine.
With this call i am getting one token. The problem is that i have to pass this token as header to make the second call. So, the second call will be like this.
#Headers({
"AppId: 3a97b932a9d449c981b595",
"Content-Type: application/json",
"appVersion: 5.10.0",
"apiVersion: 3.0.0",
"Authorization: "+***Token***
})
#GET("/products")
Call<MainUserLogin> getUseraccounts ();
Is there any way to pass the variable from the Activity to the interface to make the Api request?
Thank you very much.
Using Retrofit you can call API's with multiple headers as follows
#GET("/products")
Call<MainUserLogin> getUseraccounts(#Header("AppId") String appId, #Header("Content-Type") String contentType, #Header("appVersion") String appVersion, #Header("apiVersion") String apiVersion, #Header("Authorization") String token);
Instead of
#Headers({
"AppId: 3a97b932a9d449c981b595",
"Content-Type: application/json",
"appVersion: 5.10.0",
"apiVersion: 3.0.0",
"Authorization: "+***Token***
})
#GET("/products")
Call<MainUserLogin> getUseraccounts ();
this. When you call getUseraccounts method you can parse the token that you created from the previous endpoint.
Try this and let me know your feedback. Thanks!
Once you receive the token, you should save this token in a global repository since the auth token is something that your app will need in order to make further authenticated api calls.
After that, define a AuthorizationHeaderInterceptor which will extend okhttp3.Interceptor. Override the intercept method of this interceptor to add auth token to your request.
#Override
public Response intercept(#NonNull Chain chain) {
return completeRequest(chain);
}
private Response completeRequest(#NonNull Interceptor.Chain chain) {
AuthToken authToken = authTokenRepository.get();
Request.Builder requestBuilder = chain.request().newBuilder();
if (authToken != null && chain.request().header(Authorization.NAME) == null) {
requestBuilder.addHeader(Authorization.NAME, Authorization.getValue(authToken.getIdToken()));
}
Request request = requestBuilder.build();
try {
return chain.proceed(request);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
The interceptor can be added when you build your okhttpClient.
okHttpClientBuilder.addInterceptor(new AuthorizationHeaderInterceptor(authTokenRepository))
Note that the Authorization class is simple convenience class which encapsulates the authorization header name and value format.
public class Authorization {
public static final String NAME = "Authorization";
#NonNull
public static String getValue(#NonNull String accessToken) {
return String.format("Bearer %s", accessToken);
}
}