Making API call using Retrofit from JobScheduler's onStartJob() - android

I don't know if this is even possible. Please note, that I've been researching on JobScheduler only one day, so I'm sorry If I got something wrong.
So I just created one MainActivity, from it, I directly want to schedule a job, and make it periodical, say every 5 seconds I need to hit my REST endpoint and return a random integer (I got that part in Spring Boot covered).
From what I saw, in JobScheduler's onStartJob(), AsyncTask gets initialized, and its doPostExecute gets defined and method execute() is called on AsyncTask object, something like this:
#Override
public boolean onStartJob(JobParameters params) {
mJobExecutor = new JobExecutor() {
Override
protected void onPostExecute(int x){
Toast.makeText(getApplicationContext(), "Integer returned from backend " + x, Toast.LENGTH_SHORT).show();
}
}
mJobExecutor.execute();
return true;
}
I actually placed a Retrofit call to my backend instead of AsyncTask, like this:
public class MyJobScheduler extends JobService {
private Retrofit mRetrofit = RetrofitClient.getRetrofitInstance();
private MessageService mMessageService = mRetrofit.create(MessageService.class);
private JobParameters params;
#Override
public boolean onStartJob(JobParameters params) {
this.params = params;
Log.i("TAG", "onStartJob: fetching data begins");
fetchData();
return true;
}
#Override
public boolean onStopJob(JobParameters params) {
return false;
}
private void fetchData(){
Call<Integer> call = mMessageService.getData();
call.enqueue(new Callback<Integer>() {
#Override
public void onResponse(Call<Integer> call, Response<Integer> response) {
if (!response.isSuccessful()){
Toast.makeText(getApplicationContext(), "erorr", Toast.LENGTH_SHORT);
return;
}
Toast.makeText(getApplicationContext(), "Integer returned " + response.body(), Toast.LENGTH_SHORT).show();
jobFinished(params, false);
}
#Override
public void onFailure(Call<Integer> call, Throwable t) {
Toast.makeText(getApplicationContext(), "errorrrrrr", Toast.LENGTH_SHORT).show();
return;
}
});
}
}
Back in my MainActivity, I have in onResume this piece of code which checks for result status:
#Override
protected void onResume() {
super.onResume();
int res = mJobScheduler.schedule(mJobInfo);
if (res == JobScheduler.RESULT_SUCCESS){
Log.i("res", "success");
} else Log.i("res", "fail");
}
Finally I get error like this:
E/AndroidRuntime: FATAL EXCEPTION: OkHttp Dispatcher
Process: ftn.sit.jobscheduler, PID: 17710
java.lang.BootstrapMethodError: Exception from call site #1 bootstrap method
at retrofit2.DefaultCallAdapterFactory$ExecutorCallbackCall$1.onResponse(DefaultCallAdapterFactory.java:77)
at retrofit2.OkHttpCall$1.onResponse(OkHttpCall.java:150)
at okhttp3.internal.connection.RealCall$AsyncCall.run(RealCall.kt:504)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
at java.lang.Thread.run(Thread.java:764)
Caused by: java.lang.ClassCastException: Bootstrap method returned null
at retrofit2.DefaultCallAdapterFactory$ExecutorCallbackCall$1.onResponse(DefaultCallAdapterFactory.java:77) 
at retrofit2.OkHttpCall$1.onResponse(OkHttpCall.java:150) 
at okhttp3.internal.connection.RealCall$AsyncCall.run(RealCall.kt:504) 
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167) 
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641) 
at java.lang.Thread.run(Thread.java:764) 
I don't know what is wrong...

Related

Android: Wait for a response by API before continue the execution using Retrofit

I'm facing a problem on my android programming, I have an app that needs run some routines like scan some barcode.
Once that I scanned a barcode I will send the data of this barcode to my API, "wait" to get some information and after that do another things.
The problem it's when I call my API to get the information and after I try to use the information that was supposed to come from the API, I'm falling on a NULL Exception, see the code below:
My scan routine that is called every time I scan a barcode:
// 1 - First Stage: Scan barcode of tank
if(stageScannerTank == 1)
{
// Verify if the user scanned the correct Tank
if (!resultScan.equals(expectedTank)) {
Toast.makeText(getContext(), "Wrong Tank, scan the correct ", Toast.LENGTH_LONG).show();
stageScannerTank = 0;
return;
}
// Get the tank information
//new TanqueTask().execute(resultScan);
**HERE IS MY PROBLEM, When I call the function below, I fill my tankMixPlant with data**
getTankMixPlant(resultScan);
** I always fall into this IF **
// If null information
if(tankMixPlant == null) {
Toast.makeText(getContext(), "Error to read tank information", Toast.LENGTH_LONG).show();
return;
}
}
getTankMixPlant, using Retrofit to call my service, it is working if I test out of my routine:
private void getTankMixPlant(String tankName){
mService.getTankMixPlant(tankName).enqueue(new Callback<TankMixPlant>() {
#Override
public void onResponse(Call<TankMixPlant> call, Response<TankMixPlant> response) {
tankMixPlant = response.body();
}
#Override
public void onFailure(Call<TankMixPlant> call, Throwable t) {
// On failure show the error message to the User
AlertDialog alert;
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setTitle("Error");
builder.setMessage("Error: " + t.getMessage());
alert = builder.create();
alert.show();
}
});
}
How can I wait tankMixPlant be filled up with data to continue my routine?
UPDATES:
I continue with the problem, but I changed some things that you can see below:
Now I get NullPointerException.
Now I verify if my object is null inside the onResponse method:
private void getTankMixPlant(String tankName){
mService.getTankMixPlant(tankName).enqueue(new Callback<TankMixPlant>() {
#Override
public void onResponse(Call<TankMixPlant> call, Response<TankMixPlant> response) {
tankMixPlant = response.body();
if(tankMixPlant == null) {
Toast.makeText(getContext(), "Error to read tank information",
Toast.LENGTH_LONG).show();
return;
}
}
#Override
public void onFailure(Call<TankMixPlant> call, Throwable t) {
// On failure show the error message to the User
AlertDialog alert;
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setTitle("Error");
builder.setMessage("Error: " + t.getMessage());
alert = builder.create();
alert.show();
}
});
}
But when I try to verify some information about tankMixPlant:
// Verify tank stage
if(!tankMixPlant.getBlenderCode().equals("0") && tankMixPlant.getIngr() == 1)
{
getPackages(tankMixPlant.getRecipeNumber(), tankMixPlant.getBlenderCode(), tankMixPlant.getTankName());
// 2 - Mix Packages: Scan package barcode
stageScannerTank = 2;
}
if(!tankMixPlant.getIngrSAPNumber().equals("0") && tankMixPlant.getIngr() == 1)
{
getIngr(tankMixPlant.getRecipeNumber(), tankMixPlant.getTankName());
// 3 - P.I Packages: Scan package barcode
stageScannerTank = 3;
}
My app crashes with this error:
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.labtse.kibonapp, PID: 21519
java.lang.RuntimeException: Failure delivering result ResultInfo{who=null, request=114910, result=-1, data=Intent { act=com.google.zxing.client.android.SCAN flg=0x80000 VirtualScreenParam=Params{mDisplayId=-1, null, mFlags=0x00000000)} (has extras) }} to activity {com.example.labtse.kibonapp/com.example.labtse.kibonapp.TabPagesActivity}: java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.String com.example.labtse.kibonapp.model.TankMixPlant.getBlenderCode()' on a null object reference
at android.app.ActivityThread.deliverResults(ActivityThread.java:5007)
at android.app.ActivityThread.handleSendResult(ActivityThread.java:5050)
at android.app.ActivityThread.access$1600(ActivityThread.java:230)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1876)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:148)
at android.app.ActivityThread.main(ActivityThread.java:7409)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1230)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1120)
Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.String com.example.labtse.kibonapp.model.TankMixPlant.getBlenderCode()' on a null object reference
at com.example.labtse.kibonapp.MixPlantFragment.scanRoutine(MixPlantFragment.java:367)
at com.example.labtse.kibonapp.MixPlantFragment.onActivityResult(MixPlantFragment.java:399)
at android.support.v4.app.FragmentActivity.onActivityResult(FragmentActivity.java:151)
at android.app.Activity.dispatchActivityResult(Activity.java:7165)
at android.app.ActivityThread.deliverResults(ActivityThread.java:5003)
at android.app.ActivityThread.handleSendResult(ActivityThread.java:5050) 
at android.app.ActivityThread.access$1600(ActivityThread.java:230) 
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1876) 
at android.os.Handler.dispatchMessage(Handler.java:102) 
at android.os.Looper.loop(Looper.java:148) 
at android.app.ActivityThread.main(ActivityThread.java:7409) 
at java.lang.reflect.Method.invoke(Native Method) 
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1230) 
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1120) 
My tankMixPlant class:
package com.example.labtse.kibonapp.model;
public class TankMixPlant {
private String tankName;
private String recipeNumber;
private int idBatch;
private String ingrSAPNumber;
private String blenderCode;
private short ingr;
private boolean QRUN;
private boolean QEND;
private Boolean QDOSANDO;
private Boolean CONF_ING;
public TankMixPlant() {
}
public String getTankName() {
return tankName;
}
public void setTankName(String tankName) {
this.tankName = tankName;
}
public String getRecipeNumber() {
return recipeNumber;
}
public void setRecipeNumber(String recipeNumber) {
this.recipeNumber = recipeNumber;
}
public int getIdBatch() {
return idBatch;
}
public void setIdBatch(int idBatch) {
this.idBatch = idBatch;
}
public String getIngrSAPNumber() {
return ingrSAPNumber;
}
public void setIngrSAPNumber(String ingrSAPNumber) {
this.ingrSAPNumber = ingrSAPNumber;
}
public String getBlenderCode() {
return blenderCode;
}
public void setBlenderCode(String blenderCode) {
this.blenderCode = blenderCode;
}
public short getIngr() {
return ingr;
}
public void setIngr(short ingr) {
this.ingr = ingr;
}
public boolean isQRUN() {
return QRUN;
}
public void setQRUN(boolean QRUN) {
this.QRUN = QRUN;
}
public boolean isQEND() {
return QEND;
}
public void setQEND(boolean QEND) {
this.QEND = QEND;
}
public Boolean getQDOSANDO() {
return QDOSANDO;
}
public void setQDOSANDO(Boolean QDOSANDO) {
this.QDOSANDO = QDOSANDO;
}
public Boolean getCONF_ING() {
return CONF_ING;
}
public void setCONF_ING(Boolean CONF_ING) {
this.CONF_ING = CONF_ING;
}
}
Solution:
You must write this:
if(tankMixPlant == null) {
Toast.makeText(getContext(), "Error to read tank information", Toast.LENGTH_LONG).show();
return;
}
Inside your onResponse, after the line tankplant = ...
Now, null exception will be eliminated if you are getting any data from the server.
Hope it helps.
Update1:
Write this:
if(!tankMixPlant.getBlenderCode().equals("0") && tankMixPlant.getIngr() == 1)
{
getPackages(tankMixPlant.getRecipeNumber(), tankMixPlant.getBlenderCode(), tankMixPlant.getTankName());
// 2 - Mix Packages: Scan package barcode
stageScannerTank = 2;
}
if(!tankMixPlant.getIngrSAPNumber().equals("0") && tankMixPlant.getIngr() == 1)
{
getIngr(tankMixPlant.getRecipeNumber(), tankMixPlant.getTankName());
// 3 - P.I Packages: Scan package barcode
stageScannerTank = 3;
}
in your onResponse method as shown below:
private void getTankMixPlant(String tankName){
mService.getTankMixPlant(tankName).enqueue(new Callback<TankMixPlant>() {
#Override
public void onResponse(Call<TankMixPlant> call, Response<TankMixPlant> response) {
tankMixPlant = response.body();
if(tankMixPlant == null) {
Toast.makeText(getContext(), "Error to read tank information",
Toast.LENGTH_LONG).show();
return;
}
...... (Write Here)
}
#Override
public void onFailure(Call<TankMixPlant> call, Throwable t) {
// On failure show the error message to the User
AlertDialog alert;
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setTitle("Error");
builder.setMessage("Error: " + t.getMessage());
alert = builder.create();
alert.show();
}
});
Try it.
The problem is that the retrofit call is done asynchronously, while the rest of your code isn't. That's why your data is null, since it's nor ready yet.
First of all, you have to remove the part of the code where you're checking for the "pending data", and move it inside your retrofit onResponse or somewhere else that'll be later called after the network response by using callbacks.
There are quite a few ways on exactly how you can do that. I'd suggest that you read the documentation and some tutorials like this one, and this other one.
Also, while you're making the retrofit call, it's best to display a loading message to the user, to keep him aware that you're making a network call and is waiting for the results.

RxJava thread not waiting for result

I have this method that I am trying to pull data from an API, and then update the text view. Everything works except getRecipeName doesn't finish after the "end Method" log. .getRecipeName() uses RetroFit to pull from an API.
I am currently learning MVP, Dagger, RxJava, and Butterknife all at once using
Mindork's Github page on MVP Architecture
I commented out the .subscribeOn and .observeOn to see the result difference and nothing changed.
#Override
public void onRandomButtonClicked() {
getMvpView().showLoading();
Log.e(TAG, "Random Method Open");
getCompositeDisposable().add(getDataManager()
.getRecipeName()
//.subscribeOn(getSchedulerProvider().io())
//.observeOn(getSchedulerProvider().ui())
.subscribe(new Consumer<String>() {
#Override
public void accept(String s) throws Exception {
Log.e(TAG, "accept");
getMvpView().updateTextView(title);
}
}));
Log.e(TAG, "end method");
}
Here is my getRecipeName() method
#Override
public Observable<String> getRecipeName() {
/*Create handle for the RetrofitInstance interface*/
GetDataService service = RetrofitClientInstance.getRetrofitInstance().create(GetDataService.class);
Call<RecipeList> call = service.getRecipe();
call.enqueue(new Callback<RecipeList>() {
#Override
public void onResponse(#NonNull Call<RecipeList> call, #NonNull retrofit2.Response<RecipeList> response) {
Log.e("onResponse","Recipe is Successful = " + response.isSuccessful());
//if response is false then skip to avoid null object reference
if (response.isSuccessful()) {
RecipeList drinkRecipe = response.body();
List<Recipe> recipes = drinkRecipe.getDrinks();
jokeText = String.valueOf(recipes.size());
Recipe myRecipe = recipes.get(0);
jokeText = myRecipe.getStrDrink();
Log.e("On Response", "Result2: " + jokeText);
}
//jokeText = "null";
}
#Override
public void onFailure(Call<RecipeList> call, Throwable t) {
Log.e("On Response","Failure");
}
});
//return jokeText;
return Observable.fromCallable(new Callable<String>() {
#Override
public String call() throws Exception {
return jokeText;
}
});
}
Solution
So as the comments stated RxJava Adapter was the correct way to go. I will just post my working code on myself using the adapter. I found it very difficult to find a working example.
//single api call using retrofit and rxjava
#SuppressLint("CheckResult")
private void getRandomButtonClick(){
retrofit = RetrofitClientInstance.getRetrofitInstance();
retrofit.create(GetDataService.class).getRecipe()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(this::handleResults, this::handleError );
}
private void handleResults(RecipeList recipeList) {
int i = recipeList.getDrinks().size();
Log.e(TAG, "size is: "+ i);
Recipe recipe = recipeList.getDrinks().get(0);
getMvpView().updateTextView(recipe.getStrDrink());
}
private void handleError(Throwable t){
Log.e("Observer", "");
}
My Retrofit Client Instance
public static Retrofit getRetrofitInstance() {
if (retrofit == null) {
retrofit = new retrofit2.Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();
}
return retrofit;
}
My Interface
public interface GetDataService {
//#Headers({})
#GET("random.php")
Observable<RecipeList> getRecipe();
I found a great resource to reference for me to correctly implement this. Retrofit Android
The reason is because your observable is returning jokeText every time it is subscribed upon. It returns immediately after invocation and will not wait for your network operation.
One possible solution is to use the RxJavaCallAdapter. Link here: https://github.com/square/retrofit/tree/master/retrofit-adapters/rxjava2
It will automatically convert your API returns to observables. No need to manually invoke retrofit requests. Just process the response and convert it to your desired object from there.
Another approach would be to wrap your entire sequence in an Observable.create or Observable.fromAsync.

Android Retrofit 2 wait on multiple requests

I am building an activity in which I'm loading lists of objects from an api. I need to make multiple requests with retrofit which returns different objects. I can make the requests but I don't know how I can check when they're done.
The following code is what I have.
ApiRepository
public interface ApiRepository {
#GET("/api/troopmarker.json")
Call<List<TroopMarker>> getTroopMarkers();
#GET("/api/troop.json")
Call<List<Troop>> getTroops();
#GET("/api/treasure.json")
Call<List<TroopMarker>> getTreasures();
}
RepositoryService
public interface RepositoryService
{
void loadTroops(final TroopCallback callback);
void loadTroopMarkers(final TroopMarkerCallback callback);
//void loadTreasures(final TreasureCallback callback);
}
RepositoryServiceImpl
public class RepositoryServiceImpl implements RepositoryService {
private String url;
private Activity context;
public RepositoryServiceImpl(String url, Activity context) {
this.url = url;
this.context = context;
}
public void loadTroops(final TroopCallback callback) {
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(url)
.addConverterFactory(GsonConverterFactory.create())
.build();
ApiRepository repository = retrofit.create(ApiRepository.class);
repository.getTroops().enqueue(new Callback<List<Troop>>() {
public List<Troop> troops;
#Override
public void onResponse(Call<List<Troop>> call, Response<List<Troop>> response) {
if(response.isSuccessful()) {
Log.d("RETROFIT", "RESPONSE " + response.body().size());
callback.onSuccess(response.body());
}
}
#Override
public void onFailure(Call<List<Troop>> call, Throwable t) {
CharSequence text = "Error loading troops.";
int duration = Toast.LENGTH_LONG;
Toast toast = Toast.makeText(context, text, duration);
toast.show();
callback.onSuccess(null);
}
});
}
public void loadTroopMarkers(final TroopMarkerCallback callback) {
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(url)
.addConverterFactory(GsonConverterFactory.create())
.build();
ApiRepository repository = retrofit.create(ApiRepository.class);
repository.getTroopMarkers().enqueue(new Callback<List<TroopMarker>>() {
#Override
public void onResponse(Call<List<TroopMarker>> call, Response<List<TroopMarker>> response) {
if(response.isSuccessful()) {
Log.d("RETROFIT", "RESPONSE " + response.body().size());
callback.onSuccess(response.body());
}
}
#Override
public void onFailure(Call<List<TroopMarker>> call, Throwable t) {
CharSequence text = "Error loading troops.";
int duration = Toast.LENGTH_LONG;
Toast toast = Toast.makeText(context, text, duration);
toast.show();
callback.onSuccess(null);
}
});
}
public void loadTreasures() {
}
}
LoadActivity
public class LoadActivity extends AppCompatActivity
{
//TODO LOAD TROOPS AND TROOPMARKERS
//Load troops, troopmarkers, treasures and put on map
public List<Troop> troops;
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_loading);
//Start RepositoryService
final RepositoryService repositoryService = new RepositoryServiceImpl("http://internco.eu", this);
//Load troops
repositoryService.loadTroops(new TroopCallback() {
#Override
public void onSuccess(List<Troop> troops) {
Log.d("RETROFIT", "SUCCESFULLY LOADED TROOPS SIZE: " + troops.size());
}
});
//Load troopMarkers
repositoryService.loadTroopMarkers(new TroopMarkerCallback() {
public List<TroopMarker> troopMarkers;
#Override
public void onSuccess(List<TroopMarker> troopMarkers) {
Log.d("RETROFIT", "SUCCESFULLY LOADED TROOPMARKERS SIZE: " + troopMarkers.size());
}
});
//Should now here when I'm done with my requests.
Log.d("RETROFIT", "DONE");
}
}
Can someone point me out on this? I think that I have to use the RxJava library but I can't figure this out.
Your help is much appreciated.
1 hacky way of doing it would be to keep 2 flag variables loadTroopsflag & loadTroopMarkersflag.Then in the onSuccess callbacks of each check whether both are true and if they are then both your requests are complete. There might be edge cases in implementing a workaround like this but it should generally work. In case your requests depend on each other then as you will need to use nested called ie,
repositoryService.loadTroops(new TroopCallback() {
#Override
public void onSuccess(List<Troop> troops) {
Log.d("RETROFIT", "SUCCESFULLY LOADED TROOPS SIZE: " + troops.size());
repositoryService.loadTroopMarkers(new TroopMarkerCallback() {
public List<TroopMarker> troopMarkers;
#Override
public void onSuccess(List<TroopMarker> troopMarkers) {
Log.d("RETROFIT", "SUCCESFULLY LOADED TROOPMARKERS SIZE: " + troopMarkers.size());
}
});
}
});
Something like that,so in case you have more dependencies then your nested callbacks increase which is where Rxjava would come in and solve it in a few lines of code.I don't think you need to jump into Rx just yet as this is a relatively small problem and you Rx java brings in extra space that would increase the size of the app as well as development time.
Also note the part where you mention
//Should now here when I'm done with my requests.
Log.d("RETROFIT", "DONE");
does not imply that the requests are done,it simply means that they are queued up and in progress.These are asynchronous request and will complete when the callback completes.

boolean flag is always false

I am trying to interact with an API and see whether the user exists on that API by checking the JSON Response by comparing current email (stored in SharedPrefs) with the emails returned from API. If the user exist, a flag is set true so that the app doesn't send a POST request to save the new user and if it is false, the user gets saved in API.
So, this is the UEC (UserExistenceChecker) class
public class UEC extends AppCompatActivity {
List<SavePlace> userInfo;
String name;
boolean flag;
SharedPreferences sharedPref;
public UEC(SharedPreferences sharedPref){
this.sharedPref = sharedPref;
}
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
public boolean checkIfUserExists() {
email = sharedPref.getString("userEmail", "");
Retrofit retrofitCheckUser = ApiClientSavePlace.getClient();
ApiInterfaceSavePlace apiInterfaceSavePlace = retrofitCheckUser.create(ApiInterfaceSavePlace.class);
final Call<List<SavePlace>> checkUser = apiInterfaceSavePlace.getSavePlaces();
checkUser.enqueue(new Callback<List<SavePlace>>() {
#Override
public void onResponse(Call<List<SavePlace>> call, Response<List<SavePlace>> response) {
userInfo = response.body();
try {
if(userInfo.size()!=0){
for (int i = 0; i <= userInfo.size(); i++) {
String emailReturned = userInfo.get(i).getEmail();
Log.d("response", "email returned: " + emailReturned);
Log.d("sharedpref", "email: " + email);
if (emailReturned.equals(email)) {
Log.d("response:", "email match?: " + emailReturned.equals(email));
flag = true;
SharedPreferences.Editor editor = sharedPref.edit();
editor.putInt("userID", userInfo.get(i).getId());
Log.d("ID returned", String.valueOf(userInfo.get(i).getId()));
editor.apply();
break;
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
#Override
public void onFailure(Call<List<SavePlace>> call, Throwable throwable) {
Log.d("RESPONSE", "FAILED CHECKING USER ID/SOMETHING HAPPENED");
}
});
return flag;
}
}
In this class, I have made a boolean flag with default value false
and this is how I call the method checkIfUserExists() from MainActivity.java
public class MainActivity{
SharedPreferences sharedPref =
PreferenceManager.getDefaultSharedPreferences(getBaseContext());
UEC uec = new UEC(sharedPref);
boolean userExists = uec.checkIfUserExists();
if (userExists) {
Log.d("USERSTATUS", String.valueOf(sharedPref.getInt("userID", 0)));
} else {
Log.d("USERSTATUS", "FALSE:DOESNT EXIST");
Log.d("USERSTATUS", String.valueOf(sharedPref.getInt("userID", 0)));
}
}
Now, the problem is that, according to the logs, else condition is always true because the flag is always false even though I am setting it true in checkIfUserExists() method.
and the interesting thing about logs is that this
05-13 15:27:54.278 1613-1613/xyz.gautamhans.locus D/USERSTATUS: FALSE:DOESNT
EXIST
05-13 15:27:54.278 1613-1613/xyz.gautamhans.locus D/USERSTATUS: 12
comes first and then this comes in logs after above log
05-13 15:27:55.746 1613-1613/xyz.gautamhans.locus D/response: email
returned: some-email#gmail.com
05-13 15:27:55.749 1613-1613/xyz.gautamhans.locus D/sharedpref: email:
some-email#gmail.com
05-13 15:27:55.749 1613-1613/xyz.gautamhans.locus D/response: email
returned: some-email#gmail.com
05-13 15:27:55.749 1613-1613/xyz.gautamhans.locus D/sharedpref: email:
some-email#gmail.com
05-13 15:27:55.749 1613-1613/xyz.gautamhans.locus D/response: email match?:
true
05-13 15:27:55.749 1613-1613/xyz.gautamhans.locus D/ID returned: 12
which means that it detected the email and set the sharedpref
but the flag is still false.
From a quick look at your code it looks like that the enqueue method is causing for the boolean to only be changed to true after you call the checkIfUserExists() method.
This is what you're seeing in your logs, due to the asynchronous nature of the enqueue method, all the code within your onResponse() and onFailure() is only executed after everything else in a background thread.
To help avoid this you could implement a callback method so that whenever the onResponse() method is finished you call the method to check if the user exists. In the code below the callback method is onUserExists() which replaces the true boolean flag and I've also included an else statement if the user doesn't exist which will trigger a second callback, the onUserDoesNotExist() method. These callback methods will trigger the code in the MainActivity within the onUserExists() and onUserDoesNotExist() methods there.
public void checkIfUserExists(OnUserExistsCallback onUserExistsCallback) {
email = sharedPref.getString("userEmail", "");
Retrofit retrofitCheckUser = ApiClientSavePlace.getClient();
ApiInterfaceSavePlace apiInterfaceSavePlace = retrofitCheckUser.create(ApiInterfaceSavePlace.class);
final Call<List<SavePlace>> checkUser = apiInterfaceSavePlace.getSavePlaces();
OnUserExistsCallback callback = onUserExistsCallback;
checkUser.enqueue(new Callback<List<SavePlace>>() {
#Override
public void onResponse(Call<List<SavePlace>> call, Response<List<SavePlace>> response) {
userInfo = response.body();
try {
if(userInfo.size()!=0){
for (int i = 0; i <= userInfo.size(); i++) {
String emailReturned = userInfo.get(i).getEmail();
Log.d("response", "email returned: " + emailReturned);
Log.d("sharedpref", "email: " + email);
if (emailReturned.equals(email)) {
Log.d("response:", "email match?: " + emailReturned.equals(email));
SharedPreferences.Editor editor = sharedPref.edit();
editor.putInt("userID", userInfo.get(i).getId());
Log.d("ID returned", String.valueOf(userInfo.get(i).getId()));
editor.apply();
callback.onUserExists();
break;
} else {
callback.onUserDoesNotExist();
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
#Override
public void onFailure(Call<List<SavePlace>> call, Throwable throwable) {
Log.d("RESPONSE", "FAILED CHECKING USER ID/SOMETHING HAPPENED");
}
});
return flag;
}
For this to work you need to create the OnUserExistsCallback interface.
public interface OnUserExistsCallback {
void onUserExists();
void onUserDoesNotExist();
}
Finally this is how your MainActivity would now look.
public class MainActivity {
SharedPreferences sharedPref =
PreferenceManager.getDefaultSharedPreferences(getBaseContext());
UEC uec = new UEC(sharedPref);
uec.checkIfUserExists(new OnUserExistsCallback() {
#Override
public void onUserExists() {
Log.d("USERSTATUS", String.valueOf(sharedPref.getInt("userID", 0)));
}
#Override
public void onUserDoesNotExist() {
Log.d("USERSTATUS", "FALSE:DOESNT EXIST");
Log.d("USERSTATUS", String.valueOf(sharedPref.getInt("userID", 0)));
}
);
}
Not sure if this will compile and run successfully as I haven't ran this code myself. Hopefully it will and that it will solve your problem.
The behaviour is due to Asynchronous execution of checkUser.enqueue(new Callback<List<SavePlace>>(). So when you call this this method from checkIfUserExists(), your execution thread will not wait for checkUser.enqueue() to complete. Instead it will immediately go to the next line and return the current flag value which is false. checkUser.enqueue() will be executed in a background thread and you get the result in onResponse() method. The behaviour is correct as per your code. Please try to handle the scenarios asynchronously as that is the recommended approach for a Network call.
The problem is that you are dealing with asynchronous function call when you call this method
uec.checkIfUserExists();
the code inside this function is executed in normal way until the line where you make your api call here
final Call<List<SavePlace>> checkUser = apiInterfaceSavePlace.getSavePlaces();
which makes a web api call in background thread and your function executes in normal way and returns which means you will get flag as false value.
and when the background task is finished then the code inside
public void onResponse()
method is executed which is a callback method and you get your values with true flag.
Solution
You should wait for your api call to complete and then perform any check on user exist or not.
So one simple way is to put your user exist check inside onResponse() callback method itself.
And if you want to handle it in your activity or fragment you can create your own callback method and pass it to checkIfUserExists();
Something like this
public interface MyInterface{
public void onCallback(boolean isUserExists);
}
and in your activity
uec.checkIfUserExists(
new MyInterface(){
#Override
public void onCallback(boolean isUserExists){
if (isUserExists) {
//your code
}
else{
//your code
}
}
}
);
Make changes to you checkIfUserExists() method like this
public void checkIfUserExists(final MyInterface myInterface) {
//your code
checkUser.enqueue(new Callback<List<SavePlace>>(final MyInterface myInterface) {
#Override
public void onResponse(Call<List<SavePlace>> call, Response<List<SavePlace>> response) {
userInfo = response.body();
try {
//your code
if (emailReturned.equals(email)) {
flag = true;
}
//pass your flag to callback method here.
myInterface.onCallback(flag);
}
} catch (Exception e) {
e.printStackTrace();
}
}
#Override
public void onFailure(Call<List<SavePlace>> call, Throwable throwable) {
//Handle failure
}
});
}

Race condition on boolean written by callbacks and read within the activity

From an Android activity I'm sending http requests driven by the user pressing buttons on the UI.
I don't want multiple requests running at the same time (OutlookClient crashes).
My question is: is it possible to have race conditions due to the callbacks with the results writing the same boolean (using runOnUiTread) that is read before sending a new request?
Thanks
// Should this be either "volatile" or atomic ??
private boolean isThereAPendingRequest = false;
#Override
protected void onCreate(Bundle savedInstanceState) {
genericClient = clientInitializer.create(this);
// ...
isThereAPendingRequest = true; // still have to login
Futures.addCallback(genericClient.logon(this, scopes), new FutureCallback<Boolean>() {
#Override
public void onSuccess(Boolean result) {
// ...
isThereAPendingRequest = false;
}
#Override
public void onFailure(#NonNull Throwable t) {
// ...
isThereAPendingRequest = false;
}
});
// ...
}
// ...
public void getBookings(View view){
if(isThereAPendingRequest){
Toast.makeText(getApplicationContext(), "There's already a pending request. Try in a few seconds.", Toast.LENGTH_LONG).show();
return;
}
isThereAPendingRequest = true;
Futures.addCallback( genericClient.getCalendarEvents(), new FutureCallback<List<List>>(){
#Override
public void onSuccess(final List<List> resultCalendars) {
Log.d("APP", "Success. Result: "+resultCalendars);
runOnUiThread(new Runnable() {
#Override
public void run() {
// ..
isThereAPendingRequest = false;
}
}
}
// ..
}
public void sendBooking(View view){
if(isThereAPendingRequest){
Toast.makeText(getApplicationContext(), "There's already a pending request. Try in a few seconds.", Toast.LENGTH_LONG).show();
return;
}
isThereAPendingRequest = true;
Futures.addCallback( genericClient.sendBooking( booker, title), new FutureCallback<List<String>>(){
#Override
public void onSuccess(final List<String> resultBooking) {
Log.d("APP", "Success. Result: "+resultBooking);
runOnUiThread(new Runnable() {
#Override
public void run() {
// ...
isThereAPendingRequest = false;
}
});
}
#Override
public void onFailure(Throwable t) {
Log.e( "APP", "Delete error. Cause: "+t.getLocalizedMessage() );
// ...
Toast.makeText(getApplicationContext(), "Fail!", Toast.LENGTH_LONG).show();
isThereAPendingRequest = false;
}
});
}catch(Exception ex){
// logger
isThereAPendingRequest = false;
}
}
UPDATE: this is one of the function called in the Futures..
public ListenableFuture<List<List>> getCalendarEvents(){
// logger
final SettableFuture<List<List>> future = SettableFuture.create();
DateTime now = new DateTime(DateTimeZone.UTC);
DateTime workDayEnd = new DateTime( now.getYear(), now.getMonthOfYear(), now.getDayOfMonth(), 23, 59, 0 );
Futures.addCallback(
mClient .getMe()
.getCalendarView()
.addParameter("startDateTime", now)
.addParameter("endDateTime", workDayEnd)
.read(),
new FutureCallback<OrcList<Event>>() {
#Override
public void onSuccess(final OrcList<Event> result) {
// ...
future.set(myList);
}
#Override
public void onFailure(#NonNull Throwable t) {
// ...
future.setException(t);
}
}
);
return future;
}
If getBookings and setBookings are both invoked on the UI thread all the time, you should be fine. You know that by the time isThereAPendingRequest is set to false, the request must have already completed and therefore you are safe to go. By the way, Futures.addCallback has an alternate signature that allows you to explicitly pass in an Executor, so if you use that you don't need to call runOnUiThread which reduces some code nesting.
However, if you intend to invoke these methods concurrently, I see at least one race condition that requires locks to prevent. More details on that if you're interested.
Edit for completeness:
The question states that your goal is to prevent two requests from running at the same time. The are two cases where that can happen:
isThereAPendingRequest==false, but there is actually a pending request. Your code so far is safe from this, since you only set it to false after the request has been completed. You don't even need volatile here.
getBookings and/or setBookings are called on different threads. What happens if they both reach if(isThereAPendingRequest) at the same time? They can simultaneously (and correctly) see that it is false, set it to true, then both independently send a request and cause you to crash.
You don't need to worry about (1), and (2) should not be a problem as long as you always invoke those methods on the same thread.

Categories

Resources