enter image description herei want to cancel request in model implementation using mvp pattern in android
iam using retrofit2 .in this method i sent file path and state to check on it because button action make (upload,cancel)in the same function.
this snipet of class
{public class ModelImpl implements UploadInterface.Interactor, ProgressRequestBody.UploadCallbacks {enter image description here
//another way we can use retrofit call here to upload file and
//return result in OnFinishedListener interface inside model interface
//we use here service to upload to run in background service
// this way we can cancel request and retry
//but using intent service in service difficult to stop because it designed to
//run long task and stop it self with caller.
private OnProgressListener listener;
public ModelImpl(OnProgressListener listener) {
this.listener = listener;
}
#Override
public void uploadImage(String status, String filePath, OnFinishedListener onFinishedListener) {
// call servce to start upload throw service
/*Intent mIntent = new Intent(context, FileUploadService.class);
mIntent.putExtra("mFilePath", filePath);
FileUploadService.enqueueWork(context, mIntent);*/
// starting http service upload
if (!filePath.isEmpty()) {
File file = new File(filePath.trim());
ProgressRequestBody fileBody = new ProgressRequestBody(file, "image", this);
MultipartBody.Part filePart = MultipartBody.Part.createFormData("fileUpload", file.getName(), fileBody);
RestApiService apiService = RetrofitInstance.getApiService();
Call<PojoResponse> callUpload = apiService.onFileUpload2(filePart);
if (status.equals("upload")) {
callUpload.enqueue(new Callback<PojoResponse>() {
#Override
public void onResponse(Call<PojoResponse> call, Response<PojoResponse> response) {
Log.d("ResponseData", "" + response.body().getUrl());
onFinishedListener.onFinished(response.body());
}
#Override
public void onFailure(Call<PojoResponse> call, Throwable t) {
if (call != null && !call.isCanceled()) {
// Call is not cancelled, Handle network failure
onFinishedListener.onFailure(call, t);
} else if (call != null && call.isCanceled()) {
// Call is CANCELLED. IGNORE THIS SINCE IT WAS CANCELLED.
onFinishedListener.onFailure(call, t);
}
//onFinishedListener.onFailure(call, t);
}
});
} else {
if (callUpload != null && callUpload.isExecuted()) {
callUpload.cancel();
}
}
}
}
}
package com.example.mvp2.ui.main.model;
import android.util.Log;
import com.example.mvp2.ui.main.network.RestApiService;
import com.example.mvp2.ui.main.network.RetrofitInstance;
import com.example.mvp2.ui.main.utils.ProgressRequestBody;
import com.example.mvp2.ui.main.views.upload.UploadInterface;
import java.io.File;
import okhttp3.MultipartBody;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
public class ModelImpl implements UploadInterface.Interactor, ProgressRequestBody.UploadCallbacks {
//another way we can use retrofit call here to upload file and
//return result in OnFinishedListener interface inside model interface
//we use here service to upload to run in background service
// this way we can cancel request and retry
//but using intent service in service difficult to stop because it designed to
//run long task and stop it self with caller.
private OnProgressListener listener;
private Call < PojoResponse > callUpload;
public ModelImpl(OnProgressListener listener) {
this.listener = listener;
}
#Override
public void uploadImage(String status, String filePath, OnFinishedListener onFinishedListener) {
if (!filePath.isEmpty()) {
File file = new File(filePath.trim());
ProgressRequestBody fileBody = new ProgressRequestBody(file,
"image", this);
MultipartBody.Part filePart =
MultipartBody.Part.createFormData("fileUpload", file.getName(), fileBody);
RestApiService apiService = RetrofitInstance.getApiService();
callUpload = apiService.onFileUpload2(filePart);
// if (status.equals("upload")) {
callUpload.enqueue(new Callback < PojoResponse > () {
#Override
public void onResponse(Call < PojoResponse > call, Response < PojoResponse > response) {
Log.d("ResponseData", "" + response.body().getUrl());
onFinishedListener.onFinished(response.body());
}
#Override
public void onFailure(Call < PojoResponse > call, Throwable t) {
if (call != null && !call.isCanceled()) {
// Call is not cancelled, Handle network failure
onFinishedListener.onFailure(call, t);
} else if (call != null && call.isCanceled()) {
// Call is CANCELLED. IGNORE THIS SINCE IT WAS CANCELLED.
onFinishedListener.onFailure(call, t);
}
}
});
// }
/* else {
if (callUpload != null && callUpload.isExecuted()) {
callUpload.cancel();
// this will go to presenter
onFinishedListener.onCancel();
}
}*/
}
}
public void cancelUpload() {
if (callUpload != null && callUpload.isExecuted()) {
callUpload.cancel();
// this will go to presenter
onFinishedListener.onCancel();
}
}
#Override
public void onProgressUpdate(int percentage) {
Log.d("percent", "" + percentage);
listener.onProgressChange(percentage);
}
#Override
public void onError() {
}
#Override
public void onFinish() {
Log.d("percent", "" + "finishedddddd");
listener.onProgressFinished();
}
}
UploadActivityPresenter .java
package com.example.mvp2.ui.main.views.upload;
import android.util.Log;
import com.example.mvp2.ui.main.model.ModelImpl;
import com.example.mvp2.ui.main.model.PojoResponse;
import retrofit2.Call;
public class UploadActivityPresenter implements UploadInterface.Presenter, UploadInterface.Interactor.OnFinishedListener, UploadInterface.Interactor.OnProgressListener {
private UploadInterface.View view;
private UploadInterface.Interactor model;
public UploadActivityPresenter(UploadInterface.View view) {
this.view = view;
model = new ModelImpl(this);
}
#Override
public void uploadBtnClicked(String status, String filePath) {
// this interface call method upload without know about logic about it
// model = new ModelImpl(this);
if (view != null) {
if (filePath.length() > 0) {
Log.d("filepath", "" + filePath.trim());
view.setStatus(status);
if (model != null) {
if (status.equals("upload")) {
model.uploadImage(status, filePath, this);
} else {
model.cancelUpload()
}
}
Log.d("ss", "ssssss");
} else {
view.selectFileFirst();
}
}
}
#Override
public void imageClicked() {
if (view != null) {
view.showFullImageInFragment();
}
}
#Override
public void onFinished(PojoResponse obj) {
if (view != null) {
view.getResponse(obj);
view.setStatus("Done");
}
}
#Override
public void onFailure(Call < PojoResponse > call, Throwable t) {
if (view != null) {
view.errorUploading(call, t);
}
}
#Override
public void onCancel() {
if (view != null) {
}
}
#Override
public void onProgressChange(int percent) {
Log.d("aaaaa", "" + percent);
if (view != null) {
view.setProgressPercent(percent);
}
}
#Override
public void onProgressFinished() {
if (view != null) {
view.setProgressFinished();
}
}
}
this should work , u were creating new Model object everytime you upload image or cancel upload image in presenter ,similarly in modelImpl call object was instantiated on every call .
Make sure your presenter class is instantiated one time only .
Related
I am trying to upload plain text and image to the api using RetroFit. I want make sure the request continues to execute on orientation change. To do this, I have encapsulated the RetroFit api call inside a Headless fragment. This works fine when I try to upload an image. The request stops and resumes on device rotation. However it just gets cancelled on a text upload.
The only difference between the two uploads is that for image upload I use execute() and for text I use enqueue(). However, if I try to use execute() with the text, it still does not work.
Below is some code :-
UpdateTaskHelper (Headless fragment)
public static class UploadTaskHelper extends Fragment
{
private UploadAsync uploadTask;
private ProgressDialog m_loadingp;
public static UploadTaskHelper newInstance()
{
return new UploadTaskHelper();
}
#Override
public void onCreate(#Nullable Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setRetainInstance(true);
}
#Override
public void onDestroy()
{
Log.d(getClass().getName(), "[onDestroy]");
super.onDestroy();
if (uploadTask != null)
{
uploadTask.cancel(true);
}
}
public void startUpload(ActionActivity actionActivity, boolean shouldTakePhoto, boolean isTextNote, String noteContent)
{
uploadTask = new UploadAsync(actionActivity, shouldTakePhoto, isText, noteContent);
uploadTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
private static class UploadAsync extends AsyncTask<Void, Void, Void>
{
private Bitmap m_bitmap = null;
private Pair<Boolean, String> m_errorPair;
private File m_uploadedFile = null;
private WeakReference<ActionActivity> m_weakActivity;
private boolean shouldTakePhoto;
private boolean isTextNote;
private java.io.File m_capturedImageFile;
UploadAsync(#NonNull ActionActivity activity, boolean shouldTakePhoto, boolean isTextNote, String textNoteContent)
{
this.m_weakActivity = new WeakReference<>(activity);
this.shouldTakePhoto = shouldTakePhoto;
this.isTextNote = isTextNote;
}
#Override
protected Void doInBackground(Void... params)
{
try
{
final ActionActivity activity = this.m_weakActivity.get();
activity.m_fileAPIWrapper = new FileAPIWrapper(new IHttpEventTracker<File>()
{
#Override
public void getCallProgress(int progress) {}
#Override
public void onCallFail(#NonNull String cause, #NonNull Throwable t, #Nullable ResponseBody responseBody)
{
m_errorPair = new Pair<>(true, t.getLocalizedMessage());
}
#Override
public void onCallSuccess(#NonNull RealmList<File> models)
{
m_errorPair = new Pair<>(false, AppConstants.EMPTY_STRING);
m_uploadedFile = models.get(0);
}
});
if(!isTextNote)
{
final java.io.File storageDir = new java.io.File(Environment.getExternalStorageDirectory()
.getAbsolutePath() + java.io.File.separator + activity.getPackageName()
+ java.io.File.separator + "-" + java.io.File.separator);
if (!storageDir.exists())
{
storageDir.mkdirs();
}
this.m_capturedImageFile = java.io.File.createTempFile("IMG_" + System.currentTimeMillis(), ".jpg", storageDir);
final FileOutputStream outStream = new FileOutputStream(this.m_capturedImageFile);
this.m_bitmap.compress(Bitmap.CompressFormat.JPEG, 50, outStream);
outStream.flush();
outStream.close();
final ByteArrayOutputStream stream = new ByteArrayOutputStream();
this.m_bitmap.compress(Bitmap.CompressFormat.JPEG, 50, stream);
activity.m_fileAPIWrapper.postImage(RequestBody.create(MediaType.parse("image/jpeg"), stream.toByteArray()));
stream.flush();
stream.close();
}
else
{
activity.m_fileAPIWrapper.postTextNote(RequestBody.create(MediaType.parse("multipart/raw"), activity.m_addContentNoteEdit.getText()
.toString()));
}
}
catch (Exception e)
{
e.printStackTrace();
this.m_errorPair = new Pair<>(true, e.toString());
}
return null;
}
#Override
protected void onPreExecute()
{
super.onPreExecute();
activity.m_loading.show();
}
#Override
protected void onPostExecute(Void aVoid)
{
super.onPostExecute(aVoid);
}
}
}
Network calls :-
public void postImage(#NonNull RequestBody reqFile) {
if (m_eventTracker != null) {
final ResponseToken token = NetworkUtil.getAccessToken();
if (getService() != null && m_httpOperationWrapper != null && token != null) {
m_call = getService().postImage(token.getTokenType() + " " + token.getAccessToken(), NetworkUtil.X_VERSION,
"filename=IMG_" + System.currentTimeMillis(), "image/jpeg", reqFile);
m_httpOperationWrapper.initCall(m_call, this, true);
} else {
m_eventTracker.onCallFail(AppConstants.BAD_REQUEST, new Throwable("Something went wrong, Try again later!"), null);
}
}
}
/**
* Execute HTTP call to post a new text note.
*/
public void postTextNote(#NonNull RequestBody requestBody) {
if (m_eventTracker != null) {
final ResponseToken token = NetworkUtil.getAccessToken();
if (getService() != null && m_httpOperationWrapper != null && token != null) {
m_call = getService().postFile(token.getTokenType() + " " + token.getAccessToken(), NetworkUtil.X_VERSION,
"filename=" + token.getOwnerId() + "_text_note_" + System.currentTimeMillis(), "text/plain",
requestBody);
m_httpOperationWrapper.initCall(m_call, this);
} else {
m_eventTracker.onCallFail(AppConstants.BAD_REQUEST, new Throwable("Something went wrong, Try again later!"), null);
}
}
}
public void initCall(#NonNull Call<ContentResponse> call, #NonNull IHttpOperationCallback callback, final boolean isSynchronousCall) {
m_callback = callback;
try {
if (NetworkUtil.isNetworkAvailable()) {
if (isSynchronousCall) {
m_executeRequest(call);
} else {
m_enqueueRequest(call);
}
} else {
m_callback.onFailure(call, new Throwable(AppConstants.NO_INTERNET), null);
}
} catch (Exception e) {
m_callback.onFailure(call, e.fillInStackTrace(), null);
}
}
private void m_enqueueRequest(#NonNull Call<ContentResponse> call) {
call.enqueue(new Callback<ContentResponse>() {
#SuppressWarnings("ConstantConditions")
#Override
public void onResponse(#NonNull Call<ContentResponse> call, #NonNull Response<ContentResponse> response) {
if (m_callback != null) {
if (!Util.isValidResponse(response)) {
String error = "Status: " + response.code() + " " + response.message();
m_callback.onFailure(call, new Throwable(
response.code() == HttpURLConnection.HTTP_UNAUTHORIZED ? AppConstants.UNAUTHORIZED : error), response.errorBody());
return;
}
m_callback.onSuccess(call, response.body());
}
}
#Override
public void onFailure(#NonNull Call<ContentResponse> call, #NonNull Throwable t) {
if (m_callback != null) {
m_callback.onFailure(call, t, null);
}
}
});
}
#WorkerThread
private void m_executeRequest(#NonNull Call<ContentResponse> call) {
try {
Response<ContentResponse> response = call.execute();
if (m_callback != null) {
if (!Util.isValidResponse(response)) {
String error = "Status: " + response.code() + " " + response.message();
m_callback.onFailure(call,
new Throwable(response.code() == HttpURLConnection.HTTP_UNAUTHORIZED ? AppConstants.UNAUTHORIZED : error),
response.errorBody());
return;
}
//noinspection ConstantConditions
m_callback.onSuccess(call, response.body());
}
} catch (IOException | RuntimeException e) {
e.printStackTrace();
if (m_callback != null) {
m_callback.onFailure(call, e.fillInStackTrace(), null);
}
}
}
How can I get the same behaviour for the text note? Any help is appreciated.
When you use enqueue your request sent async, and the orientation change destroys the activity and cancels your response code scope.
You should consider move the request code into a ViewModel class which is part of the MVVM architecture. The ViewModel would make the request even after orientation change and keep the data inside it, then you could access its data after the activity is re-created.
I am working with MVVM pattern. I have just started it and i have done it successfully.
But I don't understand how to add progress bar for showing and hide as we normally do for API calls.
I am not using data binding. So how can i use progress bar for showing and hide it.
For Login
public class LoginRepository {
private DATAModel dataModel = new DATAModel();
private MutableLiveData<DATAModel> mutableLiveData = new MutableLiveData<>();
private Application application;
public LoginRepository(Application application) {
this.application = application;
}
public MutableLiveData<DATAModel> getMutableLiveData(String username, String password) {
APIRequest apiRequest = RetrofitRequest.getRetrofit().create(APIRequest.class);
JsonLogin jsonLogin = new JsonLogin(Constants.DEVICE_TYPE, Functions.getDeviceId(application.getApplicationContext()), Constants.APP_VERSION, Constants.API_VERSION, Functions.getTimeStamp(), Functions.getDeviceModel(), Build.VERSION.RELEASE, username, password);
Call<APIResponseLogin> call = apiRequest.getUsersDetails(jsonLogin);
call.enqueue(new Callback<APIResponseLogin>() {
#Override
public void onResponse(Call<APIResponseLogin> call, Response<APIResponseLogin> response) {
APIResponseLogin apiResponse = response.body();
if (apiResponse != null && apiResponse.getStatuscode() == 0) {
if (apiResponse.getDataModel() != null) {
dataModel = apiResponse.getDataModel();
mutableLiveData.setValue(dataModel);
}
} else if (apiResponse != null && apiResponse.getStatuscode() == 1) {
Log.v("AAAAAAAAA", apiResponse.getStatusmessage());
}
}
#Override
public void onFailure(Call<APIResponseLogin> call, Throwable t) {
Log.v("ErrorResponse", t.getMessage() + " : " + call.request().toString());
}
});
return mutableLiveData;
}
Activity Code
void loginCall() {
loginViewModel.getUserDetails(editTextUsername.getText().toString().trim(), editTextPassword.getText().toString().trim()).observe(this, new Observer<DATAModel>() {
#Override
public void onChanged(#Nullable DATAModel dataModel) {
if (dataModel != null) {
Userdetails userdetails = dataModel.getUserdetails();
List<ContactTypes> contactTypes = dataModel.getContactTypes();
if (userdetails != null) {
MySharedPreferences.setCustomPreference(LoginActivity.this, Constants.SHAREDPREFERENCE_USERDETAILS, userdetails);
MySharedPreferences.setStringPreference(LoginActivity.this, Constants.SHAREDPREFERENCE_USER_ID, userdetails.getUserId());
}
if (contactTypes != null) {
MySharedPreferences.setCustomArrayList(LoginActivity.this, Constants.SHAREDPREFERENCE_CONTACTTYPES, contactTypes);
}
Intent i = new Intent(LoginActivity.this, MainActivity.class);
startActivity(i);
finish();
}
}
});
}
Advanced help would be appreciated!
When you call api that time you have to take one live variable which shows your api is in loading mode or not and after success or failure you have to update that variable.
After observe that variable in your activity or fragment class and show or hide your progress.
public class LoginRepository {
private DATAModel dataModel = new DATAModel();
private MutableLiveData<DATAModel> mutableLiveData = new MutableLiveData<>();
private Application application;
private MutableLiveData<Boolean> progressbarObservable;
public LoginRepository(Application application) {
this.application = application;
}
public MutableLiveData<DATAModel> getMutableLiveData(String username, String password) {
// add below line
progressbarObservable.value = true
APIRequest apiRequest = RetrofitRequest.getRetrofit().create(APIRequest.class);
JsonLogin jsonLogin = new JsonLogin(Constants.DEVICE_TYPE, Functions.getDeviceId(application.getApplicationContext()), Constants.APP_VERSION, Constants.API_VERSION, Functions.getTimeStamp(), Functions.getDeviceModel(), Build.VERSION.RELEASE, username, password);
Call<APIResponseLogin> call = apiRequest.getUsersDetails(jsonLogin);
call.enqueue(new Callback<APIResponseLogin>() {
#Override
public void onResponse(Call<APIResponseLogin> call, Response<APIResponseLogin> response) {
// add below line
progressbarObservable.value = false
APIResponseLogin apiResponse = response.body();
if (apiResponse != null && apiResponse.getStatuscode() == 0) {
if (apiResponse.getDataModel() != null) {
dataModel = apiResponse.getDataModel();
mutableLiveData.setValue(dataModel);
}
} else if (apiResponse != null && apiResponse.getStatuscode() == 1) {
Log.v("AAAAAAAAA", apiResponse.getStatusmessage());
}
}
#Override
public void onFailure(Call<APIResponseLogin> call, Throwable t) {
// add below line
progressbarObservable.value = false
Log.v("ErrorResponse", t.getMessage() + " : " + call.request().toString());
}
});
return mutableLiveData;
}
}
Now, observe above variable in activity or fragment and based on that value hide or show your progress bar
public class LoginActivity extends AppCompatActivity {
...
#Override
protected void onCreate(Bundle savedInstanceState) {
...
observeLogin();
}
#Override
public void onClick(View view)
{
switch (view.getId()) {
case R.id.button_login:
// Do something
loginCall();
}
}
private void observeLogin() {
loginViewModel.progressbarObservable().observe(this, new Observer<Boolean>() {
#Override
public void onChanged(final Boolean progressObserve) {
if(progressObserve){
show your progress
}
else {
hide your progress
}
}
});
}
void loginCall() {
loginViewModel.getUserDetails(editTextUsername.getText().toString().trim(), editTextPassword.getText().toString().trim()).observe(this, new Observer<DATAModel>() {
#Override
public void onChanged(#Nullable DATAModel dataModel) {
if (dataModel != null) {
Userdetails userdetails = dataModel.getUserdetails();
List<ContactTypes> contactTypes = dataModel.getContactTypes();
if (userdetails != null) {
MySharedPreferences.setCustomPreference(LoginActivity.this, Constants.SHAREDPREFERENCE_USERDETAILS, userdetails);
MySharedPreferences.setStringPreference(LoginActivity.this, Constants.SHAREDPREFERENCE_USER_ID, userdetails.getUserId());
}
if (contactTypes != null) {
MySharedPreferences.setCustomArrayList(LoginActivity.this, Constants.SHAREDPREFERENCE_CONTACTTYPES, contactTypes);
}
Intent i = new Intent(LoginActivity.this, MainActivity.class);
startActivity(i);
finish();
}
}
});
}
}
I find it easier to write my own callback interface in this situation. Just not that this will be done synchronously so all will wait until your api call responds. But in such a case, a progress dialog would be havin the similar effect.
1.Create inteface:
public interface ProgressCallback{
void onDone(String message);
void onFail(String message);
}
Now in your method where you call the API
loginUser(String name, String password, ProgressCallback
progressCallback){
call.enqueue(new Callback<LoginData>() {
#RequiresApi(api = Build.VERSION_CODES.O)
#Override
public void onResponse(Call<LoginData> call, Response<LoginData> response) {
progressCallBack.onSuccess(response.message());
}
#Override
public void onFailure(Call<LoginData> call, Throwable t) {
progressCallBack.onFail(t.getMessage());
}
});
Now when you call the method
loginUser("John#doe.com", "applesgravity", new ProgressCallBack() {
#Override
public void onSuccess(String message) {
progressBar.setVisibility(View.INVISIBLE);
}
#Override
public void onFail(String message) {
progressBar.setVisibility(View.INVISIBLE);
}
});
I have a general custom listener/callback question.
In my code, I have the following interface and LocalDB class that read room database:
# Custom interface
public interface MyInterface {
void OnSuccess();
void OnFailure();
}
# Class LocalDB
public class LocalDB {
private MyInterface myInterface;
public static PIMUserLocalDataSource getInstance(#NonNull Context context)
{
if (INSTANCE == null) {
synchronized (PIMUserLocalDataSource.class) {
INSTANCE = new PIMUserLocalDataSource(context);
}
}
return INSTANCE;
}
public void setCustomListener(CustomListener customListener) {
this.customListener = customListener;
}
private void queryA() {
Runnable runnable = new Runnable() {
result = appDatabase.myDao().getQueryA();
if (result != null) {
if (customListener != null) {
customListener.onSuccess();
} else {
customListener.onFailure();
}
}
}
}
private void queryB() {
Runnable runnable = new Runnable() {
result = appDatabase.myDao().getQueryB();
if (result != null) {
if (customListener != null) {
customListener.onSuccess();
} else {
customListener.onFailure();
}
}
}
}
}
# Fragment / Activity
LocalDB myDB = LocalDB.getInstance(context)
myDB.setCustomListener(new CustomListener) {
#Override
public void OnSuccess() {
Log.e(logTag, "Success queryA");
}
#Override
public void OnFailure() {
Log.e(logTag, "Failed queryA");
}
}
myDB.queryA()
myDB.setCustomListener(new CustomListener) {
#Override
public void OnSuccess() {
Log.e(logTag, "Success queryB");
}
#Override
public void OnFailure() {
Log.e(logTag, "Failed queryB");
}
}
myDB.queryB()
Problem
These works fine most of the time, however, there is sometimes that queryA is slow and queryB is done before queryA, queryB callback to queryB no problem, but when queryA is done, it callback to queryB listener. I think because the listener of B overwritten A? How should I avoid this kind of problem?
when you call queryA or queryB. pass the listener.
# Custom interface
public interface MyInterface {
void OnSuccess();
void OnFailure();
}
# Class LocalDB
public class LocalDB {
boolean successA,successB;
public static PIMUserLocalDataSource getInstance(#NonNull Context context)
{
if (INSTANCE == null) {
synchronized (PIMUserLocalDataSource.class) {
INSTANCE = new PIMUserLocalDataSource(context);
}
}
return INSTANCE;
}
private void queryA(CustomListener customListener) {
Runnable runnable = new Runnable() {
result = appDatabase.myDao().getQueryA();
if (result != null) {
if (customListener != null) {
customListener.onSuccess();
} else {
customListener.onFailure();
}
}
}
}
private void queryB(CustomListener customListener) {
Runnable runnable = new Runnable() {
result = appDatabase.myDao().getQueryB();
if (result != null) {
if (customListener != null) {
customListener.onSuccess();
} else {
customListener.onFailure();
}
}
}
}
}
# Fragment / Activity
LocalDB myDB_A = LocalDB.getInstance(context)
myDB.setCustomListener(new CustomListener) {
#Override
public void OnSuccess() {
successA=true;
checkIfTwoFinishedExcutecode();
Log.e(logTag, "Success queryA");
}
#Override
public void OnFailure() {
Log.e(logTag, "Failed queryA");
}
}
myDB.queryA(myDB_A )
LocalDB myDB_B = LocalDB.getInstance(context)
#Override
public void OnSuccess() {
successB=true;
checkIfTwoFinishedExcutecode();
Log.e(logTag, "Success queryB");
}
#Override
public void OnFailure() {
Log.e(logTag, "Failed queryB");
}
}
myDB.queryB(myDB_B)
void checkIfTwoFinishedExcutecode(){
if(successA&&successB){
// the two is finished. write your code
}
}
I am trying to call the web service to fetch the data and storing it into database using following code. I have created a separate class to perform following operation.
Now , the issue is i want to notify my activity when i successfully fetch and store data in database. if some error occurs then i want to show that on UI itself.
somehow i am able to write a code to fetch the data using pagination but not sure how would i notify UI where i can subscribe catch the update related to progress and error if any.
public Flowable<Response> getFitnessData() {
Request request = new Request();
request.setAccess_token("d80fa6bd6f78cc704104d61146c599bc94b82ca225349ee68762fc6c70d2dcf0");
Flowable<Response> fitnessFlowable = new WebRequest()
.getRemoteClient()
.create(FitnessApi.class)
.getFitnessData("5b238abb4d3590001d9b94a8",request.toMap());
fitnessFlowable.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.takeUntil(response->response.getSummary().getNext()!=null)
.subscribe(new Subscriber<Response>() {
#Override
public void onSubscribe(Subscription s) {
s.request(Long.MAX_VALUE);
}
#Override
public void onNext(Response response) {
Log.e(TAG, "onNext" );
if(response !=null){
if(response.getFitness()!=null && response.getFitness().size()!=0){
Realm realm = Realm.getDefaultInstance();
realm.executeTransactionAsync(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
realm.copyToRealmOrUpdate(response.getFitness());
}
}, new Realm.Transaction.OnSuccess() {
#Override
public void onSuccess() {
Log.i(TAG, "onSuccess , fitness data saved");
}
}, new Realm.Transaction.OnError() {
#Override
public void onError(Throwable error) {
Log.i(TAG, "onError , fitness data failed to save"+error.getMessage() );
}
});
}else{
Log.i(TAG, "onError , no fitness data available" );
}
}else{
Log.i(TAG, "onError , response is null" );
}
}
#Override
public void onError(Throwable t) {
Log.e(TAG, "onError" +t.getMessage());
}
#Override
public void onComplete() {
Log.e(TAG, "onComplete");
}
});;
return null;
}
Updated
Created RxBus to propagate events and error on UI
public class RxBus {
private static final RxBus INSTANCE = new RxBus();
private RxBus(){}
private PublishSubject<Object> bus = PublishSubject.create();
public static RxBus getInstance() {
return INSTANCE;
}
public void send(Object o) {
bus.onNext(o);
}
public void error(Throwable e){bus.onError(e);}
public Observable<Object> toObservable() {
return bus;
}
}
in activity
FitnessRepo fitnessRepo = new FitnessRepo();
fitnessRepo.getFitnessData();
RxBus.getInstance().toObservable().subscribe(new Observer<Object>() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onNext(Object o) {
if(o instanceof RealmList ){
RealmList<Fitness> realmList = (RealmList<Fitness>) o;
Log.e(TAG,"Fitness data size "+realmList.size());
}
}
#Override
public void onError(Throwable e) {
Log.e(TAG,e.getMessage()+ "");
if (e instanceof HttpException) {
ResponseBody body = ((HttpException) e).response().errorBody();
try {
Response response= LoganSquare.parse(body.byteStream(),Response.class);
if(response.getErrors() !=null)
if(response.getErrors().size() > 0)
Log.e(TAG, "Error "+response.getErrors().get(0).getErrors());
} catch (IOException t) {
t.printStackTrace();
}
}
}
#Override
public void onComplete() {
}
});
in a web service call
public void getFitnessData() {
Request request = new Request();
request.setAccess_token("d80fa6bd6f78cc704104d61146c599bc94b82ca225349ee68762fc6c70d2dcf0");
request.setEnd_date("2018-07-01T00:00:00");
Flowable<Response> fitnessFlowable = new WebRequest()
.getRemoteClient()
.create(FitnessApi.class)
.getFitnessData("5b238abb4d3590001d9b94a8",request.toMap());
fitnessFlowable.subscribeOn(Schedulers.io())
.takeUntil(response->response.getSummary().getNext()!=null)
.doOnNext((response) -> {
if(response ==null || response.getFitness() == null || response.getFitness().isEmpty()) {
Log.e(TAG, " Error ");
return;
}
RxBus.getInstance().send(response.getFitness());
try(Realm r = Realm.getDefaultInstance()) {
r.executeTransaction((realm) -> {
realm.copyToRealmOrUpdate(response.getFitness());
});
}
}).subscribe(item ->{
},
error ->{
RxBus.getInstance().error(error);
});
}
I have good news for you! You can delete almost all of that code and just make it generally better as a result!
public void fetchFitnessData() {
Request request = new Request();
request.setAccess_token("d80fa6bd6f78cc704104d61146c599bc94b82ca225349ee68762fc6c70d2dcf0");
Flowable<Response> fitnessFlowable = new WebRequest()
.getRemoteClient()
.create(FitnessApi.class)
.getFitnessData("5b238abb4d3590001d9b94a8",request.toMap());
fitnessFlowable.subscribeOn(Schedulers.io())
.takeUntil(response->response.getSummary().getNext()!=null)
.doOnNext((response) -> {
if(response ==null || response.getFitness() == null || response.getFitness().isEmpty()) return;
try(Realm r = Realm.getDefaultInstance()) {
r.executeTransaction((realm) -> {
realm.insertOrUpdate(response.getFitness());
});
}
}
}).subscribe();
}
This method is on a background thread now and returns void, so the way to emit stuff out of this method would be to use either a PublishSubject (one for success, one for failure) or an EventBus.
private PublishSubject<Object> fitnessResults;
public Observable<Object> observeFitnessResults() {
return fitnessResults;
}
public static class Success {
public Success(List<Fitness> data) {
this.data = data;
}
public List<Fitness> data;
}
public static class Failure {
public Failure(Exception exception) {
this.exception = exception;
}
public Exception exception;
}
public void fetchFitnessData() {
...
fitnessResults.onNext(new Success(data));
} catch(Exception e) {
fitnessResults.onNext(new Failure(e));
And then
errors = observeFitnessResults().ofType(Error.class);
success = observeFitnessResults().ofType(Success.class);
There are different ways to achieve this. I will never handle the subscriptions on my own out of a lifecycle scope as it creates a possibility of memory leak. In your case it seems that both success and failure is bound to the UI so you can simply do this.
public Completable fetchFitnessData() {
Request request = new Request();
request.setAccess_token("d80fa6bd6f78cc704104d61146c599bc94b82ca225349ee68762fc6c70d2dcf0");
Flowable<Response> fitnessFlowable = new WebRequest()
.getRemoteClient()
.create(FitnessApi.class)
.getFitnessData("5b238abb4d3590001d9b94a8",request.toMap());
return fitnessFlowable.subscribeOn(Schedulers.io())
.takeUntil(response->response.getSummary().getNext()!=null)
.doOnNext((response) -> {
if(response ==null || response.getFitness() == null || response.getFitness().isEmpty()) return;
try(Realm r = Realm.getDefaultInstance()) {
r.executeTransaction((realm) -> {
realm.insertOrUpdate(response.getFitness());
});
}
}
}).ignoreElements();
}
At UI level, you can just handle your subscription with both success and failure. In case you need success model can replace Completable with Single or Flowable.
fetchFitnessData.subscrible(Functions.EMPTY_ACTION, Timber::d);
The major advantage with this approach is that you handle your subscription lifecycles.
I have a singleton to handle the registration and elimination of an entity Profilo ( a Profile).
This entity is set by passing an identifier and gathering information on the server in an async way.
My problem is that when I have to return my instance of profilo if it's not still loaded it will return null.
public class AccountHandler {
private static AccountHandler istanza = null;
Context context;
private boolean logged;
private Profilo profilo;
private AccountHandler(Context context) {
this.context = context;
//initialization
//setting logged properly
assignField(this.getName());
}
}
public static AccountHandler getAccountHandler(Context context) {
if (istanza == null) {
synchronized (AccountHandler.class) {
if (istanza == null) {
istanza = new AccountHandler(context);
}
}
}
return istanza;
}
public void setAccount(String nickname, String accessingCode) {
logged = true;
assignField(nickname);
}
//other methods
private void assignField(String nickname) {
ProfiloClient profiloClient = new ProfiloClient();
profiloClient.addParam(Profilo.FIELDS[0], nickname);
profiloClient.get(new JsonHttpResponseHandler() {
#Override
public void onSuccess(int statusCode,
Header[] headers,
JSONArray response) {
JSONObject objson = null;
try {
objson = (JSONObject) response.getJSONObject(0);
} catch (JSONException e) {
e.printStackTrace();
}
AccountHandler accountHandler = AccountHandler.getAccountHandler(context);
// Profilo is created with a JSONObject
// **setProfilo is called in async**
**accountHandler.setProfilo(new Profilo(objson));**
}
});
}
private void setProfilo(Profilo profilo) {
this.profilo = profilo;
}
public Profilo getProfilo() {
if( logged && profilo == null)
//How can I wait that profilo is loaded by the JsonHttpResponseHandler before to return it
return this.profilo;
}
}
Instead of calling getProfilo you could use a callback mechanism in which the AccountHandler class notifies the caller when the profile has been loaded. e.g.
public void setAccount(String nickname, String accessingCode, MyCallback cb) {
assignField(nickname, cb);
}
private void assignField(String nickname, MyCallback cb) {
....
accountHandler.setProfilo(new Profilo(objson));
cb.onSuccess(this.profilo);
}
Create an inner Interface MyCallback (rename it) in your AccountHandler class
public class AccountHandler {
public interface MyCallback {
void onSuccess(Profilo profile);
}
}
Now whenever you call setAccount you will pass the callback and get notified when the profile is available e.g.
accountHandler.setAccount("Test", "Test", new AccountHandler.MyCallback() {
void onSuccess(Profilio profile) {
// do something with the profile
}
}
I added, as #Murat K. suggested, an interface to my Class that will provide a method to be call with the object when it is ready to be used.
public class AccountHandler {
public interface Callback {
void profiloReady(Profilo profilo);
}
}
This method is called in getProfilo in a Handler that makes recursive calls to getProfilo until profilo is ready to be used, then it call the callback method which class is passed as argument of getProfilo.
public void getProfilo(final Callback Callback) {
if( logged && (profilo == null || !profilo.isReady() ) {
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
getProfilo(Callback);
}
}, 500);
}else
Callback.profiloReady(profilo);
}
Example of getProfilo call
public class ProfiloCall implements AccountHandler.MyCallback {
#Override
public void profiloReady(Profilo profilo) {
//Use profilo as needed
//EXECUTED ONLY WHEN PROFILO IS READY
}
public void callerMethod() {
//useful code
accountHandler.getProfilo(this);
//other useful code
}
}