How to show a LoadingDialog only if the operation takes too long? - java

I do an API call in my Android App which can return a response almost immediatly or take a bit longer, before the API call i show my LoadingDialog and on response i dismiss it, the big issue of that is even if the response is immediate i show and dismiss the LoadingDialog and it seems like a "bug" as the screen shows and hides immediatly a dialog.
I would be able to show that dialog ONLY if the response take longer than 1 second to be returned.
Here is my code:
public void AlertLoading() {
Fragment fragment = getSupportFragmentManager().findFragmentByTag(LoadingDialog.TAG);
if (fragment instanceof LoadingDialog) {
((LoadingDialog) fragment).dismissAllowingStateLoss();
}else {
new LoadingDialog().show(getSupportFragmentManager(), LoadingDialog.TAG);
}
}
private void getTable(String tableNumber) {
AlertLoading();
Ion.with(getApplicationContext())
.load("http://" + ip + "/webquery/?FINDTAV=" + tableNumber + "&v2=true")
.setTimeout(10000)
.asString()
.setCallback((e, result) -> {
AlertLoading();
// DOING STUFF
});
}

Use Flows to receive api response and collect it inside coroutines
viewModelScope.launch(Dispatchers.IO) {
repo.myApiFunc(param1, param2)
.onStart {
withContext(Dispatchers.Main) {
uiState.value = UiState.ProgressBar(true)
}
}
.catch {
withContext(Dispatchers.Main) {
uiState.value = UiState.ProgressBar(false)
}
}
.collect { response->
withContext(Dispatchers.Main) {
uiState.value = UiState.ProgressBar(false)
handleData(response)
}
}
}
And Your repository function looks like this
suspend fun myApiFunc(param1: Any, param2: Any
): Flow<MyResponse> {
return flow<MyResponse> {
emit(apiHelper.apiCall(param1, param2))
}
}
and in your api call looks like
#POST("Endpoint")
suspend fun apiCall(
#Field("email") email: String,
#Field("password") password: String
): MyResponse
and your uistate variable is also of flow type that is collected inside fragment and displays the loader on receiving true while hides it on false

As suggested by #Ricky Mo in comments i've followed the way of checking for Content-Lenght and if this is > than X bytes i'm showing the loading dialog.
I changed the code as following:
private void getTable(String tableNumber) {
// Doing an API call with HEAD method which returns Content-Lenght, if this is > than 10000 i'm showing the dialog
LoadingDialog.RetrofitClient.getInstance(ip).getApi().getTableLenght(tableNumber, true)
.enqueue(new Callback<Void>() {
#Override
public void onResponse(#NonNull Call<Void> call, #NonNull retrofit2.Response<Void> response) {
String contentLength = response.headers().get("Content-Length");
if (contentLength != null) {
if (Double.parseDouble(contentLength) > 10000) {
showLoading(true);
}
}
}
#Override
public void onFailure(#NonNull Call<Void> call, #NonNull Throwable t) {
showLoading(false);
}
});
// I've changed Ion with Retrofit for data parsing
Call<String> call = RetrofitClient.getInstance(ip).getApi().getTable(tableNumber, true);
call.enqueue(new Callback<String>() {
#Override
public void onResponse(#NonNull Call<String> call, #NonNull Response<String> response) {
showLoading(false);
if (response.isSuccessful()) {
... Doing stuff
}
}
#Override
public void onFailure(#NonNull Call<String> call, #NonNull Throwable t) {
showLoading(false);
}
});
}

Related

How to make a void method with Retrofit return string?

So, here is the method in my adapter. Later on, in onBindViewHolder() method I want to perform such action: String pic_url = showJustProduct(product_name);, but the onResponse method can`t return anything except void. What should I do to put img_url to pic_url?
public void showJustProduct(String title){
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://url")
.addConverterFactory(GsonConverterFactory.create())
.addConverterFactory(ScalarsConverterFactory.create())
.build();
JustJsonPlaceHolderApi jsonPlaceHolderApi = retrofit.create(JustJsonPlaceHolderApi.class);
Call<JustProducts> call = jsonPlaceHolderApi.getProducts(title);
call.enqueue(new Callback<JustProducts>() {
#SuppressLint("SetTextI18n")
#Override
public void onResponse(#NotNull Call<JustProducts> call, #NotNull Response<JustProducts> response) {
if (response.isSuccessful()) {
assert response.body() != null;
List<JustProduct> justproducts = response.body().getProducts();
JustProduct justProduct = justproducts.get(0);
String img_url = justProduct.getImage();
Log.e("1", "it works!");
}
}
#Override
public void onFailure(#NotNull Call<JustProducts> call, #NotNull Throwable t) {
Log.e("failure", Objects.requireNonNull(t.getLocalizedMessage()));
}
});
}
You can use synchronized call.execute() method instead of call.enqueue().
This can lead to NetworkOnMainThreadException. So be sure to execute it on background thread not UI thread.
Example:
Response<JustProducts> response = jsonPlaceHolderApi.getProducts(title).execute();
if (response.isSuccessful()) {
...
}

How to Read error body in Retrofit when response is not successful?

I am using retrofit in Android for api execution.
Sample Snippet
Call<UniversalPojo> call = apiInterface.storeData(AppClass.getInstance().getLoggedInUser().getRemember_token(), requestBody);
call.enqueue(new Callback<UniversalPojo>() {
#Override
public void onResponse(Call<UniversalPojo> call, Response<UniversalPojo> response) {
if (response.isSuccessful()) {
} else {
//I want to read code at this stage in string.
}
}
#Override
public void onFailure(Call<UniversalPojo> call, Throwable t) {
t.printStackTrace();
}
});
My question here is how to obtain the error in String at else block of if (response.isSuccessful()).
use OkHttpClient class's addInterceptor(interceptor: Interceptor) function
override the intercept(chain: Interceptor.Chain) function and throw exceptions as you expected:
class NetInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
val response = chain.proceed(request)
val code = response.code
val body = response.body
// if body is null or something unexpected
throw IOException("receive empty body")
// else do nothing
}
}

OkHttp EventListener : byteCount value is always zero in responseBodyEnd callback

I am using EventListener callback for some analysis in my app but I am facing an issue where I am not getting the correct value byteCount in responseBodyEnd callback. It is always 0.
I am attaching the code below.
val client = OkHttpClient.Builder()
.eventListenerFactory(HttpEventListenerFactory.FACTORY)
.build()
val request = Request.Builder()
.url("http://jsonplaceholder.typicode.com/comments?postId=1")
.build()
with(client) {
newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
Log.d("OkHttp##", "Request failed")
}
override fun onResponse(call: Call, response: Response) {
handleResponse(response.body())
response.close()
Log.d("OkHttp##", "Response received")
}
})
}
MyEventListenerImpl.kt
public class HttpEventListenerFactory extends EventListener {
public static final Factory FACTORY = new Factory() {
final AtomicLong nextCallId = new AtomicLong(1L);
#Override
public EventListener create(Call call) {
long callId = nextCallId.getAndIncrement();
Log.d("OkHttp##", "next call id : " + nextCallId);
String message = String.format(Locale.US, "%04d %s%n", callId, call.request().url());
Log.d("OkHttp##", message);
return new HttpEventListenerFactory(callId, System.nanoTime());
}
};
#Override
public void responseBodyEnd(Call call, long byteCount) {
// this method never gets called
// byteCount here is always 0
printEvent("Response body end", callId);
}

Android Retrofit API usage

I'm still beginner on retrofit API for android, im trying to pass a variable into my dynamic url but it adds extra characters to it these characters are "&=".
This is what the endpoint im trying to consume looks like:
https://www.example.com/api/index.php?/Playlists/getTracks/00978d67f6933af10ec8bd8045f089a4/0673CC13-476A-4786-BF27-13ADD9C44261/9392
where "9232" is the id i am trying to pass. However when i use the retrofit library this is what my generated Url looks like:
https://www.example.com/api/index.php?/Playlists/getTracks/00978d67f6933af10ec8bd8045f089a4/0673CC13-476A-4786-BF27-13ADD9C44261/&=9392
Notice the &= being attached to the Id im sending
// Method that receives id and calls the retrofit API
private void getPlaylist(String id) {
/*Create handle for the RetrofitInstance interface*/
GetPlaylistRetrofitInterface playlistService = PlaylistClientInstance.getRetrofitInstance().create(GetPlaylistRetrofitInterface.class);
Call<List<Playlists>> call = playlistService.getPlaylist(id);
Log.d("URL", "getPlaylist: " + call.request().url());
call.enqueue(new Callback<List<Playlists>>() {
#Override
public void onResponse(Call<List<Playlists>> call, Response<List<Playlists>> response) {
myProgressBar.setVisibility(View.GONE);
populatePlayList(response.body());
}
#Override
public void onFailure(Call<List<Playlists>> call, Throwable throwable) {
myProgressBar.setVisibility(View.GONE);
Log.d("onFailure", "onFailure: " + throwable);
Toast.makeText(getActivity(), throwable.getMessage(), Toast.LENGTH_LONG).show();
}
});
}
And this is the interface where i am receiving the url id and attaching it to the endpoint
public interface GetPlaylistRetrofitInterface {
#GET("index.php?/Playlists/getTracks/00978d67f6933af10ec8bd8045f089a4/0673CC13-476A-4786-BF27-13ADD9C44261/")
Call<List<Playlists>> getPlaylist(#Query(value = "") String id);
}
I have tried using #Path in my interface
public interface GetPlaylistRetrofitInterface {
#GET("index.php?/Playlists/getTracks/00978d67f6933af10ec8bd8045f089a4/0673CC13-476A-4786-BF27-13ADD9C44261/{id}")
Call<List<Playlists>> getPlaylist(#Path("id") String id);
}
However it made my app crash with this error:
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.demoapp.HomeActivity}:
java.lang.IllegalArgumentException: URL query string "/Playlists/getTracks/00978d67f6933af10ec8bd8045f089a4/0673CC13-476A-4786-BF27-13ADD9C44261/{id}" must not have replace block. For dynamic query parameters use #Query.
for method GetPlaylistRetrofitInterface.getPlaylist
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2778)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2856)
at android.app.ActivityThread.-wrap11(Unknown Source:0)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1589)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:164)
at android.app.ActivityThread.main(ActivityThread.java:6494)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:440)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807)
Thanks in Advance
Okay so i was able to Fix the issue, I turned out i was trying to pass a query by using #Query to an endpoint which is already a query so this is how i fixed it:
This is my updated GetPlaylistInterface
public interface GetPlaylistRetrofitInterface {
#GET()
Call<List<Playlists>> getPlaylist(#Url String url);
}
This is my updated Get Playlist method
private void getPlaylist(String id) {
myProgressBar.setVisibility(View.VISIBLE);
/*Create handle for the RetrofitInstance interface*/
GetPlaylistRetrofitInterface playlistService = RetrofitClientInstance.getRetrofitInstance().create(GetPlaylistRetrofitInterface.class);
Call<List<Playlists>> call = playlistService.getPlaylist(Config.playlist_url+id);
Log.d("URL", "getPlaylist: " + call.request().url());
call.enqueue(new Callback<List<Playlists>>() {
#Override
public void onResponse(Call<List<Playlists>> call, Response<List<Playlists>> response) {
myProgressBar.setVisibility(View.GONE);
populatePlayList(response.body());
}
#Override
public void onFailure(Call<List<Playlists>> call, Throwable throwable) {
myProgressBar.setVisibility(View.GONE);
Log.d("onFailure", "onFailure: " + throwable);
Toast.makeText(getActivity(), throwable.getMessage(), Toast.LENGTH_LONG).show();
}
});
}
And this is the config class containing the endpoints:
public class Config {
public static String playlist_url = "index.php?/Playlists/getTracks/00978d67f6933af10ec8bd8045f089a4/0673CC13-476A-4786-BF27-13ADD9C44261/";
public static String playlist_details_url = "index.php?/Tracks/get/00978d67f6933af10ec8bd8045f089a4/0673CC13-476A-4786-BF27-13ADD9C44261/";
}
You're using #Query parameter, which has this syntax by default.
What your need is to use #Path instead of #Query. Also you need to include name of parameter into your url string.
Your code will look somehow like this:
public interface GetPlaylistRetrofitInterface {
#GET("index.php?/Playlists/getTracks/00978d67f6933af10ec8bd8045f089a4/0673CC13-476A-4786-BF27-13ADD9C44261/{id}")
Call<List<Playlists>> getPlaylist(#Path("id") String id);
}
You almost Have it first You need to use Query Parameter for you query String and Path for url/uri manipulation seems like you had those mixed up now below example should work
public interface ApiServices {
#GET("index.php?/Playlists/getTracks/00978d67f6933af10ec8bd8045f089a4/0673CC13-476A-4786-BF27-13ADD9C44261/{id}")
Call<ResponseBody> getTracks(#Path("id") String id);
}
EDIT:
You need to only define Interface and call it once. Above is interface and below is how to call it from a functions like your trying to do... its weird you have index.php? are you sure that is needed.
// Method that receives id and calls the retrofit API
private void getPlaylist(String id) {
/*Create handle for the RetrofitInstance interface*/
OkHttpClient client = new OkHttpClient.Builder()
.retryOnConnectionFailure(true)
.build();
Retrofit retrofit = new Retrofit.Builder()
.client(client)
.baseUrl("https://stepank.com/")
.build();
ApiServices service = retrofit.create(ApiServices.class);
try {
Call<ResponseBody> call = service.getTracks(id);
call.enqueue(new Callback<ResponseBody>() {
#Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
int result = -1;
setRefreshActionButtonState(false);
if (response.isSuccessful()) {
///DO STUFF HERE WITH RESPONSE
}
}
}
#Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
t.printStackTrace();
}
});
}

Retrofit Android - Expected a string but was BEGIN_OBJECT (When parsing request BODY)

I have a problem with Retrofit & Android.
I have API method "api/account/login". This method has Content-Type application/json and what about the body:
"Request body has to contain LoginData object serialized to JSON format and properly encrypted" (from api doc)
So, sample body:
"cUdu2LkEChP...Lw7R9m4="
Also, I have second API Method: "api/account/data", which have the same body and content-type.
What is my problem?
I am using Retrofit to connect with API.
interface APIService {
#POST("api/account/login")
void postLogin(#Body String encryptedCredentials, Callback<String> callback);
#POST("/api/account/data")
void postLoginData(#Body String encryptedCredentials, Callback<String> response);
}
for first method everything works fine, API returns what should return, but, for the second method Retrofit returns error:
com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected a string but was BEGIN_OBJECT at line 1 column 2 path $
Actually now I have no idea what is going on, both methods take the same body, but only one is failing...
handling api-calls:
#Override
// THIS WORKS FINE!
public APIRequest postLoginRequest(final String encryptedCredentials) {
return new APIRequest() {
#Override
protected void fireRequest() {
APIServiece.postLogin(encryptedCredentials, new Callback<String>() {
#Override
public void success(String s, Response response) {
logicEventBus.post(new LoginEvent(null, s));
}
#Override
public void failure(RetrofitError error) {
logicEventBus.post(new LoginEvent(error, null));
}
});
}
};
}
#Override
// THIS DOESNT WORK FINE :(
public APIRequest postLoginDataRequest(final String encryptedCredentials, final AccountData.LoggedBy loggedBy) {
return new APIRequest() {
#Override
protected void fireRequest() {
Callback<String> callback = new Callback<String>() {
#Override
public void success(String s, Response response) {
logicEventBus.post(new LoginDataEvent(null, s));
}
#Override
public void failure(RetrofitError error) {
logicEventBus.post(new LoginDataEvent(error, null));
}
};
switch (loggedBy) {
case INTERNAL:
APIService.postLoginData(encryptedCredentials, callback);
break;
case FACEBOOK:
APIService.postLoginData(encryptedCredentials, callback);
break;
case GOOGLEPLUS:
APIService.postLoginData(encryptedCredentials, callback);
break;
}
}
};
}
I have checked in the debugger, strings are fine.
API also works fine (checked in Postman).
Debugger never get into "success" (in not-working method).
Thanks for any kind of help!

Categories

Resources