Call multiple asynchronous HTTP requests and wait for the result












1














I have a Repository class which looks like this:



public class Repository extends Observable {

private List<Event> events;
private List<Article> articles;
private List<Article> sportArticles;
private List<Article> fitnessArticles;
private List<Article> governmentArticles;
private Article mainArticle;
private Configuration config;


public Repository() {
loadRepository();
}

private void loadRepository() {
ExecutorService exService = Executors.newSingleThreadExecutor();
exService.submit(new Runnable() {
@Override
public void run() {
try {
OkHttpClient client = new OkHttpClient.Builder().connectTimeout(15, TimeUnit.SECONDS).build();
final Gson gson = new GsonBuilder().registerTypeHierarchyAdapter(Calendar.class, new CalendarGson()).create();
final AtomicBoolean error = new AtomicBoolean();
final CountDownLatch latch = new CountDownLatch(7);
Request.Builder requestbuilder = new Request.Builder();
Request articlesRequest = requestbuilder.url("https://apiurlarticles").build();
Request eventsRequest = requestbuilder.url("https://apiurlevents").build();
Request sportArticlesRequest = requestbuilder.url("https://apiurlsports").build();
Request fitnessArticlesRequest = requestbuilder.url("https://apiurlfitness").build();
Request governmentArticlesrequest = requestbuilder.url("https://apiurlgovernment").build();
Request mainArticleRequest = requestbuilder.url("https://apiurlmain").build();
Request configurationRequest = requestbuilder.url("https://apiurlconfig").build();

//Article Request
client.newCall(articlesRequest).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
error.set(true);
latch.countDown();
}
@Override
public void onResponse(Call call, Response response) {
try {
ResponseBody body = response.body();
if (response.code() != 200 || body == null) {
error.set(true);
} else {
articles = gson.fromJson(body.string(), new TypeToken<List<Article>>() {
}.getType());
}
} catch (IOException | JsonSyntaxException e) {
error.set(true);
}
latch.countDown();
}
});

//Events Request
client.newCall(eventsrequest).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
error.set(true);
latch.countDown();
}
@Override
public void onResponse(Call call, Response response) {
try {
ResponseBody body = response.body();
if (response.code() != 200 || body == null) {
error.set(true);
} else {
events = gson.fromJson(body.string(), new TypeToken<List<Event>>() {
}.getType());
}
} catch (IOException | JsonSyntaxException e) {
error.set(true);
}
latch.countDown();
}
});

//Sports Request
client.newCall(sportArticlesRequest).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
error.set(true);
latch.countDown();
}
@Override
public void onResponse(Call call, Response response) {
try {
ResponseBody body = response.body();
if (response.code() != 200 || body == null) {
error.set(true);
} else {
sportArticles = gson.fromJson(body.string(), new TypeToken<List<Article>>() {
}.getType());
}
} catch (IOException | JsonSyntaxException e) {
error.set(true);
}
latch.countDown();
}
});

//Fitness Request
client.newCall(fitnessArticlesRequest).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
error.set(true);
latch.countDown();
}
@Override
public void onResponse(Call call, Response response) {
try {
ResponseBody body = response.body();
if (response.code() != 200 || body == null) {
error.set(true);
} else {
fitnessArticles = gson.fromJson(body.string(), new TypeToken<List<Article>>() {
}.getType());
}
} catch (IOException | JsonSyntaxException e) {
error.set(true);
}
latch.countDown();
}
});

//Government Request
client.newCall(governmentArticlesrequest).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
error.set(true);
latch.countDown();
}
@Override
public void onResponse(Call call, Response response) {
try {
ResponseBody body = response.body();
if (response.code() != 200 || body == null) {
error.set(true);
} else {
governmentArticles = gson.fromJson(body.string(), new TypeToken<List<Article>>() {
}.getType());
}
} catch (IOException | JsonSyntaxException e) {
error.set(true);
}
latch.countDown();
}
});

//Main Article Request
client.newCall(mainArticleRequest).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
error.set(true);
latch.countDown();
}
@Override
public void onResponse(Call call, Response response) {
try {
ResponseBody body = response.body();
if (response.code() != 200 || body == null) {
error.set(true);
} else {
mainArticle = gson.fromJson(body.string(), new TypeToken<Article>() {
}.getType());
}
} catch (IOException | JsonSyntaxException e) {
error.set(true);
}
latch.countDown();
}
});

//Configuration request
client.newCall(configurationRequest).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
error.set(true);
latch.countDown();
}
@Override
public void onResponse(Call call, Response response) {
try {
ResponseBody body = response.body();
if (response.code() != 200 || body == null) {
error.set(true);
} else {
config = gson.fromJson(body.string(), new TypeToken<Configuration>() {
}.getType());
}
} catch (IOException | JsonSyntaxException e) {
error.set(true);
}
latch.countDown();
}
});
try {
latch.await();
} catch (InterruptedException e) {
error.set(true);
}
notifyObservers(error.get() ? HTTPRequestStatus.HTTPERROR : HTTPRequestStatus.OK);
} catch (KeyManagementException | NoSuchAlgorithmException | KeyStoreException e) {
notifyObservers(HTTPRequestStatus.TLSERROR);
}
}
});
exService.shutdown();
}
}


This code calls several HTTP requests asynchronously and wait for all of them to conclude. If any request fails it notifies with an error, otherwise success. These requests returns JSON and I parse them using Gson.



For simplicity I have only showed two requests, however I want to refactor this code for any number of requests. There is a lot of similar code, but I can't figure a way to refactor it. I have already tried to create a custom Callback class however I can't figure a way to pass a Type and return the parsed value.



How would you approach this one?










share|improve this question









New contributor




Exprove is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.
















  • 2




    Simplicity isn't usually the way to go here. I recommend you show the rest of your code. You might get different answers based on the less "simple" code than you would with what you currently have. Also please change the title to describe What your code Does as apposed to your goal. (Almost everyone wants to refactor their code.)
    – bruglesco
    Dec 27 '18 at 19:36










  • @bruglesco The difference between this code and the actual one is that there are like 10 Request Objects and 10 client.newCalls(request) , should I include them anyway?
    – Exprove
    Dec 27 '18 at 20:55












  • Yes. You should.
    – bruglesco
    Dec 27 '18 at 21:17
















1














I have a Repository class which looks like this:



public class Repository extends Observable {

private List<Event> events;
private List<Article> articles;
private List<Article> sportArticles;
private List<Article> fitnessArticles;
private List<Article> governmentArticles;
private Article mainArticle;
private Configuration config;


public Repository() {
loadRepository();
}

private void loadRepository() {
ExecutorService exService = Executors.newSingleThreadExecutor();
exService.submit(new Runnable() {
@Override
public void run() {
try {
OkHttpClient client = new OkHttpClient.Builder().connectTimeout(15, TimeUnit.SECONDS).build();
final Gson gson = new GsonBuilder().registerTypeHierarchyAdapter(Calendar.class, new CalendarGson()).create();
final AtomicBoolean error = new AtomicBoolean();
final CountDownLatch latch = new CountDownLatch(7);
Request.Builder requestbuilder = new Request.Builder();
Request articlesRequest = requestbuilder.url("https://apiurlarticles").build();
Request eventsRequest = requestbuilder.url("https://apiurlevents").build();
Request sportArticlesRequest = requestbuilder.url("https://apiurlsports").build();
Request fitnessArticlesRequest = requestbuilder.url("https://apiurlfitness").build();
Request governmentArticlesrequest = requestbuilder.url("https://apiurlgovernment").build();
Request mainArticleRequest = requestbuilder.url("https://apiurlmain").build();
Request configurationRequest = requestbuilder.url("https://apiurlconfig").build();

//Article Request
client.newCall(articlesRequest).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
error.set(true);
latch.countDown();
}
@Override
public void onResponse(Call call, Response response) {
try {
ResponseBody body = response.body();
if (response.code() != 200 || body == null) {
error.set(true);
} else {
articles = gson.fromJson(body.string(), new TypeToken<List<Article>>() {
}.getType());
}
} catch (IOException | JsonSyntaxException e) {
error.set(true);
}
latch.countDown();
}
});

//Events Request
client.newCall(eventsrequest).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
error.set(true);
latch.countDown();
}
@Override
public void onResponse(Call call, Response response) {
try {
ResponseBody body = response.body();
if (response.code() != 200 || body == null) {
error.set(true);
} else {
events = gson.fromJson(body.string(), new TypeToken<List<Event>>() {
}.getType());
}
} catch (IOException | JsonSyntaxException e) {
error.set(true);
}
latch.countDown();
}
});

//Sports Request
client.newCall(sportArticlesRequest).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
error.set(true);
latch.countDown();
}
@Override
public void onResponse(Call call, Response response) {
try {
ResponseBody body = response.body();
if (response.code() != 200 || body == null) {
error.set(true);
} else {
sportArticles = gson.fromJson(body.string(), new TypeToken<List<Article>>() {
}.getType());
}
} catch (IOException | JsonSyntaxException e) {
error.set(true);
}
latch.countDown();
}
});

//Fitness Request
client.newCall(fitnessArticlesRequest).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
error.set(true);
latch.countDown();
}
@Override
public void onResponse(Call call, Response response) {
try {
ResponseBody body = response.body();
if (response.code() != 200 || body == null) {
error.set(true);
} else {
fitnessArticles = gson.fromJson(body.string(), new TypeToken<List<Article>>() {
}.getType());
}
} catch (IOException | JsonSyntaxException e) {
error.set(true);
}
latch.countDown();
}
});

//Government Request
client.newCall(governmentArticlesrequest).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
error.set(true);
latch.countDown();
}
@Override
public void onResponse(Call call, Response response) {
try {
ResponseBody body = response.body();
if (response.code() != 200 || body == null) {
error.set(true);
} else {
governmentArticles = gson.fromJson(body.string(), new TypeToken<List<Article>>() {
}.getType());
}
} catch (IOException | JsonSyntaxException e) {
error.set(true);
}
latch.countDown();
}
});

//Main Article Request
client.newCall(mainArticleRequest).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
error.set(true);
latch.countDown();
}
@Override
public void onResponse(Call call, Response response) {
try {
ResponseBody body = response.body();
if (response.code() != 200 || body == null) {
error.set(true);
} else {
mainArticle = gson.fromJson(body.string(), new TypeToken<Article>() {
}.getType());
}
} catch (IOException | JsonSyntaxException e) {
error.set(true);
}
latch.countDown();
}
});

//Configuration request
client.newCall(configurationRequest).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
error.set(true);
latch.countDown();
}
@Override
public void onResponse(Call call, Response response) {
try {
ResponseBody body = response.body();
if (response.code() != 200 || body == null) {
error.set(true);
} else {
config = gson.fromJson(body.string(), new TypeToken<Configuration>() {
}.getType());
}
} catch (IOException | JsonSyntaxException e) {
error.set(true);
}
latch.countDown();
}
});
try {
latch.await();
} catch (InterruptedException e) {
error.set(true);
}
notifyObservers(error.get() ? HTTPRequestStatus.HTTPERROR : HTTPRequestStatus.OK);
} catch (KeyManagementException | NoSuchAlgorithmException | KeyStoreException e) {
notifyObservers(HTTPRequestStatus.TLSERROR);
}
}
});
exService.shutdown();
}
}


This code calls several HTTP requests asynchronously and wait for all of them to conclude. If any request fails it notifies with an error, otherwise success. These requests returns JSON and I parse them using Gson.



For simplicity I have only showed two requests, however I want to refactor this code for any number of requests. There is a lot of similar code, but I can't figure a way to refactor it. I have already tried to create a custom Callback class however I can't figure a way to pass a Type and return the parsed value.



How would you approach this one?










share|improve this question









New contributor




Exprove is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.
















  • 2




    Simplicity isn't usually the way to go here. I recommend you show the rest of your code. You might get different answers based on the less "simple" code than you would with what you currently have. Also please change the title to describe What your code Does as apposed to your goal. (Almost everyone wants to refactor their code.)
    – bruglesco
    Dec 27 '18 at 19:36










  • @bruglesco The difference between this code and the actual one is that there are like 10 Request Objects and 10 client.newCalls(request) , should I include them anyway?
    – Exprove
    Dec 27 '18 at 20:55












  • Yes. You should.
    – bruglesco
    Dec 27 '18 at 21:17














1












1








1







I have a Repository class which looks like this:



public class Repository extends Observable {

private List<Event> events;
private List<Article> articles;
private List<Article> sportArticles;
private List<Article> fitnessArticles;
private List<Article> governmentArticles;
private Article mainArticle;
private Configuration config;


public Repository() {
loadRepository();
}

private void loadRepository() {
ExecutorService exService = Executors.newSingleThreadExecutor();
exService.submit(new Runnable() {
@Override
public void run() {
try {
OkHttpClient client = new OkHttpClient.Builder().connectTimeout(15, TimeUnit.SECONDS).build();
final Gson gson = new GsonBuilder().registerTypeHierarchyAdapter(Calendar.class, new CalendarGson()).create();
final AtomicBoolean error = new AtomicBoolean();
final CountDownLatch latch = new CountDownLatch(7);
Request.Builder requestbuilder = new Request.Builder();
Request articlesRequest = requestbuilder.url("https://apiurlarticles").build();
Request eventsRequest = requestbuilder.url("https://apiurlevents").build();
Request sportArticlesRequest = requestbuilder.url("https://apiurlsports").build();
Request fitnessArticlesRequest = requestbuilder.url("https://apiurlfitness").build();
Request governmentArticlesrequest = requestbuilder.url("https://apiurlgovernment").build();
Request mainArticleRequest = requestbuilder.url("https://apiurlmain").build();
Request configurationRequest = requestbuilder.url("https://apiurlconfig").build();

//Article Request
client.newCall(articlesRequest).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
error.set(true);
latch.countDown();
}
@Override
public void onResponse(Call call, Response response) {
try {
ResponseBody body = response.body();
if (response.code() != 200 || body == null) {
error.set(true);
} else {
articles = gson.fromJson(body.string(), new TypeToken<List<Article>>() {
}.getType());
}
} catch (IOException | JsonSyntaxException e) {
error.set(true);
}
latch.countDown();
}
});

//Events Request
client.newCall(eventsrequest).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
error.set(true);
latch.countDown();
}
@Override
public void onResponse(Call call, Response response) {
try {
ResponseBody body = response.body();
if (response.code() != 200 || body == null) {
error.set(true);
} else {
events = gson.fromJson(body.string(), new TypeToken<List<Event>>() {
}.getType());
}
} catch (IOException | JsonSyntaxException e) {
error.set(true);
}
latch.countDown();
}
});

//Sports Request
client.newCall(sportArticlesRequest).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
error.set(true);
latch.countDown();
}
@Override
public void onResponse(Call call, Response response) {
try {
ResponseBody body = response.body();
if (response.code() != 200 || body == null) {
error.set(true);
} else {
sportArticles = gson.fromJson(body.string(), new TypeToken<List<Article>>() {
}.getType());
}
} catch (IOException | JsonSyntaxException e) {
error.set(true);
}
latch.countDown();
}
});

//Fitness Request
client.newCall(fitnessArticlesRequest).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
error.set(true);
latch.countDown();
}
@Override
public void onResponse(Call call, Response response) {
try {
ResponseBody body = response.body();
if (response.code() != 200 || body == null) {
error.set(true);
} else {
fitnessArticles = gson.fromJson(body.string(), new TypeToken<List<Article>>() {
}.getType());
}
} catch (IOException | JsonSyntaxException e) {
error.set(true);
}
latch.countDown();
}
});

//Government Request
client.newCall(governmentArticlesrequest).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
error.set(true);
latch.countDown();
}
@Override
public void onResponse(Call call, Response response) {
try {
ResponseBody body = response.body();
if (response.code() != 200 || body == null) {
error.set(true);
} else {
governmentArticles = gson.fromJson(body.string(), new TypeToken<List<Article>>() {
}.getType());
}
} catch (IOException | JsonSyntaxException e) {
error.set(true);
}
latch.countDown();
}
});

//Main Article Request
client.newCall(mainArticleRequest).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
error.set(true);
latch.countDown();
}
@Override
public void onResponse(Call call, Response response) {
try {
ResponseBody body = response.body();
if (response.code() != 200 || body == null) {
error.set(true);
} else {
mainArticle = gson.fromJson(body.string(), new TypeToken<Article>() {
}.getType());
}
} catch (IOException | JsonSyntaxException e) {
error.set(true);
}
latch.countDown();
}
});

//Configuration request
client.newCall(configurationRequest).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
error.set(true);
latch.countDown();
}
@Override
public void onResponse(Call call, Response response) {
try {
ResponseBody body = response.body();
if (response.code() != 200 || body == null) {
error.set(true);
} else {
config = gson.fromJson(body.string(), new TypeToken<Configuration>() {
}.getType());
}
} catch (IOException | JsonSyntaxException e) {
error.set(true);
}
latch.countDown();
}
});
try {
latch.await();
} catch (InterruptedException e) {
error.set(true);
}
notifyObservers(error.get() ? HTTPRequestStatus.HTTPERROR : HTTPRequestStatus.OK);
} catch (KeyManagementException | NoSuchAlgorithmException | KeyStoreException e) {
notifyObservers(HTTPRequestStatus.TLSERROR);
}
}
});
exService.shutdown();
}
}


This code calls several HTTP requests asynchronously and wait for all of them to conclude. If any request fails it notifies with an error, otherwise success. These requests returns JSON and I parse them using Gson.



For simplicity I have only showed two requests, however I want to refactor this code for any number of requests. There is a lot of similar code, but I can't figure a way to refactor it. I have already tried to create a custom Callback class however I can't figure a way to pass a Type and return the parsed value.



How would you approach this one?










share|improve this question









New contributor




Exprove is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.











I have a Repository class which looks like this:



public class Repository extends Observable {

private List<Event> events;
private List<Article> articles;
private List<Article> sportArticles;
private List<Article> fitnessArticles;
private List<Article> governmentArticles;
private Article mainArticle;
private Configuration config;


public Repository() {
loadRepository();
}

private void loadRepository() {
ExecutorService exService = Executors.newSingleThreadExecutor();
exService.submit(new Runnable() {
@Override
public void run() {
try {
OkHttpClient client = new OkHttpClient.Builder().connectTimeout(15, TimeUnit.SECONDS).build();
final Gson gson = new GsonBuilder().registerTypeHierarchyAdapter(Calendar.class, new CalendarGson()).create();
final AtomicBoolean error = new AtomicBoolean();
final CountDownLatch latch = new CountDownLatch(7);
Request.Builder requestbuilder = new Request.Builder();
Request articlesRequest = requestbuilder.url("https://apiurlarticles").build();
Request eventsRequest = requestbuilder.url("https://apiurlevents").build();
Request sportArticlesRequest = requestbuilder.url("https://apiurlsports").build();
Request fitnessArticlesRequest = requestbuilder.url("https://apiurlfitness").build();
Request governmentArticlesrequest = requestbuilder.url("https://apiurlgovernment").build();
Request mainArticleRequest = requestbuilder.url("https://apiurlmain").build();
Request configurationRequest = requestbuilder.url("https://apiurlconfig").build();

//Article Request
client.newCall(articlesRequest).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
error.set(true);
latch.countDown();
}
@Override
public void onResponse(Call call, Response response) {
try {
ResponseBody body = response.body();
if (response.code() != 200 || body == null) {
error.set(true);
} else {
articles = gson.fromJson(body.string(), new TypeToken<List<Article>>() {
}.getType());
}
} catch (IOException | JsonSyntaxException e) {
error.set(true);
}
latch.countDown();
}
});

//Events Request
client.newCall(eventsrequest).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
error.set(true);
latch.countDown();
}
@Override
public void onResponse(Call call, Response response) {
try {
ResponseBody body = response.body();
if (response.code() != 200 || body == null) {
error.set(true);
} else {
events = gson.fromJson(body.string(), new TypeToken<List<Event>>() {
}.getType());
}
} catch (IOException | JsonSyntaxException e) {
error.set(true);
}
latch.countDown();
}
});

//Sports Request
client.newCall(sportArticlesRequest).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
error.set(true);
latch.countDown();
}
@Override
public void onResponse(Call call, Response response) {
try {
ResponseBody body = response.body();
if (response.code() != 200 || body == null) {
error.set(true);
} else {
sportArticles = gson.fromJson(body.string(), new TypeToken<List<Article>>() {
}.getType());
}
} catch (IOException | JsonSyntaxException e) {
error.set(true);
}
latch.countDown();
}
});

//Fitness Request
client.newCall(fitnessArticlesRequest).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
error.set(true);
latch.countDown();
}
@Override
public void onResponse(Call call, Response response) {
try {
ResponseBody body = response.body();
if (response.code() != 200 || body == null) {
error.set(true);
} else {
fitnessArticles = gson.fromJson(body.string(), new TypeToken<List<Article>>() {
}.getType());
}
} catch (IOException | JsonSyntaxException e) {
error.set(true);
}
latch.countDown();
}
});

//Government Request
client.newCall(governmentArticlesrequest).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
error.set(true);
latch.countDown();
}
@Override
public void onResponse(Call call, Response response) {
try {
ResponseBody body = response.body();
if (response.code() != 200 || body == null) {
error.set(true);
} else {
governmentArticles = gson.fromJson(body.string(), new TypeToken<List<Article>>() {
}.getType());
}
} catch (IOException | JsonSyntaxException e) {
error.set(true);
}
latch.countDown();
}
});

//Main Article Request
client.newCall(mainArticleRequest).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
error.set(true);
latch.countDown();
}
@Override
public void onResponse(Call call, Response response) {
try {
ResponseBody body = response.body();
if (response.code() != 200 || body == null) {
error.set(true);
} else {
mainArticle = gson.fromJson(body.string(), new TypeToken<Article>() {
}.getType());
}
} catch (IOException | JsonSyntaxException e) {
error.set(true);
}
latch.countDown();
}
});

//Configuration request
client.newCall(configurationRequest).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
error.set(true);
latch.countDown();
}
@Override
public void onResponse(Call call, Response response) {
try {
ResponseBody body = response.body();
if (response.code() != 200 || body == null) {
error.set(true);
} else {
config = gson.fromJson(body.string(), new TypeToken<Configuration>() {
}.getType());
}
} catch (IOException | JsonSyntaxException e) {
error.set(true);
}
latch.countDown();
}
});
try {
latch.await();
} catch (InterruptedException e) {
error.set(true);
}
notifyObservers(error.get() ? HTTPRequestStatus.HTTPERROR : HTTPRequestStatus.OK);
} catch (KeyManagementException | NoSuchAlgorithmException | KeyStoreException e) {
notifyObservers(HTTPRequestStatus.TLSERROR);
}
}
});
exService.shutdown();
}
}


This code calls several HTTP requests asynchronously and wait for all of them to conclude. If any request fails it notifies with an error, otherwise success. These requests returns JSON and I parse them using Gson.



For simplicity I have only showed two requests, however I want to refactor this code for any number of requests. There is a lot of similar code, but I can't figure a way to refactor it. I have already tried to create a custom Callback class however I can't figure a way to pass a Type and return the parsed value.



How would you approach this one?







java android gson






share|improve this question









New contributor




Exprove is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.











share|improve this question









New contributor




Exprove is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.









share|improve this question




share|improve this question








edited Dec 28 '18 at 0:08





















New contributor




Exprove is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.









asked Dec 27 '18 at 17:51









Exprove

1063




1063




New contributor




Exprove is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.





New contributor





Exprove is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.






Exprove is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.








  • 2




    Simplicity isn't usually the way to go here. I recommend you show the rest of your code. You might get different answers based on the less "simple" code than you would with what you currently have. Also please change the title to describe What your code Does as apposed to your goal. (Almost everyone wants to refactor their code.)
    – bruglesco
    Dec 27 '18 at 19:36










  • @bruglesco The difference between this code and the actual one is that there are like 10 Request Objects and 10 client.newCalls(request) , should I include them anyway?
    – Exprove
    Dec 27 '18 at 20:55












  • Yes. You should.
    – bruglesco
    Dec 27 '18 at 21:17














  • 2




    Simplicity isn't usually the way to go here. I recommend you show the rest of your code. You might get different answers based on the less "simple" code than you would with what you currently have. Also please change the title to describe What your code Does as apposed to your goal. (Almost everyone wants to refactor their code.)
    – bruglesco
    Dec 27 '18 at 19:36










  • @bruglesco The difference between this code and the actual one is that there are like 10 Request Objects and 10 client.newCalls(request) , should I include them anyway?
    – Exprove
    Dec 27 '18 at 20:55












  • Yes. You should.
    – bruglesco
    Dec 27 '18 at 21:17








2




2




Simplicity isn't usually the way to go here. I recommend you show the rest of your code. You might get different answers based on the less "simple" code than you would with what you currently have. Also please change the title to describe What your code Does as apposed to your goal. (Almost everyone wants to refactor their code.)
– bruglesco
Dec 27 '18 at 19:36




Simplicity isn't usually the way to go here. I recommend you show the rest of your code. You might get different answers based on the less "simple" code than you would with what you currently have. Also please change the title to describe What your code Does as apposed to your goal. (Almost everyone wants to refactor their code.)
– bruglesco
Dec 27 '18 at 19:36












@bruglesco The difference between this code and the actual one is that there are like 10 Request Objects and 10 client.newCalls(request) , should I include them anyway?
– Exprove
Dec 27 '18 at 20:55






@bruglesco The difference between this code and the actual one is that there are like 10 Request Objects and 10 client.newCalls(request) , should I include them anyway?
– Exprove
Dec 27 '18 at 20:55














Yes. You should.
– bruglesco
Dec 27 '18 at 21:17




Yes. You should.
– bruglesco
Dec 27 '18 at 21:17










2 Answers
2






active

oldest

votes


















2














Welcome to Code Review!



Thanks for sharing your code.



As you observed the main problem is code duplication.



We have basically two options to solve that problem. Both of them use the basic approach to separate the common code from the different code and "inject" the differing code into the common code.



prepare injecting differing code



When looking at your code the only difference between the //Article Request and the //Events Request are these lines:



//Article Request
articles = gson.fromJson(body.string(), new TypeToken<List<Article>>() {
}.getType());

//Events Request
events = gson.fromJson(body.string(), new TypeToken<List<Event>>() {
}.getType());


One problem here is that you change the objects pointed to by the member variables articles and events. We could make is easier to change if the objects would be reused:



//Article Request
articles.clear();
articles.addAll(gson.fromJson(body.string(), new TypeToken<List<Article>>() {
}.getType()));

//Events Request
events.clear();
events.addAll(gson.fromJson(body.string(), new TypeToken<List<Event>>() {
}.getType()));


now we can extract one of that parts to a new Method using the IDEs automated refactoring "extract method":



private void deserializeJson(
List<Event> events,
Gson gson,
ResponseBody body
){
events.clear();
events.addAll(gson.fromJson(body.string(), new TypeToken<List<Event>>() {
}.getType()));
}

//Events Request
// ...
} else {
deserializeJson((List<Event>)events,
Gson gson,
ResponseBody body);
}


Next we have to mat this new private method "generic":



private <L extends List> void deserializeJson(
L events,
Gson gson,
ResponseBody body
){
events.clear();
events.addAll(gson.fromJson(body.string(), new TypeToken<L>() {
}.getType()));
}


Now we can change toe other place too:



//Articles  Request
// ...
} else {
deserializeJson((List<Article>)articles);
}


from here we have two options:




  1. consolidating the common code in a single generic typed method.

  2. puttig the difering code into an "specialized classes" providing the differing behavior via an common interface.


common code in a single generic typed method



We basically do he same as above: we extract the //Events Request section or the //Articles Request into a new private method and make it "generic"



private <L extends List> void deserializeFromJson(
L theList,
OkHttpClient client,
Gson gson
){
client.newCall(articlesrequest).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
error.set(true);
latch.countDown();
}
@Override
public void onResponse(Call call, Response response) {
try {
ResponseBody body = response.body();
if (response.code() != 200 || body == null) {
error.set(true);
} else {
deserializeJson((L)theList, gson, body);
}
} catch (IOException | JsonSyntaxException e) {
error.set(true);
}
latch.countDown();
}
});
}


//Article Request
deserializeFromJson(
(List<Articles>)articles,
client,
gson
);

//Events Request
client.newCall(eventsrequest).enqueue(new Callback() {
// ...


and then also replace the other part:



//Article Request
deserializeFromJson(
(List<Articles>)articles,
client,
gson
);

//Events Request
deserializeFromJson(
(List<Event>)events,
client,
gson
);


creating specialized classes with interface



for this we have to move the private method created in the first section into a new inner class



class your current class:

private static class JsonDeserializer<T> {

void deserialize(
List<T> theList,
Gson gson,
ResponseBody body
){
theList.clear();
theList.addAll(gson.fromJson(body.string(), new TypeToken<List<T>>() {
}.getType()));

}
}
// ...

//Articles Request
// ...
} else {
new JsonDeserializer<Article>().deserialize(articles, gson, body);
}
//...


Since articles and events are member variables I'd rather have them as constructor parameters in the new class:



class your current class:

private static class JsonDeserializer<T> {
private final List<T> theList;
JsonDeserializer( List<T> theList){
this.theList = theList;
}

void deserialize(
Gson gson,
ResponseBody body
){
theList.clear();
theList.addAll(gson.fromJson(body.string(), new TypeToken<List<T>>() {
}.getType()));

}
}
// ...

//Articles Request
// ...
} else {
new JsonDeserializer<Article>(articles).deserialize(gson, body);
}
//...
//Event Request
// ...
} else {
new JsonDeserializer<Event>(eventss).deserialize(gson, body);
}
//...


Now we can create the JsonDeserializer instances at the top of the method:



private void loadRepository() {
JsonDeserializer<Article> articleDeserializer = new JsonDeserializer<>(articles);
JsonDeserializer<Event> eventDeserializer = new JsonDeserializer<>(event);
ExecutorService exService = Executors.newSingleThreadExecutor();
// ...
//Articles Request
// ...
} else {
articleDeserializer.deserialize(gson, body);
}
//...
//Event Request
// ...
} else {
eventDeserializer.deserialize(gson, body);
}
//...


Now the only difference left in //Articles Request section and //Event Request section is the name of the variable. So we can put the two JsonDeserializer instances into a collection and apply the *common code * as a loop:



private void loadRepository() {
JsonDeserializer<?> deserializers = Arrays.asList(
new JsonDeserializer<Article>(articles),
new JsonDeserializer<Event>(event)
);
ExecutorService exService = Executors.newSingleThreadExecutor();
// ...

Request eventsrequest = requestbuilder.url("https://apiurlevents").build();

//Article Request changed to loop
for(JsonDeserializer<?> deserializer: deserializers){
client.newCall(articlesrequest).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
error.set(true);
latch.countDown();
}
@Override
public void onResponse(Call call, Response response) {
try {
ResponseBody body = response.body();
if (response.code() != 200 || body == null) {
error.set(true);
} else {
deserializer.deserialize(gson, body);
}
} catch (IOException | JsonSyntaxException e) {
error.set(true);
}
latch.countDown();
}
});
}
try {
latch.await();
} catch (InterruptedException e) {
error.set(true);
}


New document types are just new instances in the collection.






share|improve this answer





















  • Really appreciate your help. That refactor would work, however not all my class member variables are from type List. Added more requests examples. If I replace List<T> to <T> would that work? Ofc I would declare JsonDeserializer as <List<Article>> for example.
    – Exprove
    Dec 28 '18 at 0:12












  • @Exprove however not all my class member variables are from type List I'd create another JsonDeserializer that deserializes the generic type directly. Problem might be to get the de-serialized object out of the JsonDeserializer instance. replacing the List parameter of the constructor with a Listener might work.
    – Timothy Truckle
    Dec 28 '18 at 0:20





















1














You could use a CompletableFuture for this. Make each call return a completed future with the body, or an exceptionally completed future with an error:



private CompletableFuture<String> call(String url) {
CompletableFuture<String> future = new CompletableFuture<>();

OkHttpClient client = new OkHttpClient.Builder().connectTimeout(15, TimeUnit.SECONDS).build();
client.newCall(requestbuilder.url(url).build()).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
future.completeExceptionally(e);
}

@Override
public void onResponse(Call call, Response response) {
try {
ResponseBody body = response.body();
if (response.code() != 200 || body == null) {
throw new IOException("Http error");
} else {
future.complete(body.string());
}
} catch (IOException | JsonSyntaxException e) {
future.completeExceptionally(e);
}
}
});

return future;
}


Then add a generic method to make the calls and deserialize the results:



private <T> Future<T> callAndDeserialize(String url, Gson gson, TypeToken<T> typeToken) {
CompletableFuture<String> future = call(url);
return future.thenApply(new Function<String, T>() {
public T apply(String body) {
return gson.fromJson(body, typeToken.getType()));
}
});
}


The loadRepository code would then be something like:



final Gson gson = new GsonBuilder().registerTypeHierarchyAdapter(Calendar.class, new CalendarGson()).create();

Future<List<Article>> articlesFuture = callAndDeserialize("https://apiurlarticles", gson, new TypeToken<List<Article>>() {});
Future<List<Event>> eventsFuture = callAndDeserialize("https://apiurlarticles", gson, new TypeToken<List<Event>>() {});
Future<List<Article>> sportsFuture = callAndDeserialize("https://apiurlarticles", gson, new TypeToken<List<Article>>() {});
Future<List<Article>> fitnessFuture = callAndDeserialize("https://apiurlarticles", gson, new TypeToken<List<Article>>() {});
Future<List<Article>> governmentFuture = callAndDeserialize("https://apiurlarticles", gson, new TypeToken<List<Article>>() {});
Future<Article> mainArticleFuture = callAndDeserialize("https://apiurlarticles", gson, new TypeToken<Article>() {});
Future<Configuration> configurationFuture = callAndDeserialize("https://apiurlarticles", gson, new TypeToken<Configuration>() {});

try {
articles = articlesFuture.get();
events = eventsFuture.get();
sportArticles = sportsFuture.get();
fitnessArticles = fitnessFuture.get();
governmentArticles = governmentFuture.get();
mainArticle = mainArticleFuture.get();
config = configurationFuture.get();

notifyObservers(HTTPRequestStatus.OK);
} catch (ExecutionException e) {
if(e.getCause() instanceof KeyManagementException || e.getCause() instanceof NoSuchAlgorithmException || e.getCause() instanceof KeyStoreException) {
notifyObservers(HTTPRequestStatus.TLSERROR);
} else {
notifyObservers(HTTPRequestStatus.HTTPERROR);
}
}


The ExecutionException at the end now retains the exception messages, causes and stacktraces. In case there's any unexpected errors you need to debug, you can also log this exception as well notifying the observers.






share|improve this answer





















    Your Answer





    StackExchange.ifUsing("editor", function () {
    return StackExchange.using("mathjaxEditing", function () {
    StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix) {
    StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
    });
    });
    }, "mathjax-editing");

    StackExchange.ifUsing("editor", function () {
    StackExchange.using("externalEditor", function () {
    StackExchange.using("snippets", function () {
    StackExchange.snippets.init();
    });
    });
    }, "code-snippets");

    StackExchange.ready(function() {
    var channelOptions = {
    tags: "".split(" "),
    id: "196"
    };
    initTagRenderer("".split(" "), "".split(" "), channelOptions);

    StackExchange.using("externalEditor", function() {
    // Have to fire editor after snippets, if snippets enabled
    if (StackExchange.settings.snippets.snippetsEnabled) {
    StackExchange.using("snippets", function() {
    createEditor();
    });
    }
    else {
    createEditor();
    }
    });

    function createEditor() {
    StackExchange.prepareEditor({
    heartbeatType: 'answer',
    autoActivateHeartbeat: false,
    convertImagesToLinks: false,
    noModals: true,
    showLowRepImageUploadWarning: true,
    reputationToPostImages: null,
    bindNavPrevention: true,
    postfix: "",
    imageUploader: {
    brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
    contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
    allowUrls: true
    },
    onDemand: true,
    discardSelector: ".discard-answer"
    ,immediatelyShowMarkdownHelp:true
    });


    }
    });






    Exprove is a new contributor. Be nice, and check out our Code of Conduct.










    draft saved

    draft discarded


















    StackExchange.ready(
    function () {
    StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f210437%2fcall-multiple-asynchronous-http-requests-and-wait-for-the-result%23new-answer', 'question_page');
    }
    );

    Post as a guest















    Required, but never shown

























    2 Answers
    2






    active

    oldest

    votes








    2 Answers
    2






    active

    oldest

    votes









    active

    oldest

    votes






    active

    oldest

    votes









    2














    Welcome to Code Review!



    Thanks for sharing your code.



    As you observed the main problem is code duplication.



    We have basically two options to solve that problem. Both of them use the basic approach to separate the common code from the different code and "inject" the differing code into the common code.



    prepare injecting differing code



    When looking at your code the only difference between the //Article Request and the //Events Request are these lines:



    //Article Request
    articles = gson.fromJson(body.string(), new TypeToken<List<Article>>() {
    }.getType());

    //Events Request
    events = gson.fromJson(body.string(), new TypeToken<List<Event>>() {
    }.getType());


    One problem here is that you change the objects pointed to by the member variables articles and events. We could make is easier to change if the objects would be reused:



    //Article Request
    articles.clear();
    articles.addAll(gson.fromJson(body.string(), new TypeToken<List<Article>>() {
    }.getType()));

    //Events Request
    events.clear();
    events.addAll(gson.fromJson(body.string(), new TypeToken<List<Event>>() {
    }.getType()));


    now we can extract one of that parts to a new Method using the IDEs automated refactoring "extract method":



    private void deserializeJson(
    List<Event> events,
    Gson gson,
    ResponseBody body
    ){
    events.clear();
    events.addAll(gson.fromJson(body.string(), new TypeToken<List<Event>>() {
    }.getType()));
    }

    //Events Request
    // ...
    } else {
    deserializeJson((List<Event>)events,
    Gson gson,
    ResponseBody body);
    }


    Next we have to mat this new private method "generic":



    private <L extends List> void deserializeJson(
    L events,
    Gson gson,
    ResponseBody body
    ){
    events.clear();
    events.addAll(gson.fromJson(body.string(), new TypeToken<L>() {
    }.getType()));
    }


    Now we can change toe other place too:



    //Articles  Request
    // ...
    } else {
    deserializeJson((List<Article>)articles);
    }


    from here we have two options:




    1. consolidating the common code in a single generic typed method.

    2. puttig the difering code into an "specialized classes" providing the differing behavior via an common interface.


    common code in a single generic typed method



    We basically do he same as above: we extract the //Events Request section or the //Articles Request into a new private method and make it "generic"



    private <L extends List> void deserializeFromJson(
    L theList,
    OkHttpClient client,
    Gson gson
    ){
    client.newCall(articlesrequest).enqueue(new Callback() {
    @Override
    public void onFailure(Call call, IOException e) {
    error.set(true);
    latch.countDown();
    }
    @Override
    public void onResponse(Call call, Response response) {
    try {
    ResponseBody body = response.body();
    if (response.code() != 200 || body == null) {
    error.set(true);
    } else {
    deserializeJson((L)theList, gson, body);
    }
    } catch (IOException | JsonSyntaxException e) {
    error.set(true);
    }
    latch.countDown();
    }
    });
    }


    //Article Request
    deserializeFromJson(
    (List<Articles>)articles,
    client,
    gson
    );

    //Events Request
    client.newCall(eventsrequest).enqueue(new Callback() {
    // ...


    and then also replace the other part:



    //Article Request
    deserializeFromJson(
    (List<Articles>)articles,
    client,
    gson
    );

    //Events Request
    deserializeFromJson(
    (List<Event>)events,
    client,
    gson
    );


    creating specialized classes with interface



    for this we have to move the private method created in the first section into a new inner class



    class your current class:

    private static class JsonDeserializer<T> {

    void deserialize(
    List<T> theList,
    Gson gson,
    ResponseBody body
    ){
    theList.clear();
    theList.addAll(gson.fromJson(body.string(), new TypeToken<List<T>>() {
    }.getType()));

    }
    }
    // ...

    //Articles Request
    // ...
    } else {
    new JsonDeserializer<Article>().deserialize(articles, gson, body);
    }
    //...


    Since articles and events are member variables I'd rather have them as constructor parameters in the new class:



    class your current class:

    private static class JsonDeserializer<T> {
    private final List<T> theList;
    JsonDeserializer( List<T> theList){
    this.theList = theList;
    }

    void deserialize(
    Gson gson,
    ResponseBody body
    ){
    theList.clear();
    theList.addAll(gson.fromJson(body.string(), new TypeToken<List<T>>() {
    }.getType()));

    }
    }
    // ...

    //Articles Request
    // ...
    } else {
    new JsonDeserializer<Article>(articles).deserialize(gson, body);
    }
    //...
    //Event Request
    // ...
    } else {
    new JsonDeserializer<Event>(eventss).deserialize(gson, body);
    }
    //...


    Now we can create the JsonDeserializer instances at the top of the method:



    private void loadRepository() {
    JsonDeserializer<Article> articleDeserializer = new JsonDeserializer<>(articles);
    JsonDeserializer<Event> eventDeserializer = new JsonDeserializer<>(event);
    ExecutorService exService = Executors.newSingleThreadExecutor();
    // ...
    //Articles Request
    // ...
    } else {
    articleDeserializer.deserialize(gson, body);
    }
    //...
    //Event Request
    // ...
    } else {
    eventDeserializer.deserialize(gson, body);
    }
    //...


    Now the only difference left in //Articles Request section and //Event Request section is the name of the variable. So we can put the two JsonDeserializer instances into a collection and apply the *common code * as a loop:



    private void loadRepository() {
    JsonDeserializer<?> deserializers = Arrays.asList(
    new JsonDeserializer<Article>(articles),
    new JsonDeserializer<Event>(event)
    );
    ExecutorService exService = Executors.newSingleThreadExecutor();
    // ...

    Request eventsrequest = requestbuilder.url("https://apiurlevents").build();

    //Article Request changed to loop
    for(JsonDeserializer<?> deserializer: deserializers){
    client.newCall(articlesrequest).enqueue(new Callback() {
    @Override
    public void onFailure(Call call, IOException e) {
    error.set(true);
    latch.countDown();
    }
    @Override
    public void onResponse(Call call, Response response) {
    try {
    ResponseBody body = response.body();
    if (response.code() != 200 || body == null) {
    error.set(true);
    } else {
    deserializer.deserialize(gson, body);
    }
    } catch (IOException | JsonSyntaxException e) {
    error.set(true);
    }
    latch.countDown();
    }
    });
    }
    try {
    latch.await();
    } catch (InterruptedException e) {
    error.set(true);
    }


    New document types are just new instances in the collection.






    share|improve this answer





















    • Really appreciate your help. That refactor would work, however not all my class member variables are from type List. Added more requests examples. If I replace List<T> to <T> would that work? Ofc I would declare JsonDeserializer as <List<Article>> for example.
      – Exprove
      Dec 28 '18 at 0:12












    • @Exprove however not all my class member variables are from type List I'd create another JsonDeserializer that deserializes the generic type directly. Problem might be to get the de-serialized object out of the JsonDeserializer instance. replacing the List parameter of the constructor with a Listener might work.
      – Timothy Truckle
      Dec 28 '18 at 0:20


















    2














    Welcome to Code Review!



    Thanks for sharing your code.



    As you observed the main problem is code duplication.



    We have basically two options to solve that problem. Both of them use the basic approach to separate the common code from the different code and "inject" the differing code into the common code.



    prepare injecting differing code



    When looking at your code the only difference between the //Article Request and the //Events Request are these lines:



    //Article Request
    articles = gson.fromJson(body.string(), new TypeToken<List<Article>>() {
    }.getType());

    //Events Request
    events = gson.fromJson(body.string(), new TypeToken<List<Event>>() {
    }.getType());


    One problem here is that you change the objects pointed to by the member variables articles and events. We could make is easier to change if the objects would be reused:



    //Article Request
    articles.clear();
    articles.addAll(gson.fromJson(body.string(), new TypeToken<List<Article>>() {
    }.getType()));

    //Events Request
    events.clear();
    events.addAll(gson.fromJson(body.string(), new TypeToken<List<Event>>() {
    }.getType()));


    now we can extract one of that parts to a new Method using the IDEs automated refactoring "extract method":



    private void deserializeJson(
    List<Event> events,
    Gson gson,
    ResponseBody body
    ){
    events.clear();
    events.addAll(gson.fromJson(body.string(), new TypeToken<List<Event>>() {
    }.getType()));
    }

    //Events Request
    // ...
    } else {
    deserializeJson((List<Event>)events,
    Gson gson,
    ResponseBody body);
    }


    Next we have to mat this new private method "generic":



    private <L extends List> void deserializeJson(
    L events,
    Gson gson,
    ResponseBody body
    ){
    events.clear();
    events.addAll(gson.fromJson(body.string(), new TypeToken<L>() {
    }.getType()));
    }


    Now we can change toe other place too:



    //Articles  Request
    // ...
    } else {
    deserializeJson((List<Article>)articles);
    }


    from here we have two options:




    1. consolidating the common code in a single generic typed method.

    2. puttig the difering code into an "specialized classes" providing the differing behavior via an common interface.


    common code in a single generic typed method



    We basically do he same as above: we extract the //Events Request section or the //Articles Request into a new private method and make it "generic"



    private <L extends List> void deserializeFromJson(
    L theList,
    OkHttpClient client,
    Gson gson
    ){
    client.newCall(articlesrequest).enqueue(new Callback() {
    @Override
    public void onFailure(Call call, IOException e) {
    error.set(true);
    latch.countDown();
    }
    @Override
    public void onResponse(Call call, Response response) {
    try {
    ResponseBody body = response.body();
    if (response.code() != 200 || body == null) {
    error.set(true);
    } else {
    deserializeJson((L)theList, gson, body);
    }
    } catch (IOException | JsonSyntaxException e) {
    error.set(true);
    }
    latch.countDown();
    }
    });
    }


    //Article Request
    deserializeFromJson(
    (List<Articles>)articles,
    client,
    gson
    );

    //Events Request
    client.newCall(eventsrequest).enqueue(new Callback() {
    // ...


    and then also replace the other part:



    //Article Request
    deserializeFromJson(
    (List<Articles>)articles,
    client,
    gson
    );

    //Events Request
    deserializeFromJson(
    (List<Event>)events,
    client,
    gson
    );


    creating specialized classes with interface



    for this we have to move the private method created in the first section into a new inner class



    class your current class:

    private static class JsonDeserializer<T> {

    void deserialize(
    List<T> theList,
    Gson gson,
    ResponseBody body
    ){
    theList.clear();
    theList.addAll(gson.fromJson(body.string(), new TypeToken<List<T>>() {
    }.getType()));

    }
    }
    // ...

    //Articles Request
    // ...
    } else {
    new JsonDeserializer<Article>().deserialize(articles, gson, body);
    }
    //...


    Since articles and events are member variables I'd rather have them as constructor parameters in the new class:



    class your current class:

    private static class JsonDeserializer<T> {
    private final List<T> theList;
    JsonDeserializer( List<T> theList){
    this.theList = theList;
    }

    void deserialize(
    Gson gson,
    ResponseBody body
    ){
    theList.clear();
    theList.addAll(gson.fromJson(body.string(), new TypeToken<List<T>>() {
    }.getType()));

    }
    }
    // ...

    //Articles Request
    // ...
    } else {
    new JsonDeserializer<Article>(articles).deserialize(gson, body);
    }
    //...
    //Event Request
    // ...
    } else {
    new JsonDeserializer<Event>(eventss).deserialize(gson, body);
    }
    //...


    Now we can create the JsonDeserializer instances at the top of the method:



    private void loadRepository() {
    JsonDeserializer<Article> articleDeserializer = new JsonDeserializer<>(articles);
    JsonDeserializer<Event> eventDeserializer = new JsonDeserializer<>(event);
    ExecutorService exService = Executors.newSingleThreadExecutor();
    // ...
    //Articles Request
    // ...
    } else {
    articleDeserializer.deserialize(gson, body);
    }
    //...
    //Event Request
    // ...
    } else {
    eventDeserializer.deserialize(gson, body);
    }
    //...


    Now the only difference left in //Articles Request section and //Event Request section is the name of the variable. So we can put the two JsonDeserializer instances into a collection and apply the *common code * as a loop:



    private void loadRepository() {
    JsonDeserializer<?> deserializers = Arrays.asList(
    new JsonDeserializer<Article>(articles),
    new JsonDeserializer<Event>(event)
    );
    ExecutorService exService = Executors.newSingleThreadExecutor();
    // ...

    Request eventsrequest = requestbuilder.url("https://apiurlevents").build();

    //Article Request changed to loop
    for(JsonDeserializer<?> deserializer: deserializers){
    client.newCall(articlesrequest).enqueue(new Callback() {
    @Override
    public void onFailure(Call call, IOException e) {
    error.set(true);
    latch.countDown();
    }
    @Override
    public void onResponse(Call call, Response response) {
    try {
    ResponseBody body = response.body();
    if (response.code() != 200 || body == null) {
    error.set(true);
    } else {
    deserializer.deserialize(gson, body);
    }
    } catch (IOException | JsonSyntaxException e) {
    error.set(true);
    }
    latch.countDown();
    }
    });
    }
    try {
    latch.await();
    } catch (InterruptedException e) {
    error.set(true);
    }


    New document types are just new instances in the collection.






    share|improve this answer





















    • Really appreciate your help. That refactor would work, however not all my class member variables are from type List. Added more requests examples. If I replace List<T> to <T> would that work? Ofc I would declare JsonDeserializer as <List<Article>> for example.
      – Exprove
      Dec 28 '18 at 0:12












    • @Exprove however not all my class member variables are from type List I'd create another JsonDeserializer that deserializes the generic type directly. Problem might be to get the de-serialized object out of the JsonDeserializer instance. replacing the List parameter of the constructor with a Listener might work.
      – Timothy Truckle
      Dec 28 '18 at 0:20
















    2












    2








    2






    Welcome to Code Review!



    Thanks for sharing your code.



    As you observed the main problem is code duplication.



    We have basically two options to solve that problem. Both of them use the basic approach to separate the common code from the different code and "inject" the differing code into the common code.



    prepare injecting differing code



    When looking at your code the only difference between the //Article Request and the //Events Request are these lines:



    //Article Request
    articles = gson.fromJson(body.string(), new TypeToken<List<Article>>() {
    }.getType());

    //Events Request
    events = gson.fromJson(body.string(), new TypeToken<List<Event>>() {
    }.getType());


    One problem here is that you change the objects pointed to by the member variables articles and events. We could make is easier to change if the objects would be reused:



    //Article Request
    articles.clear();
    articles.addAll(gson.fromJson(body.string(), new TypeToken<List<Article>>() {
    }.getType()));

    //Events Request
    events.clear();
    events.addAll(gson.fromJson(body.string(), new TypeToken<List<Event>>() {
    }.getType()));


    now we can extract one of that parts to a new Method using the IDEs automated refactoring "extract method":



    private void deserializeJson(
    List<Event> events,
    Gson gson,
    ResponseBody body
    ){
    events.clear();
    events.addAll(gson.fromJson(body.string(), new TypeToken<List<Event>>() {
    }.getType()));
    }

    //Events Request
    // ...
    } else {
    deserializeJson((List<Event>)events,
    Gson gson,
    ResponseBody body);
    }


    Next we have to mat this new private method "generic":



    private <L extends List> void deserializeJson(
    L events,
    Gson gson,
    ResponseBody body
    ){
    events.clear();
    events.addAll(gson.fromJson(body.string(), new TypeToken<L>() {
    }.getType()));
    }


    Now we can change toe other place too:



    //Articles  Request
    // ...
    } else {
    deserializeJson((List<Article>)articles);
    }


    from here we have two options:




    1. consolidating the common code in a single generic typed method.

    2. puttig the difering code into an "specialized classes" providing the differing behavior via an common interface.


    common code in a single generic typed method



    We basically do he same as above: we extract the //Events Request section or the //Articles Request into a new private method and make it "generic"



    private <L extends List> void deserializeFromJson(
    L theList,
    OkHttpClient client,
    Gson gson
    ){
    client.newCall(articlesrequest).enqueue(new Callback() {
    @Override
    public void onFailure(Call call, IOException e) {
    error.set(true);
    latch.countDown();
    }
    @Override
    public void onResponse(Call call, Response response) {
    try {
    ResponseBody body = response.body();
    if (response.code() != 200 || body == null) {
    error.set(true);
    } else {
    deserializeJson((L)theList, gson, body);
    }
    } catch (IOException | JsonSyntaxException e) {
    error.set(true);
    }
    latch.countDown();
    }
    });
    }


    //Article Request
    deserializeFromJson(
    (List<Articles>)articles,
    client,
    gson
    );

    //Events Request
    client.newCall(eventsrequest).enqueue(new Callback() {
    // ...


    and then also replace the other part:



    //Article Request
    deserializeFromJson(
    (List<Articles>)articles,
    client,
    gson
    );

    //Events Request
    deserializeFromJson(
    (List<Event>)events,
    client,
    gson
    );


    creating specialized classes with interface



    for this we have to move the private method created in the first section into a new inner class



    class your current class:

    private static class JsonDeserializer<T> {

    void deserialize(
    List<T> theList,
    Gson gson,
    ResponseBody body
    ){
    theList.clear();
    theList.addAll(gson.fromJson(body.string(), new TypeToken<List<T>>() {
    }.getType()));

    }
    }
    // ...

    //Articles Request
    // ...
    } else {
    new JsonDeserializer<Article>().deserialize(articles, gson, body);
    }
    //...


    Since articles and events are member variables I'd rather have them as constructor parameters in the new class:



    class your current class:

    private static class JsonDeserializer<T> {
    private final List<T> theList;
    JsonDeserializer( List<T> theList){
    this.theList = theList;
    }

    void deserialize(
    Gson gson,
    ResponseBody body
    ){
    theList.clear();
    theList.addAll(gson.fromJson(body.string(), new TypeToken<List<T>>() {
    }.getType()));

    }
    }
    // ...

    //Articles Request
    // ...
    } else {
    new JsonDeserializer<Article>(articles).deserialize(gson, body);
    }
    //...
    //Event Request
    // ...
    } else {
    new JsonDeserializer<Event>(eventss).deserialize(gson, body);
    }
    //...


    Now we can create the JsonDeserializer instances at the top of the method:



    private void loadRepository() {
    JsonDeserializer<Article> articleDeserializer = new JsonDeserializer<>(articles);
    JsonDeserializer<Event> eventDeserializer = new JsonDeserializer<>(event);
    ExecutorService exService = Executors.newSingleThreadExecutor();
    // ...
    //Articles Request
    // ...
    } else {
    articleDeserializer.deserialize(gson, body);
    }
    //...
    //Event Request
    // ...
    } else {
    eventDeserializer.deserialize(gson, body);
    }
    //...


    Now the only difference left in //Articles Request section and //Event Request section is the name of the variable. So we can put the two JsonDeserializer instances into a collection and apply the *common code * as a loop:



    private void loadRepository() {
    JsonDeserializer<?> deserializers = Arrays.asList(
    new JsonDeserializer<Article>(articles),
    new JsonDeserializer<Event>(event)
    );
    ExecutorService exService = Executors.newSingleThreadExecutor();
    // ...

    Request eventsrequest = requestbuilder.url("https://apiurlevents").build();

    //Article Request changed to loop
    for(JsonDeserializer<?> deserializer: deserializers){
    client.newCall(articlesrequest).enqueue(new Callback() {
    @Override
    public void onFailure(Call call, IOException e) {
    error.set(true);
    latch.countDown();
    }
    @Override
    public void onResponse(Call call, Response response) {
    try {
    ResponseBody body = response.body();
    if (response.code() != 200 || body == null) {
    error.set(true);
    } else {
    deserializer.deserialize(gson, body);
    }
    } catch (IOException | JsonSyntaxException e) {
    error.set(true);
    }
    latch.countDown();
    }
    });
    }
    try {
    latch.await();
    } catch (InterruptedException e) {
    error.set(true);
    }


    New document types are just new instances in the collection.






    share|improve this answer












    Welcome to Code Review!



    Thanks for sharing your code.



    As you observed the main problem is code duplication.



    We have basically two options to solve that problem. Both of them use the basic approach to separate the common code from the different code and "inject" the differing code into the common code.



    prepare injecting differing code



    When looking at your code the only difference between the //Article Request and the //Events Request are these lines:



    //Article Request
    articles = gson.fromJson(body.string(), new TypeToken<List<Article>>() {
    }.getType());

    //Events Request
    events = gson.fromJson(body.string(), new TypeToken<List<Event>>() {
    }.getType());


    One problem here is that you change the objects pointed to by the member variables articles and events. We could make is easier to change if the objects would be reused:



    //Article Request
    articles.clear();
    articles.addAll(gson.fromJson(body.string(), new TypeToken<List<Article>>() {
    }.getType()));

    //Events Request
    events.clear();
    events.addAll(gson.fromJson(body.string(), new TypeToken<List<Event>>() {
    }.getType()));


    now we can extract one of that parts to a new Method using the IDEs automated refactoring "extract method":



    private void deserializeJson(
    List<Event> events,
    Gson gson,
    ResponseBody body
    ){
    events.clear();
    events.addAll(gson.fromJson(body.string(), new TypeToken<List<Event>>() {
    }.getType()));
    }

    //Events Request
    // ...
    } else {
    deserializeJson((List<Event>)events,
    Gson gson,
    ResponseBody body);
    }


    Next we have to mat this new private method "generic":



    private <L extends List> void deserializeJson(
    L events,
    Gson gson,
    ResponseBody body
    ){
    events.clear();
    events.addAll(gson.fromJson(body.string(), new TypeToken<L>() {
    }.getType()));
    }


    Now we can change toe other place too:



    //Articles  Request
    // ...
    } else {
    deserializeJson((List<Article>)articles);
    }


    from here we have two options:




    1. consolidating the common code in a single generic typed method.

    2. puttig the difering code into an "specialized classes" providing the differing behavior via an common interface.


    common code in a single generic typed method



    We basically do he same as above: we extract the //Events Request section or the //Articles Request into a new private method and make it "generic"



    private <L extends List> void deserializeFromJson(
    L theList,
    OkHttpClient client,
    Gson gson
    ){
    client.newCall(articlesrequest).enqueue(new Callback() {
    @Override
    public void onFailure(Call call, IOException e) {
    error.set(true);
    latch.countDown();
    }
    @Override
    public void onResponse(Call call, Response response) {
    try {
    ResponseBody body = response.body();
    if (response.code() != 200 || body == null) {
    error.set(true);
    } else {
    deserializeJson((L)theList, gson, body);
    }
    } catch (IOException | JsonSyntaxException e) {
    error.set(true);
    }
    latch.countDown();
    }
    });
    }


    //Article Request
    deserializeFromJson(
    (List<Articles>)articles,
    client,
    gson
    );

    //Events Request
    client.newCall(eventsrequest).enqueue(new Callback() {
    // ...


    and then also replace the other part:



    //Article Request
    deserializeFromJson(
    (List<Articles>)articles,
    client,
    gson
    );

    //Events Request
    deserializeFromJson(
    (List<Event>)events,
    client,
    gson
    );


    creating specialized classes with interface



    for this we have to move the private method created in the first section into a new inner class



    class your current class:

    private static class JsonDeserializer<T> {

    void deserialize(
    List<T> theList,
    Gson gson,
    ResponseBody body
    ){
    theList.clear();
    theList.addAll(gson.fromJson(body.string(), new TypeToken<List<T>>() {
    }.getType()));

    }
    }
    // ...

    //Articles Request
    // ...
    } else {
    new JsonDeserializer<Article>().deserialize(articles, gson, body);
    }
    //...


    Since articles and events are member variables I'd rather have them as constructor parameters in the new class:



    class your current class:

    private static class JsonDeserializer<T> {
    private final List<T> theList;
    JsonDeserializer( List<T> theList){
    this.theList = theList;
    }

    void deserialize(
    Gson gson,
    ResponseBody body
    ){
    theList.clear();
    theList.addAll(gson.fromJson(body.string(), new TypeToken<List<T>>() {
    }.getType()));

    }
    }
    // ...

    //Articles Request
    // ...
    } else {
    new JsonDeserializer<Article>(articles).deserialize(gson, body);
    }
    //...
    //Event Request
    // ...
    } else {
    new JsonDeserializer<Event>(eventss).deserialize(gson, body);
    }
    //...


    Now we can create the JsonDeserializer instances at the top of the method:



    private void loadRepository() {
    JsonDeserializer<Article> articleDeserializer = new JsonDeserializer<>(articles);
    JsonDeserializer<Event> eventDeserializer = new JsonDeserializer<>(event);
    ExecutorService exService = Executors.newSingleThreadExecutor();
    // ...
    //Articles Request
    // ...
    } else {
    articleDeserializer.deserialize(gson, body);
    }
    //...
    //Event Request
    // ...
    } else {
    eventDeserializer.deserialize(gson, body);
    }
    //...


    Now the only difference left in //Articles Request section and //Event Request section is the name of the variable. So we can put the two JsonDeserializer instances into a collection and apply the *common code * as a loop:



    private void loadRepository() {
    JsonDeserializer<?> deserializers = Arrays.asList(
    new JsonDeserializer<Article>(articles),
    new JsonDeserializer<Event>(event)
    );
    ExecutorService exService = Executors.newSingleThreadExecutor();
    // ...

    Request eventsrequest = requestbuilder.url("https://apiurlevents").build();

    //Article Request changed to loop
    for(JsonDeserializer<?> deserializer: deserializers){
    client.newCall(articlesrequest).enqueue(new Callback() {
    @Override
    public void onFailure(Call call, IOException e) {
    error.set(true);
    latch.countDown();
    }
    @Override
    public void onResponse(Call call, Response response) {
    try {
    ResponseBody body = response.body();
    if (response.code() != 200 || body == null) {
    error.set(true);
    } else {
    deserializer.deserialize(gson, body);
    }
    } catch (IOException | JsonSyntaxException e) {
    error.set(true);
    }
    latch.countDown();
    }
    });
    }
    try {
    latch.await();
    } catch (InterruptedException e) {
    error.set(true);
    }


    New document types are just new instances in the collection.







    share|improve this answer












    share|improve this answer



    share|improve this answer










    answered Dec 27 '18 at 22:02









    Timothy Truckle

    4,838416




    4,838416












    • Really appreciate your help. That refactor would work, however not all my class member variables are from type List. Added more requests examples. If I replace List<T> to <T> would that work? Ofc I would declare JsonDeserializer as <List<Article>> for example.
      – Exprove
      Dec 28 '18 at 0:12












    • @Exprove however not all my class member variables are from type List I'd create another JsonDeserializer that deserializes the generic type directly. Problem might be to get the de-serialized object out of the JsonDeserializer instance. replacing the List parameter of the constructor with a Listener might work.
      – Timothy Truckle
      Dec 28 '18 at 0:20




















    • Really appreciate your help. That refactor would work, however not all my class member variables are from type List. Added more requests examples. If I replace List<T> to <T> would that work? Ofc I would declare JsonDeserializer as <List<Article>> for example.
      – Exprove
      Dec 28 '18 at 0:12












    • @Exprove however not all my class member variables are from type List I'd create another JsonDeserializer that deserializes the generic type directly. Problem might be to get the de-serialized object out of the JsonDeserializer instance. replacing the List parameter of the constructor with a Listener might work.
      – Timothy Truckle
      Dec 28 '18 at 0:20


















    Really appreciate your help. That refactor would work, however not all my class member variables are from type List. Added more requests examples. If I replace List<T> to <T> would that work? Ofc I would declare JsonDeserializer as <List<Article>> for example.
    – Exprove
    Dec 28 '18 at 0:12






    Really appreciate your help. That refactor would work, however not all my class member variables are from type List. Added more requests examples. If I replace List<T> to <T> would that work? Ofc I would declare JsonDeserializer as <List<Article>> for example.
    – Exprove
    Dec 28 '18 at 0:12














    @Exprove however not all my class member variables are from type List I'd create another JsonDeserializer that deserializes the generic type directly. Problem might be to get the de-serialized object out of the JsonDeserializer instance. replacing the List parameter of the constructor with a Listener might work.
    – Timothy Truckle
    Dec 28 '18 at 0:20






    @Exprove however not all my class member variables are from type List I'd create another JsonDeserializer that deserializes the generic type directly. Problem might be to get the de-serialized object out of the JsonDeserializer instance. replacing the List parameter of the constructor with a Listener might work.
    – Timothy Truckle
    Dec 28 '18 at 0:20















    1














    You could use a CompletableFuture for this. Make each call return a completed future with the body, or an exceptionally completed future with an error:



    private CompletableFuture<String> call(String url) {
    CompletableFuture<String> future = new CompletableFuture<>();

    OkHttpClient client = new OkHttpClient.Builder().connectTimeout(15, TimeUnit.SECONDS).build();
    client.newCall(requestbuilder.url(url).build()).enqueue(new Callback() {
    @Override
    public void onFailure(Call call, IOException e) {
    future.completeExceptionally(e);
    }

    @Override
    public void onResponse(Call call, Response response) {
    try {
    ResponseBody body = response.body();
    if (response.code() != 200 || body == null) {
    throw new IOException("Http error");
    } else {
    future.complete(body.string());
    }
    } catch (IOException | JsonSyntaxException e) {
    future.completeExceptionally(e);
    }
    }
    });

    return future;
    }


    Then add a generic method to make the calls and deserialize the results:



    private <T> Future<T> callAndDeserialize(String url, Gson gson, TypeToken<T> typeToken) {
    CompletableFuture<String> future = call(url);
    return future.thenApply(new Function<String, T>() {
    public T apply(String body) {
    return gson.fromJson(body, typeToken.getType()));
    }
    });
    }


    The loadRepository code would then be something like:



    final Gson gson = new GsonBuilder().registerTypeHierarchyAdapter(Calendar.class, new CalendarGson()).create();

    Future<List<Article>> articlesFuture = callAndDeserialize("https://apiurlarticles", gson, new TypeToken<List<Article>>() {});
    Future<List<Event>> eventsFuture = callAndDeserialize("https://apiurlarticles", gson, new TypeToken<List<Event>>() {});
    Future<List<Article>> sportsFuture = callAndDeserialize("https://apiurlarticles", gson, new TypeToken<List<Article>>() {});
    Future<List<Article>> fitnessFuture = callAndDeserialize("https://apiurlarticles", gson, new TypeToken<List<Article>>() {});
    Future<List<Article>> governmentFuture = callAndDeserialize("https://apiurlarticles", gson, new TypeToken<List<Article>>() {});
    Future<Article> mainArticleFuture = callAndDeserialize("https://apiurlarticles", gson, new TypeToken<Article>() {});
    Future<Configuration> configurationFuture = callAndDeserialize("https://apiurlarticles", gson, new TypeToken<Configuration>() {});

    try {
    articles = articlesFuture.get();
    events = eventsFuture.get();
    sportArticles = sportsFuture.get();
    fitnessArticles = fitnessFuture.get();
    governmentArticles = governmentFuture.get();
    mainArticle = mainArticleFuture.get();
    config = configurationFuture.get();

    notifyObservers(HTTPRequestStatus.OK);
    } catch (ExecutionException e) {
    if(e.getCause() instanceof KeyManagementException || e.getCause() instanceof NoSuchAlgorithmException || e.getCause() instanceof KeyStoreException) {
    notifyObservers(HTTPRequestStatus.TLSERROR);
    } else {
    notifyObservers(HTTPRequestStatus.HTTPERROR);
    }
    }


    The ExecutionException at the end now retains the exception messages, causes and stacktraces. In case there's any unexpected errors you need to debug, you can also log this exception as well notifying the observers.






    share|improve this answer


























      1














      You could use a CompletableFuture for this. Make each call return a completed future with the body, or an exceptionally completed future with an error:



      private CompletableFuture<String> call(String url) {
      CompletableFuture<String> future = new CompletableFuture<>();

      OkHttpClient client = new OkHttpClient.Builder().connectTimeout(15, TimeUnit.SECONDS).build();
      client.newCall(requestbuilder.url(url).build()).enqueue(new Callback() {
      @Override
      public void onFailure(Call call, IOException e) {
      future.completeExceptionally(e);
      }

      @Override
      public void onResponse(Call call, Response response) {
      try {
      ResponseBody body = response.body();
      if (response.code() != 200 || body == null) {
      throw new IOException("Http error");
      } else {
      future.complete(body.string());
      }
      } catch (IOException | JsonSyntaxException e) {
      future.completeExceptionally(e);
      }
      }
      });

      return future;
      }


      Then add a generic method to make the calls and deserialize the results:



      private <T> Future<T> callAndDeserialize(String url, Gson gson, TypeToken<T> typeToken) {
      CompletableFuture<String> future = call(url);
      return future.thenApply(new Function<String, T>() {
      public T apply(String body) {
      return gson.fromJson(body, typeToken.getType()));
      }
      });
      }


      The loadRepository code would then be something like:



      final Gson gson = new GsonBuilder().registerTypeHierarchyAdapter(Calendar.class, new CalendarGson()).create();

      Future<List<Article>> articlesFuture = callAndDeserialize("https://apiurlarticles", gson, new TypeToken<List<Article>>() {});
      Future<List<Event>> eventsFuture = callAndDeserialize("https://apiurlarticles", gson, new TypeToken<List<Event>>() {});
      Future<List<Article>> sportsFuture = callAndDeserialize("https://apiurlarticles", gson, new TypeToken<List<Article>>() {});
      Future<List<Article>> fitnessFuture = callAndDeserialize("https://apiurlarticles", gson, new TypeToken<List<Article>>() {});
      Future<List<Article>> governmentFuture = callAndDeserialize("https://apiurlarticles", gson, new TypeToken<List<Article>>() {});
      Future<Article> mainArticleFuture = callAndDeserialize("https://apiurlarticles", gson, new TypeToken<Article>() {});
      Future<Configuration> configurationFuture = callAndDeserialize("https://apiurlarticles", gson, new TypeToken<Configuration>() {});

      try {
      articles = articlesFuture.get();
      events = eventsFuture.get();
      sportArticles = sportsFuture.get();
      fitnessArticles = fitnessFuture.get();
      governmentArticles = governmentFuture.get();
      mainArticle = mainArticleFuture.get();
      config = configurationFuture.get();

      notifyObservers(HTTPRequestStatus.OK);
      } catch (ExecutionException e) {
      if(e.getCause() instanceof KeyManagementException || e.getCause() instanceof NoSuchAlgorithmException || e.getCause() instanceof KeyStoreException) {
      notifyObservers(HTTPRequestStatus.TLSERROR);
      } else {
      notifyObservers(HTTPRequestStatus.HTTPERROR);
      }
      }


      The ExecutionException at the end now retains the exception messages, causes and stacktraces. In case there's any unexpected errors you need to debug, you can also log this exception as well notifying the observers.






      share|improve this answer
























        1












        1








        1






        You could use a CompletableFuture for this. Make each call return a completed future with the body, or an exceptionally completed future with an error:



        private CompletableFuture<String> call(String url) {
        CompletableFuture<String> future = new CompletableFuture<>();

        OkHttpClient client = new OkHttpClient.Builder().connectTimeout(15, TimeUnit.SECONDS).build();
        client.newCall(requestbuilder.url(url).build()).enqueue(new Callback() {
        @Override
        public void onFailure(Call call, IOException e) {
        future.completeExceptionally(e);
        }

        @Override
        public void onResponse(Call call, Response response) {
        try {
        ResponseBody body = response.body();
        if (response.code() != 200 || body == null) {
        throw new IOException("Http error");
        } else {
        future.complete(body.string());
        }
        } catch (IOException | JsonSyntaxException e) {
        future.completeExceptionally(e);
        }
        }
        });

        return future;
        }


        Then add a generic method to make the calls and deserialize the results:



        private <T> Future<T> callAndDeserialize(String url, Gson gson, TypeToken<T> typeToken) {
        CompletableFuture<String> future = call(url);
        return future.thenApply(new Function<String, T>() {
        public T apply(String body) {
        return gson.fromJson(body, typeToken.getType()));
        }
        });
        }


        The loadRepository code would then be something like:



        final Gson gson = new GsonBuilder().registerTypeHierarchyAdapter(Calendar.class, new CalendarGson()).create();

        Future<List<Article>> articlesFuture = callAndDeserialize("https://apiurlarticles", gson, new TypeToken<List<Article>>() {});
        Future<List<Event>> eventsFuture = callAndDeserialize("https://apiurlarticles", gson, new TypeToken<List<Event>>() {});
        Future<List<Article>> sportsFuture = callAndDeserialize("https://apiurlarticles", gson, new TypeToken<List<Article>>() {});
        Future<List<Article>> fitnessFuture = callAndDeserialize("https://apiurlarticles", gson, new TypeToken<List<Article>>() {});
        Future<List<Article>> governmentFuture = callAndDeserialize("https://apiurlarticles", gson, new TypeToken<List<Article>>() {});
        Future<Article> mainArticleFuture = callAndDeserialize("https://apiurlarticles", gson, new TypeToken<Article>() {});
        Future<Configuration> configurationFuture = callAndDeserialize("https://apiurlarticles", gson, new TypeToken<Configuration>() {});

        try {
        articles = articlesFuture.get();
        events = eventsFuture.get();
        sportArticles = sportsFuture.get();
        fitnessArticles = fitnessFuture.get();
        governmentArticles = governmentFuture.get();
        mainArticle = mainArticleFuture.get();
        config = configurationFuture.get();

        notifyObservers(HTTPRequestStatus.OK);
        } catch (ExecutionException e) {
        if(e.getCause() instanceof KeyManagementException || e.getCause() instanceof NoSuchAlgorithmException || e.getCause() instanceof KeyStoreException) {
        notifyObservers(HTTPRequestStatus.TLSERROR);
        } else {
        notifyObservers(HTTPRequestStatus.HTTPERROR);
        }
        }


        The ExecutionException at the end now retains the exception messages, causes and stacktraces. In case there's any unexpected errors you need to debug, you can also log this exception as well notifying the observers.






        share|improve this answer












        You could use a CompletableFuture for this. Make each call return a completed future with the body, or an exceptionally completed future with an error:



        private CompletableFuture<String> call(String url) {
        CompletableFuture<String> future = new CompletableFuture<>();

        OkHttpClient client = new OkHttpClient.Builder().connectTimeout(15, TimeUnit.SECONDS).build();
        client.newCall(requestbuilder.url(url).build()).enqueue(new Callback() {
        @Override
        public void onFailure(Call call, IOException e) {
        future.completeExceptionally(e);
        }

        @Override
        public void onResponse(Call call, Response response) {
        try {
        ResponseBody body = response.body();
        if (response.code() != 200 || body == null) {
        throw new IOException("Http error");
        } else {
        future.complete(body.string());
        }
        } catch (IOException | JsonSyntaxException e) {
        future.completeExceptionally(e);
        }
        }
        });

        return future;
        }


        Then add a generic method to make the calls and deserialize the results:



        private <T> Future<T> callAndDeserialize(String url, Gson gson, TypeToken<T> typeToken) {
        CompletableFuture<String> future = call(url);
        return future.thenApply(new Function<String, T>() {
        public T apply(String body) {
        return gson.fromJson(body, typeToken.getType()));
        }
        });
        }


        The loadRepository code would then be something like:



        final Gson gson = new GsonBuilder().registerTypeHierarchyAdapter(Calendar.class, new CalendarGson()).create();

        Future<List<Article>> articlesFuture = callAndDeserialize("https://apiurlarticles", gson, new TypeToken<List<Article>>() {});
        Future<List<Event>> eventsFuture = callAndDeserialize("https://apiurlarticles", gson, new TypeToken<List<Event>>() {});
        Future<List<Article>> sportsFuture = callAndDeserialize("https://apiurlarticles", gson, new TypeToken<List<Article>>() {});
        Future<List<Article>> fitnessFuture = callAndDeserialize("https://apiurlarticles", gson, new TypeToken<List<Article>>() {});
        Future<List<Article>> governmentFuture = callAndDeserialize("https://apiurlarticles", gson, new TypeToken<List<Article>>() {});
        Future<Article> mainArticleFuture = callAndDeserialize("https://apiurlarticles", gson, new TypeToken<Article>() {});
        Future<Configuration> configurationFuture = callAndDeserialize("https://apiurlarticles", gson, new TypeToken<Configuration>() {});

        try {
        articles = articlesFuture.get();
        events = eventsFuture.get();
        sportArticles = sportsFuture.get();
        fitnessArticles = fitnessFuture.get();
        governmentArticles = governmentFuture.get();
        mainArticle = mainArticleFuture.get();
        config = configurationFuture.get();

        notifyObservers(HTTPRequestStatus.OK);
        } catch (ExecutionException e) {
        if(e.getCause() instanceof KeyManagementException || e.getCause() instanceof NoSuchAlgorithmException || e.getCause() instanceof KeyStoreException) {
        notifyObservers(HTTPRequestStatus.TLSERROR);
        } else {
        notifyObservers(HTTPRequestStatus.HTTPERROR);
        }
        }


        The ExecutionException at the end now retains the exception messages, causes and stacktraces. In case there's any unexpected errors you need to debug, you can also log this exception as well notifying the observers.







        share|improve this answer












        share|improve this answer



        share|improve this answer










        answered 20 hours ago









        fgb

        48538




        48538






















            Exprove is a new contributor. Be nice, and check out our Code of Conduct.










            draft saved

            draft discarded


















            Exprove is a new contributor. Be nice, and check out our Code of Conduct.













            Exprove is a new contributor. Be nice, and check out our Code of Conduct.












            Exprove is a new contributor. Be nice, and check out our Code of Conduct.
















            Thanks for contributing an answer to Code Review Stack Exchange!


            • Please be sure to answer the question. Provide details and share your research!

            But avoid



            • Asking for help, clarification, or responding to other answers.

            • Making statements based on opinion; back them up with references or personal experience.


            Use MathJax to format equations. MathJax reference.


            To learn more, see our tips on writing great answers.





            Some of your past answers have not been well-received, and you're in danger of being blocked from answering.


            Please pay close attention to the following guidance:


            • Please be sure to answer the question. Provide details and share your research!

            But avoid



            • Asking for help, clarification, or responding to other answers.

            • Making statements based on opinion; back them up with references or personal experience.


            To learn more, see our tips on writing great answers.




            draft saved


            draft discarded














            StackExchange.ready(
            function () {
            StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f210437%2fcall-multiple-asynchronous-http-requests-and-wait-for-the-result%23new-answer', 'question_page');
            }
            );

            Post as a guest















            Required, but never shown





















































            Required, but never shown














            Required, but never shown












            Required, but never shown







            Required, but never shown

































            Required, but never shown














            Required, but never shown












            Required, but never shown







            Required, but never shown







            Popular posts from this blog

            Список кардиналов, возведённых папой римским Каликстом III

            Deduzione

            Mysql.sock missing - “Can't connect to local MySQL server through socket”