Room Update the data only if it changed from the server - android

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.

Related

How to do additional logic onSuccess case in RxJava

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

Single.concat report error and continue to the next

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!

Is it possible to send a synchronous request in the Firebase?

I'm using Firebase Android SDK and became interested in sending synchronous request instead of asynchronous. According to the documentation, in any request callbacks are presented. But what about the synchronicity?
Thanks!
There is no way to synchronously load data from the Firebase Database.
While it is common for developers new to Firebase to wish for a synchronous method, it simply doesn't fit with Firebase's data synchronization model. Also see my answer here: Setting Singleton property value in Firebase Listener
It is not possible to load data synchronously with the official SDK. However, you can access all the data in firebase using the REST API. This would allow you to make synchronous calls. As mentioned about, Firebase is a realtime database and you will be missing the feature of updates when your data changes.
I made a simple class to call tasks synchronously in Android.
Note that this is similar to Javascript's async await function.
Check my gist.
TasksManager.class
public class TasksManager {
...
public ExecutorService getExecutor() {
if (mDefaultExecutor == null || mDefaultExecutor.isShutdown() || mDefaultExecutor.isTerminated()) {
// Create a new ThreadPoolExecutor with 2 threads for each processor on the
// device and a 60 second keep-alive time.
int numCores = Runtime.getRuntime().availableProcessors();
ThreadPoolExecutor executor = new ThreadPoolExecutor(
numCores * 2,
numCores * 2,
60L,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>()
);
mDefaultExecutor = executor;
}
return mDefaultExecutor;
}
public static <TResult> Task<TResult> call(#NonNull Callable<TResult> callable) {
return Tasks.call(getInstance().getExecutor(), callable);
}
}
Here's a sample code to use it.
TasksManager.call(() -> {
Tasks.await(AuthManager.signInAnonymously());
// You can use multiple Tasks.await method here.
// Tasks.await(getUserTask());
// Tasks.await(getProfileTask());
// Tasks.await(moreAwesomeTask());
// ...
startMainActivity();
return null;
}).addOnFailureListener(e -> {
Log.w(TAG, "signInAnonymously:ERROR", e);
});
While it is not possible to load data from the FirebaseDatabase in a synchronous way, it is possible to wait for the load to finish synchronously.
You can wrap your value listener in a CountDownLatch and count down,
once the onDataChange or onCancelled implementation is called.
This is actually what the Tasks api is doing internally if you call Tasks.await(someTask).
You should use the value listener for single event listening, because in this case I assume you don't want continued updates. And use a proper timeout for the CountDownLatch, since Firebase won't timeout, ever.
reference.addListenerForSingleValueEvent(...);
You also have to take into account, that if you have the FirebaseDatabase
cache enabled, the first result might not be the actual value on the
server.
I have to add: While this might work, it is against the idea how firebase is designed and supposed to be used, as Frank already said.
If you are using Kotlin, add an extension function:
private suspend fun <TResult> Task<TResult>.await(): TResult? {
return try {
Tasks.await(this)
} catch (e: Exception) {
null
}
}
Now you can do
val snapshot = fireStore.collection(USER_ROOT_PATH).document(documentPath)?.get()?.await()

Android Parse SDK issue

I'm using parse to store my data and do a lot of queries while using my program.
The issue is that after about +/-20 similar queries, parse findInBackground() or getFirstInBackground() doesn't return a callback and app stuck at that possition.
My query code:
ParseQuery<OptionCodeDTO> mQuery;
mQuery = ParseQuery.getQuery(OptionCodeDTO.class);
mQuery.whereEqualTo("code", prCode);
mQuery.getFirstInBackground(new GetCallback<OptionCodeDTO>() {
#Override
public void done(OptionCodeDTO optionCodeDTO, ParseException e) {
if (isVisible()) {
if (e == null) {
OptionCode opCode = new OptionCode(optionCodeDTO);
mCodes.push(opCode);
printCodes();
prDescrLayout.setVisibility(View.VISIBLE);
prDescProgress.setVisibility(View.GONE);
mPRLable.setVisibility(View.GONE);
} else {
if (e.getCode() == ParseException.CONNECTION_FAILED) {
mPrDescr.setText(R.string.dtc_lookup_check_network);
} else if (e.getCode() == ParseException.OBJECT_NOT_FOUND) {
mPrDescr.setText(R.string.pr_lookup_code_not_found);
} else {
mPrDescr.setText(R.string.dtc_lookup_other_problems);
}
prDescrLayout.setVisibility(View.VISIBLE);
prDescProgress.setVisibility(View.GONE);
}
}
}
});
First of all, if your app ANRs (application not responding) because of something from UI thread that relies on background threads, that is incorrect architecture.
Probably you have to optimize your app's interaction with Parse. Generally it is a bad practice to make lots of saveInBackground, for example from inside a loop. You can add objects, those need to be saved, to a list and then use ParseObject.saveAllInBackground(objectList)
Also an idea to optimize is to use local storage - android's built in SQLite. For example if your app relies on something being saved to Parse, the logic is like this:
When saving object first you save to local DB and run a saveInBackground method.
When fetching objects you first fetch from your local DB and then run a getInBackground method, which inside a callback persists the information to your local DB.
This way you will make your app usable without internet connection.

Network query returns local datastore pointer

I have recently managed to switch to Local Datastore, moving all the needed parts of the database to the client. Furthermore I send a push notification whenever data changes so that the client data stays up to date.
Now my problem is that one of the pointers in my data keeps returning the old pointer after updating.
Here is the code that I use:
public ParseQuery<CircuitUnit> circuitUnits() {
ParseQuery<CircuitUnit> query = CircuitUnit.getQuery();
query.include(CircuitUnit.circuits);
query.include(CircuitUnit.guard);
query.setLimit(1000);
return query;
}
circuitUnits().findInBackground(
new FindCallback<CircuitUnit>() {
#Override
public void done(List<CircuitUnit> objects, ParseException e) {
Log.d(TAG, "Pinning circuitUnits " + objects.size());
// debugging loop of results
for (CircuitUnit circuitUnit : objects) {
if (circuitUnit.getObjectId().equals("TXEZDch6wK")) {
Guard guard = circuitUnit.getGuard();
Log.d(TAG, "Guard: " + guard.getObjectId());
}
}
// This function updates the Local datastore with the newly fetched objects
// updateServerDataPin(objects, PinsGlobal.CIRCUIT_UNITS,
e, callback);
}
});
The Logged output is: "Guard: L440gHXKTY" where it should be SDDg23rR4h
I have added a screenshot of the CircuitUnit table showing that SDDg23rR4h is indeed the expected objectId of the pointer.
I tried to create a sample project showing the issue as part of the bug report for parse.com, but that returned the correct pointer.
My theory is then that this problem lies in having data in the Local Datastore which somehow interferes with the result. Not that I understand why, cause I am clearly querying the network and not locally.
It seems as if the include statements are ignored and simply filled with the known data from the Local Datastore.
Has anyone experienced something similar or have a possible explanation to this behaviour?

Categories

Resources