I want to replace the following with generics:
for (Post post : postResponse.getResults()) {, where Post can be any POJO.
List<Post> posts = postResponse.getResults(); where List<Post> can be a list of anything I pass into it.
What would my method call and method body look like?
Different examples of method calls:
retrieveData(mCardAdapter, new Post(), Post.class);
retrieveData(mCardAdapter, new Contact(), Contact.class);
retrieveData(mCardAdapter, new Product(), Product.class);
retrieveData(mCardAdapter, new Booking(), Booking.class);
Method:
private void retrieveData(final CardAdapter mCardAdapter, final Object<T> postObject, Class<T> postClass) {
RetrofitService service = ServiceFactory.createRetrofitService(RetrofitService.class, RetrofitService.SERVICE_ENDPOINT);
service.getPosts()
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<PostResponse>() {
#Override
public final void onCompleted() {
setRefreshingFalse();
Log.e("RetrofitService", "Retrofit Request Completed!");
}
#Override
public final void onError(Throwable e) {
setRefreshingFalse();
Log.e("RetrofitService", e.getMessage());
}
#Override
public final void onNext(PostResponse postResponse) {
if (postResponse != null) {
Log.e("RetrofitService", "Returned objects: " + postResponse.getResults());
for (postObject post : postResponse.getResults()) {
Log.e("RetrofitService", post.getObjectId() + ": " + post.getText());
}
setRefreshingFalse();
mCardAdapter.clear();
List<postClass> posts = postResponse.getResults();
mCardAdapter.addData(posts);
} else {
Log.e("RetrofitService", "Object returned is null.");
}
}
});
}
I'm getting Unknown class: 'tClass' and Unknown class: 'postClass'. Obviously this is not the way to do it so perhaps treat what I've shown above as pseduo-code. Does it makes sense what I'm trying to? I really want to generify this retrieveData method so that I can be used to query differences classes.
To help with understanding. What I want to avoid:
retrievePosts(mCardAdapter);
retrieveUsers(mCardAdapter);
private void retrievePosts(final CardAdapter mCardAdapter) {
RetrofitService service = ServiceFactory.createRetrofitService(RetrofitService.class, RetrofitService.SERVICE_ENDPOINT);
service.getPosts()
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<PostResponse>() {
#Override
public final void onCompleted() {
setRefreshingFalse();
Log.e("RetrofitService", "Retrofit Request Completed!");
}
#Override
public final void onError(Throwable e) {
setRefreshingFalse();
Log.e("RetrofitService", e.getMessage());
}
#Override
public final void onNext(PostResponse postResponse) {
if (postResponse != null) {
Log.e("RetrofitService", "Returned objects: " + postResponse.getResults());
for (Post post : postResponse.getResults()) {
Log.e("RetrofitService", post.getObjectId() + ": " + post.getText());
}
/*for (Post post : postResponse.getResults()) {
mCardAdapter.addData(post);
}*/
setRefreshingFalse();
mCardAdapter.clear();
List<Post> posts = postResponse.getResults();
mCardAdapter.addData(posts);
} else {
Log.e("RetrofitService", "Object returned is null.");
}
}
});
}
private void retrieveUsers(final CardAdapter mCardAdapter) {
RetrofitService service = ServiceFactory.createRetrofitService(RetrofitService.class, RetrofitService.SERVICE_ENDPOINT);
service.getUsers()
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<PostResponse>() {
#Override
public final void onCompleted() {
setRefreshingFalse();
Log.e("RetrofitService", "Retrofit Request Completed!");
}
#Override
public final void onError(Throwable e) {
setRefreshingFalse();
Log.e("RetrofitService", e.getMessage());
}
#Override
public final void onNext(PostResponse postResponse) {
if (postResponse != null) {
Log.e("RetrofitService", "Returned objects: " + postResponse.getResults());
for (User user : userResponse.getResults()) {
Log.e("RetrofitService", user.getObjectId() + ": " + user.getText());
}
/*for (Post post : postResponse.getResults()) {
mCardAdapter.addData(post);
}*/
setRefreshingFalse();
mCardAdapter.clear();
List<User> users = userResponse.getResults();
mCardAdapter.addData(users);
} else {
Log.e("RetrofitService", "Object returned is null.");
}
}
});
}
If I understood your question correctly you want to have a generic method for different classes. I did not see that you are adding something to the List so this might work for you.
private <clazz> void retrieveData(final Class<?> clazz) {
for (clazz post : postResponse.getResults()) {
// you can't do anything here since clazz can be anything
}
List<clazz> posts = postResponse.getResults();
}
Related
So I want to add a dialog message to my app. There already an option for other types of error's. I just want to add an error for when there's no mobile data and WiFi. It's an older app, so it's taking me a bit more to understand, but here's what I got.
So here is the status code for the errors. Not sure if the codes are random or not (I didn't make this project)
public class StatusCodeUtil {
public static final int AWS_GATEWAY_ERROR = 1;
public static final int URL_INVALID = 2;
public static final int INTERNAL_SERVER_ERROR = 14;
public static final int ENDPOINT_INFO_STORAGE_INCOMPLETE = 7;
public static final int NO_PERMISSION_GET_DEVICE_ID = 8;
public static final int INVALID_API_FUNCTION = 18;
public static final int INVALID_HTTP_STATUS_CODE = -1;
public static final int NO_NETWORK_ERROR = 3; <- This is the status code I want to work
}
Here is the Callback for the errors
public abstract class ApiCallBack<T> implements Callback<ApiResponse<T>> {
private ParabitSDKBeaconApplication application;
public ApiCallBack(ParabitSDKBeaconApplication application) {
this.application = application;
}
#Override
public void onResponse(Call<ApiResponse<T>> call, Response<ApiResponse<T>> response) {
Long roundTripTime = getRoundTripTime(response);
if (response.isSuccessful()) {
ApiResponse<T> responseBody = response.body();
onApiResponse(responseBody.getMessage(), response.code(), responseBody.getData());
} else {
/**
* error level 1 (HTTP client or gateway error)
* */
String errorBodyJson = getErrorBodyStr(response);
// can not user ApiResponse<T> to catch the json here
// will lead to exception: java.lang.AssertionError: illegal type variable reference
// no good way to solve this (Gson's problem)
ApiErrorResponse errorBody = GsonUtil.jsonStrToObject(errorBodyJson,
new TypeToken<ApiErrorResponse>(){});
if (errorBody.getMessage().equalsIgnoreCase("forbidden")) { // x-api-key invalid
if (getLogControlManager().isLog()) {
Log.e(PARABIT_SDK_LOG, "AWS Gateway Error: " + errorBody.getMessage());
}
onError(new ApiErrorCodeInfo(AWS_GATEWAY_ERROR, response.code(),
errorBody.getMessage()));
} else if (errorBody.getMessage().equalsIgnoreCase(
"missing authentication token")) {
if (getLogControlManager().isLog()) {
Log.e(PARABIT_SDK_LOG, "AWS Gateway Error: " + errorBody.getMessage());
}
onError(new ApiErrorCodeInfo(INVALID_API_FUNCTION, response.code(),
errorBody.getMessage()));
} else {
if (getLogControlManager().isLog()) {
Log.e(PARABIT_SDK_LOG, "Other Error Response: " + errorBody.getMessage());
}
// should never happen for now
onError(new ApiErrorCodeInfo(INTERNAL_SERVER_ERROR, response.code(),
errorBody.getMessage()));
}
}
}
#Override
public void onFailure(Call<ApiResponse<T>> call, Throwable t) {
/**
* error level 1 (HTTP client or gateway error)
* */
if (t instanceof UnknownHostException) { // host of end point is unknown
if (getLogControlManager().isLog()) {
Log.e(PARABIT_SDK_LOG, "onFailure: " + "UnknownHostException");
}
onError(new ApiErrorCodeInfo(URL_INVALID, t.getLocalizedMessage()));
} else {
if (getLogControlManager().isLog()) {
Log.e(PARABIT_SDK_LOG, "onFailure: " + t.getLocalizedMessage());
}
onError(new ApiErrorCodeInfo(INTERNAL_SERVER_ERROR,
t.getLocalizedMessage()));
}
}
public static<T> String getErrorBodyStr(Response<ApiResponse<T>> response) {
if (response.errorBody() == null) {
return "";
}
String errorBodyStr = "";
try {
errorBodyStr = response.errorBody().string();
} catch (IOException e) {
e.printStackTrace();
}
return errorBodyStr;
}
protected Long getRoundTripTime(Response response) {
Long roundTripTime = response.raw().sentRequestAtMillis()
- response.raw().receivedResponseAtMillis();
return roundTripTime;
}
// public abstract void onSuccess(String successMsg, List<T> data);
public abstract void onApiResponse(String ApiMsg, int httpStatusCode, List<T> data);
public abstract void onError(ApiErrorCodeInfo apiErrorCodeInfo);
protected LogControlManager getLogControlManager() {
return SdkApplicationInstance.getSdkLogControlManager(application);
}
}
The code in the Activity that controls which error is shown
loginViewModel.loginStatusInfo.observe(this, loginStatusInfo -> {
if (loginStatusInfo.getStatus() == API_SUCCESS_STATUS){
hideLoadingDialog();
startHomeActivity();
}else if (loginStatusInfo.getStatus() == INTERNAL_SERVER_ERROR) {
hideLoadingDialog();
loginErrorDialog(getString(R.string.fail_to_login_server_error));
}else if(loginStatusInfo.getStatus() == NO_NETWORK_ERROR){<- I added this else if
hideLoadingDialog();
loginErrorDialog(getString(R.string.network_require_msg));
}
else {
hideLoadingDialog();
loginErrorDialog(loginStatusInfo.getMessage());
}
});
Any help will be appreciated, Thank you.
So I actually called ConectivityManager on the loginViewModel.loginStatusInfo method and it worked.
loginViewModel.loginStatusInfo.observe(this, loginStatusInfo -> {
if (loginStatusInfo.getStatus() == API_SUCCESS_STATUS){
hideLoadingDialog();
startHomeActivity();
}else if (loginStatusInfo.getStatus() == INTERNAL_SERVER_ERROR) {
hideLoadingDialog();
loginErrorDialog(getString(R.string.fail_to_login_server_error));
}else if(ConnectivityManager.TYPE_MOBILE == 0){
hideLoadingDialog();
networkErrorDialog(getString(R.string.network_require_msg));
}
else {
hideLoadingDialog();
loginErrorDialog(loginStatusInfo.getMessage());
}
});
I want to send multiple API requests, so I'm using RxJava's Zip operator, I want to know the success rate of the API requests to show it to the user, but here, whenever one request getting failed, I couldn't see any logs inside the complete method,
How to listen over all the responses together (success/fail) and find the success rate?
List<io.reactivex.rxjava3.core.Observable<Object>> requests = new ArrayList<>();
requests.add(
RetrofitInstance.getRetrofitClient()
.create(IService.class)
.sendMessage("123", com)); // my custom model class
Observable
.zip(requests, // list of API requests
new Function<Object[], List<Object>>() {
#Override
public List<Object> apply(Object[] objects) throws Exception {
Log.d("onSubscribe", "apply: " + objects.length);
for (Object o : objects) {
Log.d(TAG, "apply: %%%%% " + o.toString());
messageResponse.add((Object) o);
}
if (messageResponse.size() == requests.size()) {
Log.d(TAG, "apply: req size " + requests.size());
}
Log.d(TAG, "apply: ##4");
msgResponse[0] = true;
return messageResponse;
}
})
.subscribeOn(Schedulers.io())
.subscribe(new Observer<List<Object>>() {
#Override
public void onSubscribe(
#io.reactivex.rxjava3.annotations.NonNull Disposable d) {
Log.d(TAG, "onSubscribe: ");
}
#Override
public void onNext(
#io.reactivex.rxjava3.annotations.NonNull List<Object> objects) {
Log.d(TAG, "onNext: ");
}
#Override
public void onError(
#io.reactivex.rxjava3.annotations.NonNull Throwable e) {
Log.d(TAG, "onError: ");
}
#Override
public void onComplete() {
Log.d(TAG, "onComplete: ");
}
});
I know this is a common question, but I did all of them, still no resolved. In my MainActivity, I had a call from an ServerService.java, like this:
String randomNumber = serverService.contactServer();
In the ServerService.java, the contactServer() will call the method which contains the .enqueue:
public String contactServer() {
return requestServerService();
}
And the requestServerService() contains the code:
public String requestServerService() {
Call<RequestAttributes> call = new RetrofitConfig().getServiceRequester().requestRandomNumber();
call.enqueue(new Callback<RequestAttributes>() {
#Override
public void onResponse(Call<RequestAttributes> call, Response<RequestAttributes> response) {
if (!response.isSuccessful()) {
Log.i("Err", "Err: " + response.code());
} else {
RequestAttributes requestAttributes = response.body();
returnedValue = requestAttributes.getRandomNumber();
Log.d("jsonAnswer", "O numero aleatorio é: " + returnedValue);
}
}
#Override
public void onFailure(Call<RequestAttributes> call, Throwable t) {
Log.e("Fail", "Failed: " + t.getMessage());
}
}); return returnedValue;
The error is the returnedValue returns null. I tried debbuging, but even it doesn't reach onReponse. I know the problem must be because .enqueue is asynchronous, but how can I resolve this problem and return the request to the mainActivity?
The config of Retrofit:
public RetrofitConfig() {
this.retrofit = new Retrofit.Builder()
.baseUrl("localhost:3000/")
.addConverterFactory(GsonConverterFactory.create())
.build();
}
public ServiceRequester getServiceRequester() {
return this.retrofit.create(ServiceRequester.class);
}
The POJO:
public class RequestAttributes {
#SerializedName("randomNumber")
private String randomNumber;
public String getRandomNumber() {
return randomNumber;
}
public void setRandomNumber(String randomNumber) {
this.randomNumber = randomNumber;
}
#Override
public String toString() {
return "RequestAttributes{" +
", randomNumber='" + randomNumber + '\'' +
'}';
}
}
And the request:
#GET("api/requestRandomNumber")
Call<RequestAttributes> requestRandomNumber();
The JSON answer if I request via browser:
{"randomNumber":"u845gq"}
You can pass callbacks from your MainActivity to contactServer() method
serverService.contactServer(new Callback<RequestAttributes>() {
#Override
public void onResponse(Call<RequestAttributes> call, Response<RequestAttributes> response) {
if (!response.isSuccessful()) {
Log.i("Err", "Err: " + response.code());
} else {
RequestAttributes requestAttributes = response.body();
String returnedValue = requestAttributes.getRandomNumber();
// Do what you want here with returnedValue. You are in the activity thread(MainThread or UIThread) for example someTextView.setText(returnedValue);
Log.d("jsonAnswer", "O numero aleatorio é: " + returnedValue);
}
}
#Override
public void onFailure(Call<RequestAttributes> call, Throwable t) {
Log.e("Fail", "Failed: " + t.getMessage());
}
});
Then make it void method, pass the callback to requestServerService() method
public void contactServer(Callback<RequestAttributes> callback) {
requestServerService(callback);
}
Then implement requestServerService() method like this:
public void requestServerService(Callback<RequestAttributes> callback) {
Call<RequestAttributes> call = new RetrofitConfig().getServiceRequester().requestRandomNumber();
call.enqueue(callback);
}
I am writing functions like these and I have a couple of them. if we see the response handler in both of them are same lines of code.
private void get(JsonObject request, Message<JsonObject> message) {
webClient.get(webClientPort,
webClientHost, "/document/" + request.getString("key"))
.putHeader(HttpHeaders.CONTENT_TYPE.toString(), "application/json")
.send(res -> {
if (res.succeeded()) {
message.reply(res.result().bodyAsString());
} else {
message.fail(0, Json.encode(new JsonObject().put("error", res.cause())));
}
});
}
private void add(JsonObject request, Message<JsonObject> message) {
webClient.post(webClientPort,
webClientHost, "/document/" + request.getString("key"))
.putHeader(HttpHeaders.CONTENT_TYPE.toString(), "application/json")
.sendJson(request.getJsonObject("document").encodePrettily(), res -> {
if (res.succeeded()) {
message.reply(res.result().bodyAsString());
} else {
message.fail(0, Json.encode(new JsonObject().put("error", res.cause())));
}
});
}
is there a way to avoid this duplicate code and move it to a common place?
res -> {
if (res.succeeded()) {
message.reply(res.result().bodyAsString());
} else {
message.fail(0, Json.encode(new JsonObject().put("error", res.cause())));
}
}
Can't you just encapsulate that logic another function and call it via method reference?
private void processResponse(HttpServerResponse res, Message<JsonObject> message) {
if (res.succeeded()) {
message.reply(res.result().bodyAsString());
} else {
message.fail(0, Json.encode(new JsonObject().put("error", res.cause())));
}
}
Then you can just call it with a method reference in both places your code:
.send(res -> this.processResponse(res, message));
and
.sendJson(request.getJsonObject("document").encodePrettily(), res -> this.processResponse(res, message));
I was able to do like this and it worked. Thanks #dovmo for your help. your inputs helped me reached to solution.
private void get(String key, Message<JsonObject> message) {
webClient.get(webClientPort,
webClientHost, "/document/" + key)
.putHeader(HttpHeaders.CONTENT_TYPE.toString(), "application/json")
.send(processResponse(message));
}
private void delete(String key, JsonObject body, Message<JsonObject> message) {
webClient.delete(webClientPort,
webClientHost, "/document/" + key)
.putHeader(HttpHeaders.CONTENT_TYPE.toString(), "application/json")
.send(processResponse(message));
}
private Handler<AsyncResult<HttpResponse<Buffer>>> processResponse( Message<JsonObject> message) {
Handler<AsyncResult<HttpResponse<Buffer>>> handler = reply -> {
if (reply.succeeded()) {
message.reply(reply.result().bodyAsString());
} else {
message.fail(0, Json.encode(new JsonObject().put("error", reply.cause())));
}
};
return handler;
}
I listened to this talk
https://www.youtube.com/watch?v=QdmkXL7XikQ&feature=youtu.be&t=274
And eared that I should avoid creating an Observable using the create method, because it doesn't handle unsubscription and backpressure automatically, but I can't find an alternative to use in the code bellow.
compositeSubscription.add(
Observable.create(new Observable.OnSubscribe<DTOCompaniesCallback>() {
#Override
public void call(final Subscriber<? super DTOCompaniesCallback> subscriber) {
modelTrainStrike.getCompaniesFromServer(new CompaniesCallback() {
#Override
public void onResult(DTOCompaniesCallback dtoCompaniesCallback) {
try {
if (!subscriber.isUnsubscribed()) {
subscriber.onNext(dtoCompaniesCallback);
subscriber.onCompleted();
}
} catch (Exception e) {
if (!subscriber.isUnsubscribed()) {
subscriber.onError(e);
}
}
}
});
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<DTOCompaniesCallback>() {
#Override
public void call(DTOCompaniesCallback dtoCompaniesCallback) {
Log.i("TAG", "onResult: " + dtoCompaniesCallback.getCompaniesList().size());
}
}, new Action1<Throwable>() {
#Override
public void call(Throwable throwable) {
throw new OnErrorNotImplementedException("Source!", throwable);
}
})
);
And I call clear the CompositeSubscription in the OnDestroy method
#Override
public void onDestroy() {
if (compositeSubscription != null) {
compositeSubscription.clear();
}
}
Do you see any alternative to the create method that I could use here?
Do you see any potential danger or is this approach safe?
Thanks
You can use defer + AsyncSubject:
Observable.defer(() -> {
AsyncSubject<DTOCompaniesCallback> async = AsyncSubject.create();
modelTrainStrike.getCompaniesFromServer(v -> {
async.onNext(v);
async.onComplete();
});
return async;
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
...
In case the getCompaniesFromServer supports cancellation, you can:
Observable.defer(() -> {
AsyncSubject<DTOCompaniesCallback> async = AsyncSubject.create();
Closeable c = modelTrainStrike.getCompaniesFromServer(v -> {
async.onNext(v);
async.onComplete();
});
return async.doOnUnsubscribe(() -> {
try { c.close(); } catch (IOException ex) { }
});
})