Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Realm Notifications issue #1083

Closed
lawloretienne opened this issue May 2, 2015 · 17 comments
Closed

Realm Notifications issue #1083

lawloretienne opened this issue May 2, 2015 · 17 comments
Labels

Comments

@lawloretienne
Copy link

According to the docs https://realm.io/docs/java/latest/#notifications

If you have a background thread adding data to a Realm, your UI or other threads can get notified of changes in a realm by adding a listener, which is executed when the Realm is changed (by another thread or process)

However, I have a SyncAdapter which runs on a different thread then a Fragment which sets the SyncAdapter schedule. But if I write to the realm instance in the SyncAdapter, then the ChangeListener's onChange() method which is set up in the fragment does not get called.

How can I fix this?

AccountFragment.java

public class AccountFragment extends Fragment {

   // region Listeners
  private RealmChangeListener mAccountChangedListener = new RealmChangeListener() {
        @Override
        public void onChange() {
          // Update the UI ...
        }
  }
  // endregion

    // region Constructors
    public static AccountFragment newInstance() {
        AccountFragment fragment = new AccountFragment();
        Bundle args = new Bundle();
        fragment.setArguments(args);
        return fragment;
    }

    public AccountFragment() {
    }
    // endregion

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);

        // Create account, if needed
        mAndroidAccount = createSyncAccount(getActivity());

        /*
         * Turn on periodic syncing
         */
        ContentResolver.addPeriodicSync(
                mAndroidAccount,
                AUTHORITY,
                Bundle.EMPTY,
                SYNC_INTERVAL);

        ContentResolver.setSyncAutomatically(mAndroidAccount, AUTHORITY, true);

    }

   @Override
    public void onResume() {
        super.onResume();

        try {
            mRealm = Realm.getInstance(getActivity());
            mRealm.addChangeListener(mAccountChangedListener);

            loadAccount();
        } catch (RealmMigrationNeededException e) {
            // in this case you need migration.
            // https://github.com/realm/realm-java/tree/master/examples/migrationExample
        }
    }
}

AccountSyncAdapter.java

public class AccountSyncAdapter extends AbstractThreadedSyncAdapter {

    /**
     * Set up the sync adapter
     */
    public AccountSyncAdapter(Context context, boolean autoInitialize) {
        super(context, autoInitialize);
    }

    /**
     * Set up the sync adapter. This form of the
     * constructor maintains compatibility with Android 3.0
     * and later platform versions
     */
    public AccountSyncAdapter(
            Context context,
            boolean autoInitialize,
            boolean allowParallelSyncs) {
        super(context, autoInitialize, allowParallelSyncs);
    }

    /*
     * Specify the code you want to run in the sync adapter. The entire
     * sync adapter runs in a background thread, so you don't have to set
     * up your own background processing.
     */
    @Override
    public void onPerformSync(
            Account account,
            Bundle bundle,
            String s,
            ContentProviderClient contentProviderClient,
            SyncResult syncResult) {

        try {
            mRealm = Realm.getInstance(mContext);
            loadAccount();
        } catch (RealmMigrationNeededException e) {
            // in this case you need migration.
            // https://github.com/realm/realm-java/tree/master/examples/migrationExample
        }

    }

    private void loadAccount(){
        // Perform Synchronous API calls ...
        // Perform Realm Transaction ...
    }

}
@emanuelez
Copy link
Contributor

In order for notifications to work properly the receiving thread must be a Looper/Handler thread, such as the UI thread. In our repo we provide several examples showing how to use this feature.

Does this help?

@cmelchior
Copy link
Contributor

Also be aware that if you are using the official guide from Google on SyncAdapters, they run them in a separate process' which Realm doesn't current support.

@lawloretienne
Copy link
Author

I thought the add change listener works across multiple threads. Is that
not the case?
On May 4, 2015 12:43 AM, "Christian Melchior" notifications@github.com
wrote:

Also be aware that if you are using the official guide from Google on
SyncAdapters, they run them in a separate process' which Realm doesn't
current support.


Reply to this email directly or view it on GitHub
#1083 (comment).

@cmelchior
Copy link
Contributor

It does work across multiple threads, but not across processes. I made an example here https://github.com/realm/realm-java/tree/cm-example-syncadapter that can hopefully provide some inspiration. See the EntryListFragment and SyncAdapter.

@lawloretienne
Copy link
Author

So i see that you are adding and removing the ChangeListener in onAttach() and onDetach() but what exactly is triggering those to get called? It looks like you are doing the realm transaction inside of the SyncAdapter which is what I am doing, except the addChangeListener does not get called in my Fragment.

@lawloretienne
Copy link
Author

I also noticed in the SyncAdapter much like the documentation (http://realm.io/docs/java/latest/#transaction-blocks), your transaction block does not have a copyToRealm statement, is that not required in a transaction block or is that necessary at all times with or without a transaction block?

@lawloretienne
Copy link
Author

The mSyncStatusObserver looks like it's the only listener that received a callback from the ContentProvider update. But the Realm ChangeListener does not look like it will ever get triggered.

@cmelchior
Copy link
Contributor

copyToRealm() is just another way of adding data to a Realm. Using that or createObject() shouldn't matter as far as the change listener is concerned, but both must be called from inside a transaction block.

Without seeing all your code it is hard to tell why it doesn't behave as expected, but the example I provided does work and the the change handler is called as expected.

@lawloretienne
Copy link
Author

The documentation for transaction blocks
http://realm.io/docs/java/latest/#transaction-blocks only shows the
createObject() method being called and not the copyToRealm() method being
called inside of the transaction block. I was thinking both methods had to
be called, but maybe this is not the case.

So the example that you provided in the experimental directory has a
SyncAdapter wired up to a ContentProvider and uses a ContentObserver to
observer changes in the provider. How does this ContentObserver trigger
Realm's addChangeListener callback?

On Mon, May 4, 2015 at 12:47 PM, Christian Melchior <
notifications@github.com> wrote:

copyToRealm() is just another way of adding data to a Realm. Using that
or createObject() shouldn't matter as far as the change listener is
concerned, but both must be called from inside a transaction block.

Without seeing all your code it is hard to tell why it doesn't behave as
expected, but the example I provided does work and the the change
handler is called as expected.


Reply to this email directly or view it on GitHub
#1083 (comment).

-Etienne Lawlor (Software Developer)

@cmelchior
Copy link
Contributor

I'm sorry if the ContentProvider confuses you. It has nothing to do with the example but was just part of the code in Google's example. My main point was showing that changes to a Realm from inside a SyncAdapter was detected by the fragment.

Regarding copyToRealm() and createObject() then you don't call both. It is usually one or the other as copyToRealm() is used with standalone objects.

The difference is that standalone objects are created using new Foo() and are completely normal objects that are to saved in the realm until using copyToRealm() while createObject() creates an object immediately (with default values).

@lawloretienne
Copy link
Author

Okay so I'm all clear on the copyToRealm() and createObject() methods.

I figured that the ContentProvider was not critical to showcasing a SyncSdapter working with Realm. However, I am doing something nearly identical as shown by the code snippet at the top of this issue but the changes to Realm are not being detected inside the Fragment.

@cmelchior
Copy link
Contributor

Are you able to share you source code? You can do so privately through help@realm.io if you want. Bcause without seeing what your entire application is doing it is hard to tell what could be wrong

@cmelchior
Copy link
Contributor

Hi @lawloretienne
Thank you for the code. I think the problem is in your SyncService, it is running in a separate process which Realm currently doesn't support ( http://realm.io/docs/java/latest/#current-limitations) . It should work if you change

        <service
            android:name="...services.SyncService"
            android:exported="true"
            android:process=":sync">
            <intent-filter>
                <action android:name="android.content.SyncAdapter"/>
            </intent-filter>
            <meta-data android:name="android.content.SyncAdapter"
                android:resource="@xml/syncadapter" />
        </service>

to

        <service
            android:name="...services.SyncService"
            android:exported="true">
            <intent-filter>
                <action android:name="android.content.SyncAdapter"/>
            </intent-filter>
            <meta-data android:name="android.content.SyncAdapter"
                android:resource="@xml/syncadapter" />
        </service>

While looking into this I also noticed that committing a transaction on a Thread (eg. UI thread) will currently not result in a OnRealmChanged event on the same thread. We are going to change this so the changelistener on the same thread is also going to be called as it will make updating the UI easier.

@lawloretienne
Copy link
Author

Nice. So it now works since the SyncService is not in a separate process. What are the downsides of not having the SyncService in a separate process?

Also, I am able to commit a transaction on the UI thread and the ChangeListener's onChange method gets called. I am not sure what you are referring to?

@cmelchior
Copy link
Contributor

The downside is that if you SyncService crashes it will take your application with it.

My last statement was referring to that RealmChangeListeners are currently not called on the same thread that does the commit, eg. doing the below will not trigger the listener, but if you are not running into issues with this then just ignore it :)

realm.addChangeListenter(new RealmChangeListener() {
  public void onChange() {
    ....
  }
})
realm.beginTransaction();
realm.createObject(Foo.class);
realm.commitTransaction()

@lawloretienne
Copy link
Author

Thanks for all the help!! Much appreciated.

@deanezra
Copy link

deanezra commented Nov 4, 2015

Hi, I'm getting a 404 when trying to access the example sync adapter code:

https://github.com/realm/realm-java/tree/cm-example-syncadapter

Please could you tell me its new location?

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Mar 17, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

No branches or pull requests

5 participants