3

I am having an issue where Realm sometimes returns me different data every time I do the same query. Currently I am using an SyncAdapter for uploading. The idea is that we are trying to implement offline mode.

So when the User creates an item it get's added to Realm db. I am generating the ID for that item manually by getting the maxId and adding 1000 to it. After that I am sending the itemID to the UploadSyncAdapter where I get the itemById and send it to the backend and the backend returns me the item with the real ID. So after that I delete the old item and just insert the new item into Realm.

After I go back and read the data it returns every second time for example an array of size 115 data and the other time an array of size 116. I even search for the item with the debugger by ID and it really once finds the item, the second time it doesn't. But it looks like after I rebuild the project the item is there and it works normally, or if I rerun the app by using Instant Run.

Any help appreciated...

UPDATE Btw I am using RXjava to get the data from the server but it is being subscribed and observed on the current thread (SyncAdapter thread).

Here's the code:

@Override
public void onNext(TaskResponse taskResponse) {
     tasksDatabaseManager.deleteTaskById(taskId);
     List<Task> tasks = taskResponse.getTaskDataList();
     tasksDatabaseManager.insertTasks(tasks);
 }

public void deleteTaskById(int taskId){
    Realm realm = Realm.getDefaultInstance();
    realm.beginTransaction();
    RealmResults<Task> rows = realm.where(Task.class).equalTo(ID, taskId).findAll();
    rows.deleteAllFromRealm();
    realm.commitTransaction();
    realm.close();
}

private void copyOrUpdateTasks(List<Task> tasksList){
    Realm realm = Realm.getDefaultInstance();
    ArrayList<Task> updatedTaskList;
    //first initialize task permissions
    updatedTaskList = filterTasksByPermission(tasksList);
    //initialize custom task data
    for (Task task : updatedTaskList) {
        initializeTaskCustomFields(task);
    }
    //save new data
    Log.d(TAG, "tasks number before update: " + getTasks().size());
    realm.beginTransaction();
    realm.copyToRealmOrUpdate(updatedTaskList);
    realm.commitTransaction();
    realm.close();

    Log.d(TAG, "tasks number after update: " + getTasks().size());
}

In the filterTasksByPermission I just calculate some permissions for the tasks, but the task is being returned in the list. And in the initializeTaskCustomFields I am also just calculating 2 fields of the object before saving to Realm (so that I have those values also saved in Realm)

Tooroop
  • 1,824
  • 1
  • 20
  • 31
  • I would need to see your transaction code where you do the deleting and the insertion of the new element to give a proper answer. My guess is that you're using multiple transactions on the background thread, and not evaluating the query to obtain the elements inside the transaction. Also, make sure you close the Realm on the background thread (so the sync adapter's thread) when you're done with the operation, and re-open the Realm instance for the next operation. – EpicPandaForce Aug 25 '16 at 07:07
  • Oh look, I was right: multiple transactions and doing the queries to determine the parameters of the write outside of the transaction (although it'd be nice to see the code for `filterTasksByPermission`) – EpicPandaForce Aug 25 '16 at 09:29
  • Also you should consider putting `realm.close()` in `finally {`. – EpicPandaForce Aug 25 '16 at 09:32
  • I fact in the filterTasksByPermission I also have cases where I open a transaction and close it. But why does this matter if I there are multiple transactions if I am commiting them and closing Realm every time? – Tooroop Aug 25 '16 at 10:35
  • Because it's a non-looper thread and therefore doesn't update its Realm instance. Although if you query inside the transaction and NOT outside of it, you should still be able to see the latest data. – EpicPandaForce Aug 25 '16 at 11:21
  • This is weird since I have also a SyncAdapter that downloads data from the server, and it uses the same insert method, but the data displays just fine. So then what solution do you propose for this case? – Tooroop Aug 25 '16 at 11:35
  • Wait a second, now that I look at it a bit more, the question is regarding the number of elements before and after the commit, correct? It's expected that after commit, `getTasks()` outside of the transaction will return an old version of the data: non-looper threads don't autoupdate. You should check the new size before the commit, that's where you see the latest version. Also, you should be able to see this data on the main thread when `RealmChangeListeners` are called. – EpicPandaForce Aug 25 '16 at 11:51
  • I didn't talk about number of elements directly before and after the commit in this part of code. After this part of code finishes the number of items in Realm is correct. So when the UploadAdapter finishes I get a notification that it finished. Now after this I go to the screen where I see the list of those items, and the new item is not there, when I click on an item I go to the Item details, and after that if I click back again I go to the ItemsList screen again and this time the item is there. And that repeats every time I change screens. And this doens't happen only on those screens. – Tooroop Aug 25 '16 at 12:05
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/121839/discussion-between-tooroop-and-epicpandaforce). – Tooroop Aug 25 '16 at 12:50
  • So the issue was that sync adapters are on non-looping background threads, and could open Realm instances where the UI thread's transaction hasn't been committed yet. This is why I personally prefer to have 1 transaction per background thread - transactions force data to be the latest no matter what. – EpicPandaForce Aug 30 '16 at 05:36

1 Answers1

7

After some researching I found out what was the problem. But please correct me if I'm wrong. I replaced my Subscribers.io() with Subscribers.newThread() in my Rx calls and now it works fine. So my theory is:

Before calling my UploadAdapter to upload the changed data I am using an Rx call with Subscribers.io() to insert my item in the database. Subscribers.io() uses a threadpool to reuse threads, or creates new threads if neccessary. So let's say it spawns a thread called "A". Thread A get's a Realm instance (let's say that Realm snapshot is "1") and inserts into it the created item. After that SyncAdapter get's called and it also get's a new Realm instance with the same Realm snapshot "1". After SyncAdapter finishes uploading data to the server it deletes the old item, and insert the new item it got from the server. So after this Realm data changed so the newest Realm snapshot is now "2". SyncAdapter sends a broadcast to the Activity that the upload is finished and it should get the new data from the Realm database.

For reading data from Realm I am also using Rx with Subscribers.io(). So when requesting new data from Realm the Subscribers.io() has already a thread in it's pool that is waiting to be reused, and that's thread "A". And since this thread is a non Looper thread it doesn't know that Realm data changed and it still uses the Realm snapshot "1", so that's why I get old data from Realm. And after refreshing a few times Subscribers.io() probably creates a new thread, let's say thread "B".

So thread "B" also get's a Realm snaphot, and this time it is the newest snapshot, so snapshot "2". And it returns the correct data.

So when using Subscribers.newThread() it always creates a new thread and it always has the newest Realm snapshot.

Here is a link regarding the difference between Subscribers.io() and Subscribers.newThread(): Retrofit with Rxjava Schedulers.newThread() vs Schedulers.io()

Hope this helps someone!

Community
  • 1
  • 1
Tooroop
  • 1,824
  • 1
  • 20
  • 31