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

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.

Related

How to perform long running Databse operation using RxJava2 till all the task executed and data inserted into Database in Android?

I'm new in RxJava. I have currently executed three API calls parallel which is independent of each other via Retrofit using Single.Zip Operator. On getting a successful response of all three API calls, I have to insert the data from all three APIs into Room database into Different entities which takes 20 seconds.
So I need to execute database operations inside Single.Zip operator. Because the logic is written inside onSuccess method running away before Database Operation performed.
I have tried to take separate Observer for performing database operation but didn't work.
public void callOfflineDataAPIs() {
setIsLoading(true);
Single<BaseResponse<ProductResponse>> single1 = getDataManager().getOfflineProductListApiCall(getDataManager().getLastTimeStampOfflineProductCall()).subscribeOn(getSchedulerProvider().io()).observeOn(getSchedulerProvider().ui());
Single<BaseResponse<LocationResponse>> single2 = getDataManager().getOfflineLocationListApiCall(getDataManager().getLastTimeStampOfflineLocationCall()).subscribeOn(getSchedulerProvider().io()).observeOn(getSchedulerProvider().ui());
Single<BaseResponse<OfflineMasterData>> single3 = getDataManager().getOfflineMasterDataListApiCall(getDataManager().getLastTimeStampOfflineMasterCall()).subscribeOn(getSchedulerProvider().io()).observeOn(getSchedulerProvider().ui());
DisposableSingleObserver<List<Boolean>> result = Single.zip(single3, single1, single2,
(offlineMasterDataBaseResponse, productResponseBaseResponse, locationResponseBaseResponse) -> {
List<Boolean> apiCalls = new ArrayList<>();
apiCalls.add(masterDataCRUDOperation(offlineMasterDataBaseResponse));
apiCalls.add(productDataCRUDOperation(productResponseBaseResponse));
apiCalls.add(locationDataCRUDOperation(locationResponseBaseResponse));
return apiCalls;
}).subscribeOn(getSchedulerProvider().io()).observeOn(getSchedulerProvider().ui()).subscribeWith(new DisposableSingleObserver<List<Boolean>>() {
#Override
public void onSuccess(List<Boolean> apiCalls) {
setIsLoading(false);
LogHelper.e(TAG, "DisposableSingleObserver- onSuccess");
boolean isSync = true;
for (int i = 0; i < apiCalls.size(); i++) {
if (!apiCalls.get(i)) {
isSync = false;
LogHelper.e(TAG, "DisposableSingleObserver- onSuccess- apiCalls.get(i)", i);
callOfflineDataAPIs();
break;
}
}
if (isSync) {
LogHelper.e(TAG, "IF-isSync");
if (BuildConfig.IS_CLIENT_BUILD) {
LogHelper.e(TAG, "IF-isSync-IS_CLIENT_BUILD-true");
getDataManager().setCurrentWarehouseKey(1);
getNavigator().onGoButtonClick();
} else {
LogHelper.e(TAG, "ELSE-isSync-IS_CLIENT_BUILD-false");
getWarehouseList();
}
}
}
#Override
public void onError(Throwable e) {
LogHelper.e(TAG, "DisposableSingleObserver- Throwable");
setIsLoading(false);
String errorMessage = new NetworkError(e).getAppErrorMessage();
getNavigator().exitApplicationOnError(errorMessage);
}
});
}
Logic written inside onSuccess Method execute once all DB Operation performed.
You can modify your code to something like:
DisposableSingleObserver<List<Boolean>> result = Single.zip(single3, single1, single2,
(offlineMasterDataBaseResponse, productResponseBaseResponse, locationResponseBaseResponse) -> {
List<Boolean> apiCalls = new ArrayList<>();
apiCalls.add(masterDataCRUDOperation(offlineMasterDataBaseResponse));
apiCalls.add(productDataCRUDOperation(productResponseBaseResponse));
apiCalls.add(locationDataCRUDOperation(locationResponseBaseResponse));
return apiCalls;
}).subscribeOn(getSchedulerProvider().io())
.map(new Function<List<Boolean> apiCalls, List<Boolean> apiCalls>() {
#Override
public List<Boolean> apiCalls apply(List<Boolean> apiCalls) throws Exception {
// perform database operations here
return apiCalls;
}
})
.observeOn(getSchedulerProvider().ui())
.subscribe(new Observer<List<Boolean>>() {
#Override
public void onNext(User user) {
// Do something
}
#Override
public void onError(Throwable e) {
// Do something
}
#Override
public void onComplete() {
// Do something
}
});

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
}
});
}

What's a good way to asynchronously update the progress of ProgressDialog from background thread?

I want to have a Splash screen that has an inderteminate ProgressDialog and its progress gets updated by async calls from within a Presenter class (from MVP architecture).
I have a number of API calls to make to my BaaS server and for every successfull call, I would like to update the progress bar.
What's the best way to accomplish this?
I have been trying using EventBus to send notifications to my SplashActivity but it seems that all the API calls are first completed and only then the bus notifications are getting consumed and updating the UI.
What I have done so far is:
SplashActivity:
#Subscribe(threadMode = ThreadMode.MAIN)
public void onProgressBar(String event) {
Timber.d("onProgressBar");
if(event.contains("Done")) {
roundCornerProgressBar.setProgress(100);
} else {
roundCornerProgressBar.setProgress(roundCornerProgressBar.getProgress() + 10);
}
textViewTips.setText(event);
}
Presenter:
InstanceID iid = InstanceID.getInstance(ctx);
String id = iid.getId();
mDataManager.getPreferencesHelper().putInstanceId(id);
GSUtil.instance().deviceAuthentication(id, "android", mDataManager);
GSUtil.instance().getPropertySetRequest("PRTSET", mDataManager);
GSUtil:
public void deviceAuthentication(String deviceId, String deviceOS, final DataManager mDataManager) {
gs.getRequestBuilder().createDeviceAuthenticationRequest()
.setDeviceId(deviceId)
.setDeviceOS(deviceOS)
.send(new GSEventConsumer<GSResponseBuilder.AuthenticationResponse>() {
#Override
public void onEvent(GSResponseBuilder.AuthenticationResponse authenticationResponse) {
if(mDataManager != null) {
mDataManager.getPreferencesHelper().putGameSparksUserId(authenticationResponse.getUserId());
}
EventBus.getDefault().post("Reading player data");
}
});
}
public void getPropertySetRequest(String propertySetShortCode, final DataManager mDataManager) {
gs.getRequestBuilder().createGetPropertySetRequest()
.setPropertySetShortCode(propertySetShortCode)
.send(new GSEventConsumer<GSResponseBuilder.GetPropertySetResponse>() {
#Override
public void onEvent(GSResponseBuilder.GetPropertySetResponse getPropertySetResponse) {
GSData propertySet = getPropertySetResponse.getPropertySet();
GSData scriptData = getPropertySetResponse.getScriptData();
try {
JSONObject jObject = new JSONObject(propertySet.getAttribute("max_tickets").toString());
mDataManager.getPreferencesHelper().putGameDataMaxTickets(jObject.getInt("max_tickets"));
jObject = new JSONObject(propertySet.getAttribute("tickets_refresh_time").toString());
mDataManager.getPreferencesHelper().putGameDataTicketsRefreshTime(jObject.getLong("refresh_time"));
} catch (JSONException e) {
e.printStackTrace();
}
EventBus.getDefault().post("Game data ready");
EventBus.getDefault().post("Done!");
}
});
}
Right now I am just showing you 2 API calls, but I will need another 2.
Thank you
I found the answer! It's easier that I thought, which is unfortunate as I spend about 4 hours on this:
First, I created two new methods on my MVPView interface:
public interface SplashMvpView extends MvpView {
void updateProgressBarWithTips(float prog, String tip);
void gameDataLoaded();
}
Then, in the presenter itself, I call every API call and for every call, I update the View with the updateProgressBarWithTips method and when everything is completed, I finalise it so I can move from Splash screen to Main screen:
private void doGSData(String id) {
getMvpView().updateProgressBarWithTips(10, "Synced player data");
GSAndroidPlatform.gs().getRequestBuilder().createDeviceAuthenticationRequest()
.setDeviceId(id)
.setDeviceOS("android")
.send(new GSEventConsumer<GSResponseBuilder.AuthenticationResponse>() {
#Override
public void onEvent(GSResponseBuilder.AuthenticationResponse authenticationResponse) {
if(mDataManager != null) {
mDataManager.getPreferencesHelper().putGameSparksUserId(authenticationResponse.getUserId());
}
getMvpView().updateProgressBarWithTips(10, "Synced game data");
GSAndroidPlatform.gs().getRequestBuilder().createGetPropertySetRequest()
.setPropertySetShortCode("PRTSET")
.send(new GSEventConsumer<GSResponseBuilder.GetPropertySetResponse>() {
#Override
public void onEvent(GSResponseBuilder.GetPropertySetResponse getPropertySetResponse) {
GSData propertySet = getPropertySetResponse.getPropertySet();
GSData scriptData = getPropertySetResponse.getScriptData();
try {
JSONObject jObject = new JSONObject(propertySet.getAttribute("max_tickets").toString());
mDataManager.getPreferencesHelper().putGameDataMaxTickets(jObject.getInt("max_tickets"));
jObject = new JSONObject(propertySet.getAttribute("tickets_refresh_time").toString());
mDataManager.getPreferencesHelper().putGameDataTicketsRefreshTime(jObject.getLong("refresh_time"));
} catch (JSONException e) {
e.printStackTrace();
}
getMvpView().gameDataLoaded();
}
});
}
});
}
I hope this helps someone, if you're using MVP architecture.
Cheers

Second time running the thread makes application crash

I use a worker thread to read text from a url. My thread is as follow. In the first time running, I am sure thread running is finished as I can check sdcard_readstr is null.
In the second time running, when I call thread_download.start();, then the program crashed.
What could be wrong? Thanks
public class DownloadingThread extends AbstractDataDownloading {
#Override
public void doRun() {
// TODO Auto-generated method stub
try {
// Create a URL for the desired page
URL url = new URL(SDcard_DetailView.textfileurl);
// Read all the text returned by the server
BufferedReader in = new BufferedReader(new
InputStreamReader(url.openStream()));
do{
sdcard_readstr = in.readLine();
}while(sdcard_readstr!=null);
in.close();
} catch (MalformedURLException e) {
} catch (IOException e) {
}
}
}
public abstract class AbstractDataDownloading extends Thread{
private final Set<ThreadCompleteListener> listeners
= new CopyOnWriteArraySet<ThreadCompleteListener>();
public final void addListener(final ThreadCompleteListener listener) {
listeners.add(listener);
}
public final void removeListener(final ThreadCompleteListener listener) {
listeners.remove(listener);
}
private final void notifyListeners() {
for (ThreadCompleteListener listener : listeners) {
listener.notifyOfThreadComplete(this);
}
}
#Override
public final void run() {
try {
doRun();
} finally {
notifyListeners();
}
}
public abstract void doRun();
}
EDIT1:
In my thread complete notification, I use runOnUiThreadto use the UI components.
Is that causing problem?
public void notifyOfThreadComplete(Thread thread) {
// TODO Auto-generated method stub
if(downloadingStopbuttonispressed == false){//background process completed
textfileurl = null;
this.runOnUiThread(new Runnable() {
public void run() {
Wifibutton = (Button) findViewById(R.id.Wifiscanning);
Wifibutton.setText("Load another day's data");
final MenuItem refreshItem = optionsMenu.findItem(R.id.airport_menuRefresh);
refreshItem.setActionView(null);
}
});
}
}
I called thread start in onResume() as
#Override
protected void onResume() {
super.onResume();
if(textfileurl != null){
Wifibutton.setText("Stop Data Loading");
buttonStatus = "loading";
setRefreshActionButtonState(true);
thread_download.start();
}
}
EDIT2:
My LogCat image is attached.
My solution is here . I can't reuse the same instance of the Thread object in the second time. I need to create a new instance to call the Thread in the second time. So Thread is suitable for single time running process, for multiple time running process I should use AsyncTask. Even AsyncTack is only for one time execution and for multiple time execution, we should use as new MyAsyncTask().execute(""); I don't understand why people downvote with no reason given. I couldn't find the link in my first search.

Categories

Resources