4
\$\begingroup\$

I'm new in Android, Java and the whole object-oriented programming, I'm trying to develop an app which needs to listen on many web services (PHP scripts written by me returning a standardized JSON string) and I thought I could design a class which takes this commitment: ServiceListener.

import org.json.JSONException;
import org.json.JSONObject;

import java.io.IOException;

import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;

public abstract class ServiceListener implements runnable {
    public static final String SERVICE_AIRPORTS = "http://example.com/app/a/services/json/airports.php";
    // ...As many constants as web services
    private static final int STATUS_CODE_FORMAT_ERROR = 5;
    private static final int STATUS_CODE_EXECUTION_FAILED = 3;
    private static final int INTERNAL_ERROR = -1;
    private static final int GENERIC_ERROR = 0;

    private JSONObject jsonResult, dataToSend;
    private String serviceUrl;

    protected ServiceListener(String serviceUrl, JSONObject dataToSend) {
        this.serviceUrl = serviceUrl;
        this.dataToSend = dataToSend;

        new Thread(this).start();
    }

    protected JSONObject listenToService() throws JSONException {
        String jsonString;
        OkHttpClient client = new OkHttpClient();
        Request request = new Request.Builder().url(serviceUrl + "?data=" + dataToSend.toString(0)).build();
        try (Response response = client.newCall(request).execute()) {
            jsonString = response.body().string();
            jsonResult = new JSONObject(jsonString);


            if(jsonResult.has("Status")) {
                if(jsonResult.getString("Status").equals("Success")) {
                    return jsonResult;
                }
                else if(jsonResult.getString("Status").equals("Failed")) {
                    jsonResult.put("StatusCode", STATUS_CODE_EXECUTION_FAILED);
                    jsonResult.put("StatusMessage", "Service failed execution");
                    return jsonResult;
                }
            }
            else {
                jsonResult.put("StatusCode", STATUS_CODE_FORMAT_ERROR);
                jsonResult.put("StatusMessage", "Service invalid format");
            }
        }
        catch (IOException e) {
            e.printStackTrace();
            jsonResult.put("StatusCode", INTERNAL_ERROR);
            return jsonResult;
        }
        jsonResult.put("Error", GENERIC_ERROR);
        return  jsonResult;
    }
}

Then you can get the response wherever you want using this:

try {
    JSONObject dataToSend = new JSONObject(/*data to send with GET e.g: UserToken, Query and so on...will be interpreted on the web service*/);
    new ServiceListener(ServiceListener.SERVICE_DATE_SELECTOR, dataToSend) {
        @Override public void run() {
            try {
                JSONObject jo = new JSONObject().put("Content", listenToService());
                // Do whatever you want with the JSONObject (jo)
                Log.d("joContent", jo.getJSONObject("Content").toString(0));
            } catch(JSONException e) {
                e.printStackTrace();
            }
        }
    };
} catch(JSONException e) {
    e.printStackTrace();
}

Is it a good and fast way to request for (my own) web services? Eager to receive criticism

\$\endgroup\$
4
  • \$\begingroup\$ Requesting data with OkHttpClient is fast enough. So basically you have implemented the download well. However, I would rather use IntentService instead of Runnable - developer.android.com/reference/android/app/IntentService.html \$\endgroup\$
    – Tom11
    Commented Dec 22, 2016 at 14:14
  • \$\begingroup\$ Thank you @Tom11, do you think this class could be useful for others (even you)? I would like to go further in developing this but I don't know if there is something else which may be a better approach. \$\endgroup\$ Commented Dec 22, 2016 at 14:24
  • \$\begingroup\$ How do you provided the dataToSend to your service? It is not clear in your code where that data comes from. \$\endgroup\$ Commented Dec 22, 2016 at 16:22
  • \$\begingroup\$ @EdwinDalorzo I forgot to update the name of the member (#18) and the constructor parameter (#21) (jsonSource becomes dataToSend). Anyway there could be some service which doesn't need some data to interpret so I'm going to overload the constructor. \$\endgroup\$ Commented Dec 23, 2016 at 9:39

1 Answer 1

1
\$\begingroup\$

You may consider abstracting the idea of an HttpService that provides capabilities to make remote endpoint invocations via http.

interface HtppService {
   <T> T get(URI endpoint, Class<T> responseType);
   <T> T put(URI endpoint, Object body, Class<T> responseType);
   <T> T post(URI endpoint, Object body, Class<T> responseType);
   ///...
}

The advantages are:

  • You encapsulate the reusable logic of making http remote calls, since many of your services will want to do this over and over.
  • Your remote services no longer need to worry about how to make remote calls, they will be provided with your own HttpService implementation for that matter. Then you can focus on the important business data.
  • Your implementations can vary, and today you can have one using OkHttp but tomorrow you might just as well use something like, e.g. Spring RestTemplate.
  • Also you totally encapsulate and control your dependencies of third-party libraries. In this case, your dependency with OkHttp won't be all over the place, but only in the implementation of this interface.

Wit this interface defined you could first work in the implementation of its methods, expecting that all your remote services would use them.

class OkHttpService implements HttpService {

   private final HttpClient httpClient;

   public(HttpClient httClient) {
      this.httpClient = client;
   }

   public <T> get(URI uri, Class<T> respType) {
     //TODO: implement generic get call
   }

  <T> T put(URI endpoint, Object body, Class<T> responseType) {
     //TODO: implement generic put call
   }

   //etc, etc

}

Then you could finally build your remote service. I would start by defining its interface:

interface AirportService {
  Airport[] getAirportsByCity(String city);
}

Then you can provide an implementation of this service that will make remote calls through your HttpService:

class DefaultAirportService implements AirportService {

   private final String host;
   private final HttpService httpService;

   public DefaultAirportService(String host, HttpService httpService) {
      this.host = host;
      this.httpService = httpService;
   }

   public Airport[] getAirportsByCity(String city) {
      URI endpoint = URI.create(String.format("%s/airports?city=%s", host, city));
      try {
          return httpService.get(endpoint, Airport[].class);
      }
      catch(Exception e) {
        throw AirportServiceException("Failed to get airports for " + city, e);
      }
   }
}

There are few advantages here:

  • First, by passing an HttpService instance as an argument to the constructor you can easily mock that service and test your AirportService implementation in isolation.
  • Also, the fact that we accept the host name as one of the arguments will help you configure different instances of your service for different environments. For example, in integrations servers you may want the endpoint to point to a test server.
  • Finally, you can concentrate on your business logic here, and avoid having to worry about Http calls and how to deal with them.

Once you have reached that point, if you want to encapsulate your service in a runnable object, that is going to be quite simple to do:

AirportService airportService = new DefaultAirpotService("http://localhost:8080", myHttpService);

Runnable runService = new Runnable() {
   public void run() {
      aiportService.getAirportsByCity("Miami"); 
   }
}
new Thread(runService).start();
\$\endgroup\$

Not the answer you're looking for? Browse other questions tagged or ask your own question.