I am developing an Android app.
RxJava is used.
It stores user data in the local database with expire time.
First, it gets user data from the local database.
And check the expire time, if the data is old data, then it gets user data from remote server and update it to the local database.
fun getPlayer(playerId: String): Single<Player> {
return playerDao.getPlayer(playerId)
.doOnSuccess { // "doOnSuccess" is right? what method should I use?
if (PlayerUtil.isNeededUpdate(it)) {
Log.d(TAG, "getPlayer(local) - old!")
getPlayerFromRemote(playerId)
// How can I return Observable/Flowable/Single in here?
// (case of need to update data)
}
}
.onErrorResumeNext {
// If local database has no player, then it try to get it from remote server
Log.d(TAG, "getPlayer(local) - onError: ${it.message}")
getPlayerFromRemote(playerId)
}
}
doOnSuccess is meant to be used for side-effects, not actions that impact the stream itself.
What you're looking for is flatMap and just returning the scalar value if nothing needs to be done:
.flatMap {
if (PlayerUtil.isNeededUpdate(it)) {
getPlayerFromRemote(playerId)
} else {
Single.just(it)
}
}
Related
I'm using Parse Server for my Android app and everything is working fine, but every time I call saveEventually on a new or old ParseObject, it is taking a really long time. Sometimes it's more than 1 minute for 1 item to return the callback.
Anyone had this problem?
Example:
orderObject.p.apply {
put(ORDER_STATE, ORDER_STATE_FINISHED)
put(ORDER_NEXT_DATE, orderEndDate)
}
createLog("FinishOrderSeq", "OrderActivity - saveOrder - before saveEvent")
orderObject.p.saveEventuallyEx(isOnline(this)){ e ->
createLog("FinishOrderSeq", "OrderActivity - saveOrder - after saveEvent")
if (e == null){
createToast(getString(R.string.order_dialog_success), this)
createOrderCopy(orderObject, dialog)
} else {
createToast(getString(R.string.order_dialog_err), this)
changeButtonState(posBtn, true)
changeButtonState(negBtn, true)
}
}
fun ParseObject.saveEventuallyEx(isOnline: Boolean, callback: (ParseException?) -> Unit){
if (isOnline){
saveEventually{ err ->
callback(err)
}
} else {
saveEventually()
callback(null)
}
}
Also logs as I replaced it with saveInBackground with callback(still 30 seconds):
2020-05-28 14:53:49.805 18673-18673/? I/FinishOrderSeq: OrderActivity - saveOrder - before saveEvent
2020-05-28 14:54:15.694 18673-18673/? I/FinishOrderSeq: OrderActivity - saveOrder - after saveEvent
UPDATE:
So I figured out from parse dashboard, that ParseObject is saved as record in table immediatelly, but callback from saveEventually is sent after 30sec - 2 minutes.
UPDATE 2:
I also tried to use saveInBackground() if user is online (with callback). This also took 30seconds to 2 minutes for callback to return. Object was saved to parse database with all data after 100ms (checked from Parse Dashboard).
Then I thought something is wrong with ParseSDK threads, so I used save() inside Coroutine. Same problem occured here, save() took up to 2 minutes to perform.
Code with coroutine:
fun ParseObject.saveAsync(context: CoroutineContext, scope: CoroutineScope, isOnline: Boolean, callback: (ParseException?) -> Unit){
if (isOnline){
scope.launch {
var ex: ParseException? = null
try {
save()
} catch (e: ParseException){
ex = e
}
withContext(context){
callback(ex)
}
}
}
}
There is some serious problem with callbacks in ParseSDK for Android and I don't know what can cause this. No exception no error on server side.
UPDATE 3:
After deeper investigation, I found which function is taking long time to proceed.
ParseObject.State result = saveTask.getResult();
Approximately 30 seconds - 2 minutes to get into next line of code.
This is lowest level of function I can get inside SDK.
Inside function save() or saveInBackground() there is this inner function in Java:
Task<Void> saveAsync(final String sessionToken, final Task<Void> toAwait) {
if (!isDirty()) {
return Task.forResult(null);
}
final ParseOperationSet operations;
synchronized (mutex) {
updateBeforeSave();
validateSave();
operations = startSave();
}
Task<Void> task;
synchronized (mutex) {
// Recursively save children
/*
* TODO(klimt): Why is this estimatedData and not... I mean, what if a child is
* removed after save is called, but before the unresolved user gets resolved? It
* won't get saved.
*/
task = deepSaveAsync(estimatedData, sessionToken);
}
return task.onSuccessTask(
TaskQueue.<Void>waitFor(toAwait)
).onSuccessTask(new Continuation<Void, Task<ParseObject.State>>() {
#Override
public Task<ParseObject.State> then(Task<Void> task) {
final Map<String, ParseObject> fetchedObjects = collectFetchedObjects();
ParseDecoder decoder = new KnownParseObjectDecoder(fetchedObjects);
return getObjectController().saveAsync(getState(), operations, sessionToken, decoder);
}
}).continueWithTask(new Continuation<ParseObject.State, Task<Void>>() {
#Override
public Task<Void> then(final Task<ParseObject.State> saveTask) {
ParseObject.State result = saveTask.getResult(); <--- THIS IS TAKING LONG TIME
return handleSaveResultAsync(result, operations).continueWithTask(new Continuation<Void, Task<Void>>() {
#Override
public Task<Void> then(Task<Void> task) {
if (task.isFaulted() || task.isCancelled()) {
return task;
}
// We still want to propagate saveTask errors
return saveTask.makeVoid();
}
});
}
});
}
From the docs:
Most save functions execute immediately, and inform your app when the save is complete. If you don’t need to know when the save has finished, you can use saveEventually instead.
It can take a long time because with saveEventually you are basically saying "save it soon". If you want to "save it as soon a possible" then use saveInBackground as described in the docs.
Further it says:
All calls to saveEventually (and deleteEventually) are executed in the order they are called, so it is safe to call saveEventually on an object multiple times. If you have the local datastore enabled, then any object you saveEventually will be pinned as long as that save is in progress. That makes it easy to retrieve your local changes while waiting for the network to be available.
Which means that you can save and modify the object locally multiple times and the latest version will be stored in the database as soon as the network connection is reestablished.
I have a requirement wherein I have to send saved API requests on a button click. These API requests are added to a list and this list is saved to SharedPreferences if the device is offline. Once the device regains connectivity, the saved requests should be sent on a click of a button. If one of the requests get a HTTP status code of 401, the whole process should stop. However, in case of other Exceptions, the process should not interrupted and the next saved request on the list should be sent. If a request succeeds, it is removed from the list of saved requests. At the end of the process, any requests that remain unsent are saved to SharedPreferences.
Now I have a special case for an Exception that I call InvalidRequestException. I want to remove the request from the list when it encounters this particular error, and at the same time I want to carry on sending the remaining requests in the list.
I modeled my code from this post. Here is the code for the method that kicks off the whole process:
public LiveData<UploadStatus> startUploading() {
MutableLiveData<UploadStatus> uploadStatus = new MutableLiveData<>();
compositeDisposable.add(paramRepository.getSavedOfflineRequest() // returns Observable<List<Request>>
.doOnComplete(() -> uploadStatus.setValue(UploadStatus.NO_ITEMS))
.flatMapIterable( requests -> {
requestList = requests;
requestListSizeText.set(Integer.toString(requestList.size()));
return requestList;
}) // observable should now be Observable<Request>
.flatMapCompletable(this::uploadProcess)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(() ->{
paramRepository.setOfflineRequestString(""); // clear saved offline requests from shared preferences
uploadStatus.setValue(UploadStatus.SUCCESS);
},
error -> {
if (error instanceof SessionExpiredException) {
uploadStatus.setValue(UploadStatus.LOGGED_OUT);
} else {
if(!requestList.isEmpty()) {
paramRepository.saveRequestsToPrefs(requestList);
} else {
paramRepository.deleteSavedRequests();
}
uploadStatus.setValue(UploadStatus.FAIL);
}
}
)
);
return uploadStatus;
}
The actual sending of saved requests happens in uploadProcess. This is where I attempt to catch the occurrence of InvalidRequestException and delete the request that encounters it:
private Completable uploadProcess(Request request) {
return apiService.transact(saleUrl, BuildConfig.ApiKey,request)
.doOnSubscribe(disposable -> {
uploadAttempts++;
})
.toMaybe()
.onErrorResumeNext(error -> {
if(error instanceof InvalidRequestException) {
requestList.remove(request);
if(requestList.isEmpty()) {
return Maybe.error(new OfflineTxnsNotUploadedException());
}
}
else if (error instanceof SessionExpiredException) // inform UI that session has expired
return Maybe.error(error);
else if (requestList.size() == uploadAttempts) { // nothing was uploaded
return Maybe.error(new OfflineTxnsNotUploadedException());
}
return Maybe.empty();
})
.flatMapCompletable(response -> {
requestList.remove(request);
successCount++;
successCountText.set(Integer.toString(successCount));
return createTransaction(request, response);
});
}
Now when I tested this, I found out that the whole stream stops whenever InvalidRequestException is encountered, which is not the behavior I want. I want to continue sending the other requests in the list. I actually removed the part where the request is removed from the list (requestList.remove(request);), and the stream continued and the next request was sent via apiService.transact().
Am I mistaken in assuming that returning Maybe.empty() would resume the emission of Observable<Request> from the flatMapIterable?
EDIT: It seems I am encountering a ConcurrentModificationException, that's why the stream terminates immediately and the other Requests are not sent. I will have to study this exception first.
As I noted in my edit, I wasn't able to catch the ConcurrentModificationException, thus the entire stream was interrupted. I was in fact, modifying the List<Request> that is being emitted into individual Observable<Request>, since requestList is only a shallow copy of the List<Request> emitted by getSavedOfflineRequest.
One solution I tried was to first serialize the list via Moshi, so that the unserialzied list will not hold any reference to the original list. I found out soon enough that requestList.remove(request) will return false, since I had yet to properly override the equals() and hashCode() method for the Request class. In the end, I just settled to create a "failed requests" list, adding Requests to this list whenever an error is encountered.
I am retrieving my response from my API with rxjava with funcs as follows:
#Override
public Single<MyInfoResponse> getMyInfoApiCall() {
return Rx2AndroidNetworking.get(ApiEndPoint.ENDPOINT_MY_INFO)
.addHeaders(mApiHeader.getProtectedApiHeader())
.build()
.getObjectSingle(MyInfoResponse.class);
}
Now ,I am retrieving this data and using it in my UI code( with usual compositedisposables) as follows:
#Override
public void onViewPrepared() {
getCompositeDisposable().add(getDataManager()
.getMyInfoApiCall()
.subscribeOn(getSchedulerProvider().io())
.observeOn(getSchedulerProvider().ui())
.subscribe(myInfoResponse -> {
getDataManager().updateMyManagerInfo(myInfoResponse);
if (myInfoResponse != null && myInfoResponse.getData() != null) {
getMvpView().updateMyRepo(myInfoResponse.getData());
}
}, throwable -> {
if (!isViewAttached()) {
return;
}
getMvpView().hideLoading();
// handle the error here
if (throwable instanceof ANError) {
ANError anError = (ANError) throwable;
handleApiError(anError);
}
}));
}
Now everytime I have internet connectivity and it tries to retrieve data it works fine, but as soon as I lose internet connectivity, I am trying to cache this data so it still displays on the UI until the network connectivity is back and ready to update data real time. How do I go about this in the easiest and the most ideal way possible?
I suggest you to use offline support using android Room Persistence.
Have a look at the google sample from the below link
https://developer.android.com/topic/libraries/architecture/room
In this case
Prefetch a request (so that it can return from cache when required at instant)
AndroidNetworking.get("https://fierce-cove-29863.herokuapp.com/getAllUsers/{pageNumber}")
.addPathParameter("pageNumber", "0")
.addQueryParameter("limit", "30")
.setTag(this)
.setPriority(Priority.LOW)
.build()
.prefetch();
First of all the server must send cache-control in header so that is
starts working.
Response will be cached on the basis of cache-control
max-age,max-stale.
If internet is connected and the age is NOT expired it will return
from cache.
If internet is connected and the age is expired and if server returns
304(NOT MODIFIED) it will return from cache.
If internet is NOT connected if you are using
getResponseOnlyIfCached() - it will return from cache even it date is
expired.
If internet is NOT connected , if you are NOT using
getResponseOnlyIfCached() - it will NOT return anything.
If you are using getResponseOnlyFromNetwork() , it will only return
response after validation from server.
If cache-control is set, it will work according to the
max-age,max-stale returned from server.
If internet is NOT connected only way to get cache Response is by
using getResponseOnlyIfCached().
I just went through the docs:
Refer here
I have a function which runs every 30 seconds, in which it fetches the latest data from the server and stores locally.
Currently, I'm deleting all the existing rows and reinserting all the data again, but I think this way is not the efficient one, I should be updating the data only if the local data and server data defers.
So how can I do that?
Here's what I'm doing:
DatabaseDao:
#Dao
public interface GeneralDatabaseDao {
#Query("DELETE FROM table_tables")
int deleteTables();
#Insert(onConflict = OnConflictStrategy.REPLACE)
void insertRestaurantTablesData(TablesModel... TablesModels);
}
Repository:
public LiveData<List<TablesModel>> getTablesData(int mLocationID) {
mTablesList = new MutableLiveData<>();
LiveData<TablesModel> mTablesData = mTableDataSource.getTablesData();
Observer<TablesModel> mObserver = tableModels -> {
mExecutors.diskIO().execute(() -> {
//Completed: delete old table data if there are conflicts.
if (tableModels != null) {
mDatabaseDao.deleteTables();
mDatabaseDao.insertTablesFromServer(tableModels.getTables());
} else {
Log.e(LOG_TAG, "Nothing: ");
}
});
Log.e("Handlers", "repository getTablesData");
};
if (!mTablesData.hasObservers()) {
mTablesData.observeForever(mObserver);
}
return mDatabaseDao.getTablesData(mLocationID);
}
I know I need to compare all the local rows to the data which I got from the server and then update only changed data, But for that, I need to query local data and check row by row in a loop and then update. I'm fine with doing that but is there any other efficient way to that?
No, there's no alternative. Moreover, if you delete and insert again, you will activate the UI refresh each time. Since you used live data to update UI only when it is needed, you need to update the database only when it is strictly necessary.
With the code you write, each time you check web service, UI will be refresh. Better check each time if there are rows to change and modify them.
I'm trying to use RxJava with Android to asynchronously update my view. When user clicks the movie from the list in the RecyclerView, I want to present him first with the movie from the database, if present. Then I want to fetch the latest information and update the database as well as UI. I'm trying to use concat method and its variant but it does not work.
I have skipped other codes only to post the relevant RxJava methods that are fetching data as the rest is working fine.
When I disable network connection with the code below (hence remote returns error), the code below does not display data from the database at all. Only it reports the error. Which means the local is not resolving.
public Flowable<Movie> getMovie(final int id) {
return Single.concat(mLocal.getMovie(id), mRemote.getMovie(id).doOnSuccess(data -> {
mLocal.save(data);
})).onErrorResumeNext(error->{
return Flowable.error(error);
});
}
And in this code, it works fine, except now that I don't get the error message (and rightly so, since I have replaced it with new stream from the database)
public Flowable<Movie> getMovie(final int id) {
return Single.concat(mLocal.getMovie(id), mRemote.getMovie(id).doOnSuccess(data -> {
mLocal.save(data);
})).onErrorResumeNext(error->{
return mLocal.getMovie(id).toFlowable();
});
}
Now, how can I get database data first and then fire network call next to update data and get errors from the database or network call?
UPDATE
The latest method code
// calling getMovie on mLocal or mRemote returns Single
public Flowable<Movie> getMovie(final int id) {
return Single.concat(mLocal.getMovie(id), mRemote.getMovie(id).doOnSuccess(data -> {
mLocal.insertMovie(data);
})).onErrorResumeNext(error -> {
return Flowable.error(error);
});
}
Here is how I call them
public void loadMovie(int id)
{
Disposable d = mRepo.getMovie(id)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread(), true)
.doOnSubscribe(subscription -> {
subscription.request(Long.MAX_VALUE);
//post progress here
})
.subscribe(data -> {
//onNext
},
error -> {
//onError
},
() -> {
//onComplete
}
);
mDisposables.add(d);
}
With affirmation that my code works and guides on troubleshooting from #akarnokd I found the latest code (see OP) works flawlessly. The result of RxJava chain is posted to LiveData object which should update View. Unfortunately it only posts the latest data (which is an error) and skips the first (which is the data from the database).
I will deal with that but since the post deals with RxJava, I will consider this solved!