I am making an Android app that connects to a web service via REST API and I have a dilemma with the design of the internal architecture.
Now I have class Client.java whose purpouse is make connect with the server (ConnectionMethod is Enum that contains GET|POST values):
public class Client {
private AsyncHttpClient client = new AsyncHttpClient(); //I use com.loopj.AsyncHttpClient to connect
private ConnectionMethod method;
private RequestParams params = new RequestParams();
private AsyncHttpResponseHandler responseHandler = new JsonHttpResponseHandler(){
#Override
public void onSuccess(int statusCode, Header[] headers, JSONObject response) {
//Actions when connection success
}
#Override
public void onFailure(int statusCode, Header[] headers, JSONObject response, Throwable error) {
//Actions when connection fails
}
};
public Client (RequestParams params, ConnectionMethod method) {
this.params = params;
this.method = method;
}
public void addParameters (Map<String, String> parameters) {
for (Map.Entry<String, String> entry : parameters.entrySet()) {
this.params.put(entry.getKey(), entry.getValue());
}
}
public ServerResponse connect () {
RequestHandle handle;
if (this.method==ConnectionMethod.POST) {
handle = postRequest();
}
else {
handle = getRequest();
}
//How can I treat here different type of responses homogeneously?
}
private RequestHandle getRequest () {
return client.get(Constants.getEndpoint(), this.params, this.responseHandler);
}
private RequestHandle postRequest () {
return client.post(Constants.getEndpoint(), this.params, this.responseHandler);
}
}
A sample method that requests info from the server is this:
public static void login (String login, String password) {
//This classes should be static or dynamic?
Map<String, String> map = new HashMap<String, String>();
map.put("login", login);
map.put("password", password);
map.put("method", "site_login");
Client c = new Client();
c.addParameters(map);
c.getRequest();
}
All server responses are JSON: {status:0, result:array/int/string} when response is correct and {status:-1, message:string} when response is incorrect.
Additionaly I want to make classes to model components from JSON result (User.java, Message.java...) and intermediate methods between UI and API to implement the logic of the app and classes.
What is the best way to design an homogeneous connection system that manages automatically correct/fail response and independent of model (user, message...)?
There is a bunch of frameworks which can make this whole process much easier.
For example Retrofit is very simple framework for mapping java classes to REST calls. It comes with gson which will automatically deserialize response from json to plain java objects.
It also allows use callbacks as well as rxJava Observables. It allows to handle errors as well.
You can check sample app: https://github.com/JakeWharton/u2020
You are describing tools that already exist. My favorite happens to be Retrofit but there are others out there. Retrofit can handle the success and fail responses and even map JSON directly to a POJO.
My API client
public class ApiClient {
private static ApiInterface sApiInterface;
public static ApiInterface getApiClient(Context context) {
//build the rest adapter
if (sApiInterface == null) {
final RestAdapter restAdapter = new RestAdapter.Builder()
.setEndpoint("example.com")
.build();
sApiInterface = restAdapter.create(ApiInterface.class);
}
return sApiInterface;
}
public interface ApiInterface {
#GET("/program/{id}")
void getProgram(#Path("id") int id, RetrofitCallback<Program> callback);
}
My RetrofitCallback
public class RetrofitCallback<S> implements Callback<S> {
private static final String TAG = RetrofitCallback.class.getSimpleName();
#Override
public void success(S s, Response response) {
}
#Override
public void failure(RetrofitError error) {
Log.e(TAG, "Failed to make http request for: " + error.getUrl());
Response errorResponse = error.getResponse();
if (errorResponse != null) {
Log.e(TAG, errorResponse.getReason());
if (errorResponse.getStatus() == 500) {
Log.e(TAG, "Handle Server Errors Here");
}
}
}
}
My model
public class Program {
#Expose
private doublea.models.Airtime Airtime;
#Expose
private String id;
#Expose
private String title;
#SerializedName("short_name")
#Expose
private String shortName;
#SerializedName("full_description")
#Expose
private String fullDescription;
#SerializedName("short_description")
#Expose
private String shortDescription;
#Expose
private doublea.models.Image Image;
#SerializedName("image")
#Expose
private String imageName;
#Expose
private List<Host> hosts = new ArrayList<Host>();
#Expose
private List<Category> categories = new ArrayList<Category>();
#Expose
private List<Airtime> airtimes = new ArrayList<Airtime>();
/** Getters and Setters */
public Program() {
}
How it is used.
private void executeProgramApiCall(int programId) {
ApiClient.getApiClient(this).getProgram(programId, new RetrofitCallback<Program>() {
#Override
public void success(Program program, Response response) {
super.success(program, response);
addDataToAdapter(program);
}
});
}
Related
im new with retrofit and now, when i know how to sent the normal data without any objects, just with parameters or simple body i want to know how to sent the objects...
I spent like 20h to debug it and i'm confused because i dont know how to do this...
There is my codes:
API Interface:
#POST("/api/assortment")
Call<PostAssortment> getAssortment(#Body String PostShipmentProgress);
PostAssortment class:
public class PostAssortment {
private String JSON;
#SerializedName("token")
#Expose
private String token;
#SerializedName("assortment")
#Expose
private Assortment assortment;
#SerializedName("tokens")
#Expose
private List<String> tokens;
#SerializedName("positions")
#Expose
private List<Position> positions;
#SerializedName("deviceId")
#Expose
private String deviceId;
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
public Shipment getAssortment() {
return assortment;
}
public void setAssortment(Assortment assortment) {
this.assortment = assortment;
}
public List<String> getTokens() {
return tokens;
}
public void setTokens(List<String> tokens) {
this.tokens = tokens;
}
public List<Position> getPositions() {
return positions;
}
public void setPositions(List<Position> positions) {
this.positions = positions;
}
public String getDeviceId() {
return deviceId;
}
public void setDeviceId(String deviceId) {
this.deviceId = deviceId;
}
public String getJSON() {
return JSON;
}
public void setJSON(String JSON) {
this.JSON = JSON;
}
}
And the mainJava class:
Gson gson = new Gson();
PostAssortment postAssortment= new PostAssortment();
List<String> tokens = new ArrayList<>();
tokens.add("someToken");
postAssortment.setTokens(tokens);
postAssortment.setDeviceId("aaaaa");
List<Position> currentPosition = new ArrayList<>();
Position cp = new Position();
cp.setItemName("Some");
cp.setPlace("POLAND");
cp.setTimestamp("2020-12-09T11:00:00");
currentPosition.add(cp);
postAssortment.setPositions(currentPosition);
String postAssortmentJSON = gson.toJson(postAssortment);
Call<PostAssortment> call = ApiLoginInterface.getAssortment(postAssortmentJSON);
call.enqueue(new Callback<PostAssortment>() {
#Override
public void onResponse(Call<PostAssortment> call, Response<PostAssortment> response) {
PostAssortment assortmentResponse = response.body();
}
#Override
public void onFailure(Call<PostAssortment> call, Throwable t) {
Log.d("FAILURE", "onFailure: " + t.getMessage());
}
});
}
And my retrofit onCreate:
Gson gson = new GsonBuilder()
.setLenient()
.create();
String BASE_URL = getString(API_URL);
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create(gson))
.build();
ApiLoginInterface = retrofit.create(ApiLoginInterface.class);
And after im trying to call it im not getting any point on call enqueue just a
Android: java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.String java.lang.Object.toString()' on a null object reference
Error...
Can someone describe this and help me to make it work? :/
You haven't provided enough information to help identify the error. Probably add the full stacktrace to the question as well. But if your API post request is expecting a json body I would start with the fixes below:
Remove this:
String postAssortmentJSON = gson.toJson(postAssortment);
Then pass your object as a pojo to your retrofit interface like this:
#POST("/api/assortment")
Call<PostAssortment> getAssortment(#Body PostAssortment postAssortment);
Then when doing your call you don't need to convert it to a string json string. The adapter does that for you:
Call<PostAssortment> call = ApiLoginInterface.getAssortment(postAssortment);
Post assortment in my problem will be in a list, so to make it works I need to change the Call to
For example, I have some REST API testing task.
I took Unirest framework, and what I have got some JSON extractors,
protected int extractStatus (HttpResponse<JsonNode> login) {
return login.getStatus();
}
protected String extractError (HttpResponse<JsonNode> login) {
return login.getBody().getObject()
.getJSONObject("data")
.getJSONObject("error")
.toString();
}
protected String extractEmail (HttpResponse<JsonNode> login) {
return login.getBody().getObject()
.getJSONObject("data")
.getJSONObject("result")
.getJSONObject("userProfile")
.getString("registrationEmail");
}
For my simple tests:
public class LoginJSON extends Request {
#Test
public void validLoginTest() {
response = login("probe#grr.la", "9876541");
Assert.assertEquals(200, extractStatus(response));
Assert.assertNotNull("ID expected", extractId(response));
Assert.assertNotNull("Token expected", extractToken(response));
Assert.assertEquals("probe#grr.la", extractEmail(response));
Assert.assertEquals("M", extractGender(response));
Assert.assertEquals("EmailEnabled", true, extractEmailEnabled(response));
Assert.assertEquals("EmailDisabled",false, extractEmailDisabled(response));
Assert.assertEquals(2, extractRolesCount(response));
Assert.assertTrue("User role expected", extractRoleByName(response, "ROLE_USER"));
Assert.assertTrue("Admin role expected", extractRoleByName(response, "ROLE_ADMIN"));
}
Maybe there was more simpliest way?
Try Gson with Retrofit!
HttpResponse<JsonNode> jsonResponse = request.asJson();
Gson gson = new Gson();
String responseJSONString = jsonResponse.getBody().toString();
MyResponseObject myObject = gson.fromJson(responseJSONString, MyResponseObject.class);
Classes
class MyResponseObject {
#Serializable("data")
private MyDataObject myData;
#get set methods
}
class MyDataObject {
#Serializable("result")
private MyResultObject myResultObject;
#get set methods
}
class MyResultObject {
#Serializable("userProfile")
private UserProfileDao userProfileDao;
#get set methods
}
class UserProfileDao {
#Serializable("registerationEmail")
private String registerationEmail;
#get set methods
}
You could do a try catch for successful parse or failed parse.
I am using framework in which I simply pass a java object. Framework methods and classes converts this object to json string and makes a post request. Also I am getting response as java object.
This same thing I wish to implement using volley library
I just want to pass Java request object and in response also want java object
Please suggest me a way how could I do that.
Thanks...
I have something that might help you starting.
Just create your BaseRequest where you pass what you are expecting as Response. Son library takes care of converting your Json to you Java Object type.
BaseRequest
public class BaseRequest<T> extends Request<T> {
private final Class<T> resultClass;
private final Response.Listener<T> listener;
Gson gson = new Gson();
private final int REQUEST_TIMEOUT_LIMIT_SECONDS = 10; // Value in seconds
public BaseRequest(int method, String url, Class<T> resultClass, Response.Listener<T> listenerSuccess, Response.ErrorListener listenerError) {
super(method, url, listenerError);
this.resultClass = resultClass;
this.listener = listenerSuccess;
this.setRetryPolicy(new DefaultRetryPolicy(REQUEST_TIMEOUT_LIMIT_SECONDS * 1000, DefaultRetryPolicy.DEFAULT_MAX_RETRIES,
DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));
}
#Override
protected Response<T> parseNetworkResponse(NetworkResponse response) {
try {
String json = new String(
response.data,
HttpHeaderParser.parseCharset(response.headers));
return Response.success(
gson.fromJson(json, resultClass),
HttpHeaderParser.parseCacheHeaders(response));
} catch (UnsupportedEncodingException e) {
return Response.error(new ParseError(e));
} catch (JsonSyntaxException e) {
return Response.error(new ParseError(e));
}
}
#Override
protected void deliverResponse(T response) {
listener.onResponse(response);
}
}
The method parseNetworkResponse will take care of converting your Json to Java Object type.
Now you can create a class for each Request, for example Login:
public class LoginRequest<T> extends BaseRequest<T> {
private final Gson gson = new Gson();
private final String email;
private final String password;
public LoginRequest(String email, String password, Class<T> clazz,
Response.Listener<T> listener, Response.ErrorListener errorListener) {
super(Request.Method.POST, "url here...", clazz, listener, errorListener);
this.email = email;
this.password = password;
}
#Override
protected Map<String, String> getParams() throws AuthFailureError {
Map<String, String> params = new HashMap<>();
params.put("param1", email);
params.put("param2", password);
return params;
}
}
Finally create the call for your request (inside your activity, fragment, wherever you need):
LoginRequest request = new LoginRequest<>("aaaa#email.com", "myDummyPassword", LoginResponseModel.class, new Response.Listener<LoginResponseModel>() {
#Override
public void onResponse(LoginResponseModel loginResponseModel) {
//Success
}
}, new BaseError(getApplicationContext()) {
#Override
public void onError(ErrorResponse response) {
//Erros
}
});
This way you will receive the objects you want, parsed from Gson.
Can you anyone help me giving good example on how to use retrofit for posting large data from my local DB to mysql server.
Currently I am using async-http API and strangely there is always memory error coming up. I am looking for better API that wont give memory error while uploading huge text of data.
This is my setup:
List<TextDetails> unTextList = dbvalue.getTextData();
for (TextDetails td : unTextList)
{
String textID = td.getSerialNumber();
String textSMS = td.getText();
String textAddress = td.getFulladdress();
String textDate = td.getFulldate();
String textException = td.getExceptiontext();
textDetailsBackUpDataOnline(textID , textSMS, textAddress, textDate, textException);
}
private void textDetailsBackUpDataOnline(final String textID ,
String textSMS, String textAddress, String textDate, String textException)
{
final String uploadWebsite = url_backup_text_details;
RequestParams requestParams = new RequestParams();
requestParams.put("textSMS", textSMS);
requestParams.put("textAddress", textAddress);
requestParams.put("textDate", textDate);
requestParams.put("textException", textException);
Text_HttpClient.post(uploadWebsite, requestParams, new AsyncHttpResponseHandler()
{
#Override
public void onSuccess(int statusCode, org.apache.http.Header[] headers, byte[] responseBody)
{
Log.e("textID", "= how many times");
}
#Override
public void onFailure(int statusCode, org.apache.http.Header[] headers, byte[] errorResponse, Throwable e)
{
e.printStackTrace(System.out);
}
});
}
Text_HttpClient class has the following:
public class Text_HttpClient
{
private static AsyncHttpClient client = new AsyncHttpClient();
public static void get(String url, RequestParams params, AsyncHttpResponseHandler responseHandler)
{
client.get(url, params, responseHandler);
}
public static void post(String url, RequestParams requestParams, AsyncHttpResponseHandler responseHandler)
{
client.post(url, requestParams, responseHandler);
}
}
1) Write service interface:
public interface ArticleGetListService {
#FormUrlEncoded // Request will have "application/x-www-form-urlencoded" MIME type
#POST("/api/Article/ArticleGetList")
public void getArticleList(#Field("LanguageCode") String languageCode,
#Field("CategoryId") String categoryId,
#Field("Token") String token,
Callback<ArticleViewPojo> response); //POJO: The json retrieved from the server is added to this class.
}
Here my Rest service requires 3 Parameters, change it as your need.
2) Write POJO for converting JSON returned from Rest Api into java class object so you can use data.
Just copy your JSON into this site, choose JSON source type, annotation as Gson. It will generate POJO for your JSON automatically.
3)On your Main Activity
RestAdapter restAdapter = new RestAdapter.Builder()
.setLogLevel(RestAdapter.LogLevel.FULL)
.setEndpoint(baseUrl)
.build();
ArticleGetListService articleGetListService = restAdapter.create(ArticleGetListService.class);
Callback<ArticleViewPojo> callback = new Callback<ArticleViewPojo>() {
#Override
public void success(ArticleViewPojo model, Response response) {
//use model which is data returned to you
}
#Override
public void failure(RetrofitError error) {
//handle error
}
};
//START REST CALL
articleGetListService.getArticleList(languageCode, categoryId, token, callback);
//above parameters are those written in service interface at 1
//Whole Url is baseUrl+ArticleGetListService in above example
I have the following code to create a netty web server based on http server created in the netty's example. My buisness logic is the following.
public class HttpServerHandler extends SimpleChannelInboundHandler<Object> {
private final static Logger LOG = LogManager
.getLogger(HttpServerHandler.class);
private WorkflowService workflowService;
private HttpRequest request;
private final StringBuffer buff = new StringBuffer();
private API avalancheApi;
public HttpServerHandler(WorkflowService workflowService) {
this.workflowService = workflowService;
this.avalancheApi = new API(this.workflowService);
}
#Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
LOG.debug("channelActive");
LOG.debug(ctx.toString());
};
#Override
public void channelReadComplete(ChannelHandlerContext ctx) {
ctx.flush();
}
#Override
public void channelRead0(ChannelHandlerContext ctx, Object msg)
throws IOException {
avalancheApi.setContext(ctx);
if (msg instanceof HttpRequest) {
HttpRequest request = this.request = (HttpRequest) msg;
if (HttpHeaders.is100ContinueExpected(request)) {
send100Continue(ctx);
}
String command = getCommand(request);
LOG.debug(command);
Map<String, List<String>> parameters = getParameters(request);
LOG.debug(parameters);
switch (command) {
case "/login":
ctx = avalancheApi.login(parameters);
break;
case "/test":
ctx = avalancheApi.test();
break;
default:
break;
}
}
if (msg instanceof LastHttpContent) {
LOG.debug("msg is of LastHttpContent");
}
if (!HttpHeaders.isKeepAlive(request)) {
// If keep-alive is off, close the connection once the content is
// fully written.
ctx.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(
ChannelFutureListener.CLOSE);
}
}
public class API {
private static final Logger LOG = LogManager.getLogger(API.class);
private ChannelHandlerContext ctx;
private HttpResponse response;
private WorkflowService workflowService;
public API(WorkflowService workflowService) {
this.workflowService = workflowService;
this.ctx = null;
}
public void setContext(ChannelHandlerContext ctx) {
this.ctx = ctx;
}
public ChannelHandlerContext login(Map<String, List<String>> parameters) {
boolean success;
String username = getUsername(parameters);
String password = getPassword(parameters);
User user = null;
user = workflowService.login(username, password);
success = validateLogin(user);
this.response = writeLoginResponse(success);
this.ctx.write(this.response);
writeLoginContext(success, response);
return this.ctx;
}
private void writeLoginContext(boolean success, HttpResponse response) {
JsonObject jsonResponseMessage = new JsonObject();
jsonResponseMessage.addProperty("result", success);
LOG.debug(jsonResponseMessage.toString());
this.ctx.write(Unpooled.copiedBuffer(jsonResponseMessage.toString(),
CharsetUtil.UTF_8));
this.response.headers().set(HttpHeaders.Names.CONTENT_LENGTH,
jsonResponseMessage.toString().length());
}
private HttpResponse writeLoginResponse(boolean success) {
if (success)
return createSuccessfullLoginResponse();
else
return createLoginFailureResponse();
}
private HttpResponse createLoginFailureResponse() {
return Response.loginFailureResponse();
}
private HttpResponse createSuccessfullLoginResponse() {
return Response.loginSuccessResponse();
}
}
Response class is only creating the response and the content_type which is of application/json. Content Length is set in the API class. Using python client with requests, results in the request made in http://localhost/login?username=name&password=pass works only once. The second time everything works, but it doesn't finish processing the request and send the response object. Api calls get executed normally, and I also get the message of LastHttpContext message getting print. The problem sometimes happens with browser too. Am I missing something? Maybe the content data and the content length doesn't match? Could it be that when making requests from python client, the content of the previous context isn't flushed and the content_length value of the header and content length of the context doesn't match?
Just wild guess
this.response.headers().set(HttpHeaders.Names.CONTENT_LENGTH,
jsonResponseMessage.toString().length());
Instead, shouldn't you be doing jsonResponseMessage.toString().getBytes().length ?? Sometimes, one character is not just one byte.
My guess is that you have overwritten the context in your API class, and as a result, are writing the response to the wrong context. Is your HttpServerHandler marked with #Shareable?