0

So I have created a very standard sync adapter (using this fantastic tutorial) and during onPerformSync I run some realm transactions in a method called syncDatastore within my DataManager class. The issue is when the sync adapter tries to perform the sync, I get

java.lang.IllegalStateException: Realm access from incorrect thread. Realm objects can only be accessed on the thread they were created.

Here is an excerpt from my SyncAdapter:

@Override
public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult) {
    try {
        // Get the auth token for the current account
        String authToken = _accountManager.blockingGetAuthToken(account, AccountGeneral.AUTHTOKEN_TYPE_FULL_ACCESS, true);

        // run network and database operations
        dataManager.syncDatastore();

    } catch (Exception e) {
        e.printStackTrace();
    }
}

I initialize the RealmConfiguration in my Application class with:

RealmConfiguration realmConfiguration = new RealmConfiguration.Builder(this).build();
Realm.setDefaultConfiguration(realmConfiguration);

And an example of how it is used in my DataManager:

private Realm realm = Realm.getDefaultInstance();

public void syncDatastore() {
    postResources();
    pushDataToServer();
    getDataFromServer();
}

private void postResources() {
    ArrayList<Client> clients = new ArrayList<>();
    clients.addAll(realm.where(Client.class).equalTo("isSynced", false).equalTo("apiId", "0").findAll());
    Log.e("clients count", String.valueOf(clients.size()));
    for (Client c : clients) {
        createClientResource(c);
    }
}

Please note I have tried to remove android:process=":sync" from my service declaration in the manifest as outlined here but to no avail. I am also quite new to both SyncAdapters and Realm, so any help would be most appreciated.

barnacle.m
  • 2,070
  • 3
  • 38
  • 82
  • Where does the Realm instance used in this line `clients.addAll(realm.where(Client.class).equalTo("isSynced", false).equalTo("apiId", "0").findAll());` come from? Where is it initialized? – EpicPandaForce Aug 07 '16 at 19:31
  • Please see my edit, in `DataManager` – barnacle.m Aug 07 '16 at 19:34
  • oh and your DataManager is instantiated along with Application at some point, so the Realm that belongs to it is on the UI thread. Makes sense. That won't work. – EpicPandaForce Aug 07 '16 at 19:35
  • Well I just followed the realm docs on where to instantiate realm, which is in the Application manager, but this doesn't seem to correspond to how it would work with a sync adapter. – barnacle.m Aug 07 '16 at 19:38
  • `onPerformSync` runs on a different thread, so you need to create the Realm instance inside it. – EpicPandaForce Aug 07 '16 at 19:49

1 Answers1

2

Realm instances are thread confined, so you need a new Realm instance on your background thread (the background thread on which the sync is happening).

@Override
public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult) {
    Realm realm = null;
    try {
        realm = Realm.getDefaultInstance();
        // Get the auth token for the current account
        String authToken = _accountManager.blockingGetAuthToken(account, AccountGeneral.AUTHTOKEN_TYPE_FULL_ACCESS, true);

        // run network and database operations
        dataManager.syncDatastore(realm);
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        if(realm != null) {
            realm.close();
        }
    }
}

public void syncDatastore(Realm realm) {
    postResources(realm);
    pushDataToServer();
    getDataFromServer();
}

private void postResources(Realm realm) {
    RealmResults<Client> clients = realm.where(Client.class).equalTo("isSynced", false).equalTo("apiId", "0").findAll();
    Log.e("clients count", String.valueOf(clients.size()));
    for (Client c : clients) {
        createClientResource(c);
    }
}
EpicPandaForce
  • 79,669
  • 27
  • 256
  • 428
  • Thanks for the response. Unfortunately using this code gives a `java.lang.IllegalStateException: This Realm instance has already been closed, making it unusable` error when calling `realm.beginTransaction()` in other methods in `DataManager` – barnacle.m Aug 07 '16 at 19:36
  • I should also mention that DataManager is a singleton class. But I don't think that will make any difference. – barnacle.m Aug 07 '16 at 19:45
  • the thing is though, syncDatastore doesn't run on a blocking thread, hence why it closes just as soon as it opens in this code. – barnacle.m Aug 07 '16 at 19:52
  • Well you're closing it **somewhere** so in order to tell what's up, I'd have to see more of the code – EpicPandaForce Aug 07 '16 at 19:53
  • Wait a second. So you're saying your `onPerformSync` is asynchronous? As per https://developer.android.com/reference/android/content/AbstractThreadedSyncAdapter.html the `onPerformSync` is running on a background thread, and the logic on it should be synchronous. – EpicPandaForce Aug 07 '16 at 19:55
  • In that case you'll need to open the Realm instance where your Volley request has been executed and you're manipulating its results. :o – EpicPandaForce Aug 07 '16 at 20:20
  • If I post an example of one if my volley requests, would you be so kind as to show me how to handle opening the realm instance therein? – barnacle.m Aug 07 '16 at 20:27
  • Well I hope so. Volley is notorious for running the request on the background thread, but the `onSuccess` part of it is on the UI thread, which means that most of this `downloaded stuff => Realm` direction data flow should be a custom request. – EpicPandaForce Aug 07 '16 at 21:02
  • Hi,Were you successful in getting through this. Since I am also planning to use sync adapter along with Realm but little bit confused that I should go ahead or not. Please help. – Shraddha Shravagi Jun 01 '17 at 08:19
  • What I said in the answer should work without problems. Although Volley is bad for this use case and I recommend Retrofit. – EpicPandaForce Jun 01 '17 at 08:24