I'm having problems implementing a custom class as response JSON in Retrofit2.
The call succeeds and when I log the body (using HttpLoggingInterceptor) I can see the JSON is fetched correctly.
The only problem is that it is not parsed into the custom class I created.
Here is my ServiceGenerator:
public class ServiceGenerator
{
//Base url for the API
public static final String API_BASE_URL = "http://base.url";
private static Retrofit.Builder GSONBuilder =
new Retrofit.Builder()
.baseUrl(API_BASE_URL)
.addConverterFactory(GsonConverterFactory.create());
public static <T> T createJSONService(Class<T> serviceClass)
{
OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
logging.setLevel(HttpLoggingInterceptor.Level.BODY);
httpClient.addInterceptor(logging);
Retrofit retrofit = GSONBuilder.client(httpClient.build()).build();
return retrofit.create(serviceClass);
}
}
Here is the ServiceInterface:
public interface ServiceInterface
{
class UserResponse
{
public int id;
public String email;
public String created_at;
#Override
public String toString()
{
return "UserResponse{" +
"id: " + id +
", email: " + email +
", created_at: " + created_at +
"}";
}
}
#GET("user/{userId}")
Call<UserResponse> currentUser(#Path("userId") int userId);
}
And this is where I actually call it:
public void getUser(int userId)
{
ServiceInterface clientCreate = ServiceGenerator.createJSONService(ServiceInterface.class);
Call<ServiceInterface.UserResponse> callCreate = clientCreate.currentUser(userId);
callCreate.enqueue(new Callback<ServiceInterface.UserResponse>()
{
#Override
public void onResponse(Call<ServiceInterface.UserResponse> call, Response<ServiceInterface.UserResponse> response)
{
ServiceInterface.UserResponse user = response.body();
if (user == null)
{
System.out.println("error");
}
else
{
System.out.println(user.toString());
//This line gets printed, but the class is empty
//What it should show: UserResponse{id: 5, email: "test#email.com", created_at: "2016-03-02"}
//What it actually shows: UserResponse{id: 0, email: null, created_at: null}
}
}
#Override
public void onFailure(Call<ServiceInterface.UserResponse> call, Throwable t)
{
System.out.println("Fail: " + t.getMessage());
}
});
}
I feel like I did everything right, is there any explanation why the GsonConverter does not convert the response to my custom class (UserResponse)?
To make myself even more clear, here is the actual JSON response (using Postman):
{"id": 5, "email": "test#email.com", "created_at": "2016-03-02"}
Thanks in advance!
Edit 1:
For anyone interested.
I have just figured out that if I use a String as a return type it actually writes all the data to the String.
So that means the fault lies with the conversion. I think that somewhere along the way I made a mistake with the GSONBuilder.
Related
I am trying to move the assertThat method from Authentication class to the BDDStyledMethod class but the current code will generate the following error "'Creds(java.lang.String)' in 'steps.Authentication' cannot be applied to '()'"
How do i correct my code so that the assertThat method works in the BDDStyledMethod class ?
public class Authentication {
public static void Creds(String url){
RequestSpecification httpRequest=RestAssured.given()
.auth().oauth2(Authentication.login("user","password"));
Response response = httpRequest.get(url);
ResponseBody body=response.getBody();
body.prettyPrint();
System.out.println("The status received: " + response.statusLine());
assertThat("They are not the same",response.statusLine(),is("HTTP/1.1 200"));
}
}
public class BDDStyledMethod {
public static void GetActivityById(){
Authentication.Creds("www.randomurl.com");
assertThat("They are not the same",Authentication.Creds().response.statusLine(),is("HTTP/1.1 200"));
}
}
The problem is with the Creds method. It is not returning anything and the exception is raised in this line -> Authentication.Creds().response.statusLine()
We can return a string from Creds method and then try to apply assert() on the returned string in GetActivityById class.
public class Authentication {
public static String Creds(String url){
RequestSpecification httpRequest=RestAssured.given()
.auth().oauth2(Authentication.login("user","password"));
Response response = httpRequest.get(url);
ResponseBody body=response.getBody();
body.prettyPrint();
System.out.println("The status received: " + response.statusLine());
return response.statusLine().toString();
}
}
public class BDDStyledMethod {
public static void GetActivityById(){
String returned_str = Authentication.Creds("www.randomurl.com");
assertThat("They are not the same",returned_str,is("HTTP/1.1 200"));
}
}
Using Retrofit I post my contact list on the phone without authentication. I need to do retrofit basic authentication, but as far as I can't see from the internet. Can you help me, please?
This is my Code:
public void GetContactsIntoArrayList(){
mAPIService = ApiUtils.getAPIService();
final User user = new User();
final Post post = new Post();
cursor = getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null,null, null, null);
while (cursor.moveToNext()) {
int i = 0;
name = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
phonenumber = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
StoreContacts.add(name + " " + ":" + " " + phonenumber);
user.setphoneNumber(phonenumber.toString().trim());
user.setname(name.toString().trim());
List<User> phoneList = new ArrayList<>();
phoneList.add(user);
post.setUsers(phoneList);
sendPost(post);
}
cursor.close();
}
public void sendPost(Post post){
mAPIService.savePost(post).enqueue(new Callback<Post>() {
#Override
public void onResponse(Call<Post> call, Response<Post> response) {
Log.d("requestError", "onResponse: "+ call.request().body().toString());
if(response.isSuccessful()) {
Log.i("Is Ok?","OK :)");
}
}
#Override
public void onFailure(Call<Post> call, Throwable t) {
}
});
}
This is so far the easiest method i have ever tried for "Basic Authentication".
Use the below code to generate the auth header (API/Repository class)
var basic = Credentials.basic("YOUR_USERNAME", "YOUR_PASSWORD")
Pass this as header to the webservice call (API/Repository class)
var retrofitCall = myWebservice.getNewsFeed(basic)
Add the basic header as parameter (Retrofit Webservice interface class)
#GET("newsfeed/daily")
fun getNewsFeed(#Header("Authorization") h1:String):Call<NewsFeedResponse>
My code is in Kotlin, just in case you were looking for Java. But can be easily translated to Java.
References: https://mobikul.com/basic-authentication-retrofit-android/
You can add auth value in header of the request like if you want to send auth token then follow below step:
#POST("/auth/update-contactno")
#Headers(
"Content-Type: application/json",
Constants.headerApp,
Constants.headerLanguage,
Constants.headerPlatform,
Constants.headerVersion
)
fun updateMobileNumber(#Header(Constants.authorization) token: String?, #Body verifyForgotPasswordOTPInput: VerifyForgotPasswordOTPInput): Call<JsonObjectResponse<UserModel>>
And call updateMobileNumber() like :
apiService.updateMobileNumber(token, verifyForgotPasswordOTPInput)
I need to change base url at run time.
I have login button and when login button click time i am called my login api
like below :
login api = http://192.168.0.61/api/authenticate
API_BASE_URL = http://192.168.0.61/api/
when i get success response from first api i get client server url for changing baseUrl.
CompanyUrlConfigEntity companyUrlConfigEntity = response.body();
like below :
String clientUrl = companyUrlConfigEntity.
getBaseUrl();
clientUrl = http://192.168.0.238/api/
In this project mainly for client or company based.So they have their own server.
Each company has using more than 20 api's.
So i need to change base url .
I am also checked below link for changing base url:
https://futurestud.io/tutorials/retrofit-2-how-to-change-api-base-url-at-runtime-2
and changed code like that
public static void changeApiBaseUrl(String newApiBaseUrl) {
API_BASE_URL = newApiBaseUrl;
builder = new Retrofit.Builder()
.baseUrl(API_BASE_URL)
.addConverterFactory(new NullOnEmptyConverterFactory())
.addConverterFactory(ScalarsConverterFactory.create())
.addConverterFactory(GsonConverterFactory.create(new Gson()));
}
when i debugged and checked my baseUrl then it shows properly like below:
API_BASE_URL = http://192.168.0.238/api/
But when i call my customer api it shows the my first base url calling,
the url not changed.
expected customer api : http://192.168.0.238/api/customers
reality customer api : http://192.168.0.61/api/customers
I am also checked below link :
https://futurestud.io/tutorials/retrofit-2-how-to-use-dynamic-urls-for-requests
thats working , But each api need to pass fullPath url with each api like below:
#GET
public Call<ResponseBody> profilePicture(#Url String url);
But using this method , each api calling place i need to attach full path of url.
There is any other options? Please help me.
ServiceGenerator.class
public class ServiceGenerator {
public static String API_BASE_URL = "http://192.168.0.61/api/";
private static Retrofit retrofit;
private static OkHttpClient.Builder httpClient = new
OkHttpClient.Builder();
private static Retrofit.Builder builder =
new Retrofit.Builder()
.baseUrl(API_BASE_URL)
.addConverterFactory(new NullOnEmptyConverterFactory())
.addConverterFactory(ScalarsConverterFactory.create())
.addConverterFactory(GsonConverterFactory.create(new
Gson()));
private ServiceGenerator() {
}
public static void changeApiBaseUrl(String newApiBaseUrl) {
API_BASE_URL = newApiBaseUrl;
builder = new Retrofit.Builder()
.baseUrl(API_BASE_URL)
.addConverterFactory(new NullOnEmptyConverterFactory())
.addConverterFactory(ScalarsConverterFactory.create())
.addConverterFactory(GsonConverterFactory.create(new Gson()));
}
public static Retrofit retrofit() {
return retrofit;
}
public static <S> S createService(Class<S> serviceClass) {
return createService(serviceClass, null, null);
}
public static <S> S createService(Class<S> serviceClass,
final String authToken,
final ProgressListener progressListener) {
if (authToken != null) {
httpClient.addInterceptor(new Interceptor() {
#Override
public Response intercept(Chain chain) throws IOException {
Request original = chain.request();
final String headerValue = AUTHORIZATION_TYPE + authToken;
Request request = original.newBuilder()
.header(AUTHORIZATION_HEADER_KEY, headerValue)
.method(original.method(), original.body())
.build();
return chain.proceed(request);
}
});
}
addResponseProgressListener(progressListener);
if (BuildConfig.DEBUG) {
HttpLoggingInterceptor httpLoggingInterceptor = new
HttpLoggingInterceptor();
httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
httpClient.addInterceptor(httpLoggingInterceptor);
}
if (authToken != null) {
if (picasso == null) {
setUpPicasso(authToken);
}
}
OkHttpClient client = httpClient.build();
httpClient.connectTimeout(15, TimeUnit.SECONDS);
httpClient.readTimeout(2, TimeUnit.MINUTES);
httpClient.writeTimeout(2, TimeUnit.MINUTES);
retrofit = builder.client(client).build();
return retrofit.create(serviceClass);
}
}
LoginFragment.java
#OnClick(R.id.bt_login)
void onLogin() {
checkValidityOfUser();
}
private void checkValidityOfUser() {
final Login login = getLoginCredentials();
Call<CompanyUrlConfigEntity> callCheckValidity = dataProcessController.
getApiClient().
checkValidityOfUsers(login.getUsername());
callCheckValidity.enqueue(new Callback<CompanyUrlConfigEntity>() {
#Override
public void onResponse(Call<CompanyUrlConfigEntity> call,
Response<CompanyUrlConfigEntity> response) {
if (response.code() == 200) {
CompanyUrlConfigEntity companyUrlConfigEntity = response.body();
boolean status = companyUrlConfigEntity.isValidUser();
if (status) {
String baseUrls = companyUrlConfigEntity.
getBaseUrl();
baseUrls = baseUrls + "/api/";
ServiceGenerator.changeApiBaseUrl(baseUrls);
logins();
} else {
ToastHelper.show("please contact admin");
}
} else {
ToastHelper.show("" + response.code() + response.message());
}
}
#Override
public void onFailure(Call<CompanyUrlConfigEntity> call, Throwable t) {
ToastHelper.show("please contact admin");
}
});
}
private void logins() {
login = getLoginCredentials();
Call<Void> callLogin = dataProcessController.
getApiClient().
login(login);
callLogin.enqueue(new Callback<Void>() {
#Override
public void onResponse(Call<Void> call, Response<Void> response) {
if (response.code() == 200) {
} else if (response.code() == 401) {
}
}
#Override
public void onFailure(Call<Void> call, Throwable t) {
}
});
}
Base on your comments, I would say that you are correctly changing the API url on your builder, but that your second call still uses an instance of service where the url has not changed.
To explain a little more, from what I understand this is how everything gets executed:
when fragment is created, the apiClient is created and pointing to the first url
with dataProcessController.getApiClient() in your first call, you are getting the service that is pointing to the first url and then execute the call.
when the call is successful, you read the new url from result and update the ServiceGenerator with that new url. Then you execute the logins() method.
and in that method, you recall the dataProcessController.getApiClient() and do the second call with it. However, as you never redid apiClient = ServiceGenerator.createService(ApiClient.class);, the apiClient instance you are getting is still pointing to the first url, because it hasn't been notified that the url changed.
What I would try here, would be to change the method getApiClient() in your DataProcessController class to something like this:
public ApiClient getApiClient() {
apiClient = ServiceGenerator.createService(ApiClient.class);
return apiClient;
}
and see if this is work better.
Or if you don't want to regenerate the service inside that function, you can also do something like this:
public class DataProcessController {
private ApiClient apiClient = null;
private DataProcessController() {
regenerateClient();
}
public ApiClient getApiClient() { return apiClient; }
// add this to regenerate the client whenever url changes
public void regenerateClient() {
apiClient = ServiceGenerator.createService(ApiClient.class);
}
}
then, everytime you do change the url, do this:
ServiceGenerator.changeApiBaseUrl(baseUrls);
dataProcessController.regenerateClient();
and you should get a client that points to the correct url everytime you do dataProcessController.getApiClient()
https://segunfamisa.com/posts/firebase-remote-config
You should follow concept of firebase remote config. Here you dont need to store base Url in source code it will be retrieved from firebase config values which is stored at server of firebase.
// fetch
mRemoteConfig.fetch(3000)
.addOnCompleteListener(this, new OnCompleteListener<Void>() {
#Override
public void onComplete(Task<Void> task) {
if (task.isSuccessful()) {
// update your base url here.
} else {
//task failed
}
}
});
I am making a weather app by using the wunderground API. I am also using Retrofit2 and GSON library.
Here is the API URL format to get the JSON response:
http://api.wunderground.com/api/API_KEY/conditions/q/ISO_COUNTRY_CODE/CITY_NAME.json
I've declared an java API_Interface as follows:
public interface API_Interface {
#GET("/api/{apikey}/conditions/q/BD/{city}.json")
Call<CurrentObservation> getCurrentWeather(
#Path("apikey") String apikey,
#Path("city") String city);
}
And trying to pass the apikey and city from the MainActivity as follows:
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(Constants.BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build();
API_Interface weatherService = retrofit.create(API_Interface.class);
Call<CurrentObservation> call = weatherService.getCurrentWeather(Constants.API_KEY,"Dhaka");
call.enqueue(new Callback<CurrentObservation>() {
#Override
public void onResponse(Call<CurrentObservation> call, Response<CurrentObservation> response) {
textView.setText(response.body().toString());
Log.d("result",response.body().toString());
}
#Override
public void onFailure(Call<CurrentObservation> call, Throwable t) {
textView.setText("Something went wrong: " + t.getMessage());
Log.e("error",t.getMessage());
}
});
Here is the Constant class:
public class Constants {
public static final String BASE_URL="http://api.wunderground.com";
public static final String API_KEY="b5efba6dc63cc1b1";
}
and here is the POJO Model of CurrentObservation class: http://paste.ubuntu.com/22291964/
I've overriden a toString() method in the model.
There are some other POJO classes-
But this approach gives null response as following-
Weather Status: null
Pressure: null
Humidity: null
Temperature: null
Here is the actual JSON response from the API URL- http://paste.ubuntu.com/22292683/
How do I pass the parameters into #GET to get the correct response?
Your base URL should look like this:
http://blah.com/api/blah/
And your #GET method should have an URL like this
api/{apikey}/conditions/q/BD/{city}.json
EDIT: You might have onResponse called with an error body. Please adjust the following code for your use-case:
public static boolean handleError(Retrofit retrofit, Response<?> response) {
if(response != null && !response.isSuccessful() && response.errorBody() != null) {
Converter<ResponseBody, ErrorResponse> converter = retrofit.responseBodyConverter(ErrorResponse.class, new Annotation[0]);
try {
ErrorResponse errorResponse = converter.convert(response.errorBody());
// do something
} catch(IOException e) {
Log.e(TAG, "An error occurred", e);
}
return true;
}
return false;
}
You can do it like that:
#GET
Call<CurrentObservation> getCurrentWeather(#Url String url);
I'm using retrofit 1.9.0 and I had tried the following code to get a response in json format:
public void Execute(String email, String password, Callback<User> callback) {
final Callback<User> cb = callback;
RestAdapter restAdapter = buildRestAdapter();
System.out.println("Email " + email + " passowrd " + password);
User user = new User();
user.setEmail(email);
user.setPassword(password);
restAdapter.create(YutonAPI.class).postLogin(
user,
new Callback<User>() {
#Override
public void success(User user, Response response) {
System.out.println("succes");
System.out.println(response.getBody());
}
#Override
public void failure(RetrofitError error) {
System.out.println("error "+ error);
}
});
}
So this line of code:
System.out.println(response.getBody());
Should give me a response in json format however it didn't work because I'm getting the following output:
Link: http://i.imgur.com/mBQs1LL.png
So this is how my response in json format should look like:
{
"user": {
"image": "https://www.gravatar.com/avatar/e0a190604dc3dd2ee7b66bb95c20ef7f?d=identicon&s=512"
"email": "a#hotmail.com"
"name": "a"
"id": "566dfac21043a31820bf1ae6"
} -
}
I had already tested it on my server where I was making a post request. Below you can see a screenshot of it:
Link: http://i.imgur.com/PtEMR12.png
The issue here is that response.getBody() returns a TypedInputStream object, which you can't directly output because it isn't a String.
To read a TypedInputStream there are several options, as posted in: Retrofit callback get response body, the easiest being:
String body = new String(((TypedByteArray) response.getBody()).getBytes());
If the following error is thrown:
java.lang.ClassCastException: retrofit.client.UrlConnectionClient$TypedInputStream cannot be cast to retrofit.mime.TypedByteArray
Then make sure that you set .setLogLevel(RestAdapter.LogLevel.FULL) on the RestAdapter that you use to create the service.