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

Are Realm insertions known to be slow? App crashes when trying to retrieve recently parsed data. #3047

Closed
MiralDesai opened this issue Jun 22, 2016 · 10 comments
Labels

Comments

@MiralDesai
Copy link

MiralDesai commented Jun 22, 2016

Goal

What do you want to achieve?

To use a RealmObject immediately after it has been saved (or thought to be saved I guess).

Expected Results

Data should be accessible right away.

Actual Results

App crashes with NullPointerExceptions because data does not exist. Restarting the app and everything works fine.

java.lang.RuntimeException: Unable to start activity ComponentInfo{com.app/com.app.navigation.Main}: java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.String com.app.realm.User.realmGet$firstName()' on a null object reference
                                                                 at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2416)
                                                                 at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2476)
                                                                 at android.app.ActivityThread.-wrap11(ActivityThread.java)
                                                                 at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1344)
                                                                 at android.os.Handler.dispatchMessage(Handler.java:102)
                                                                 at android.os.Looper.loop(Looper.java:148)
                                                                 at android.app.ActivityThread.main(ActivityThread.java:5417)
                                                                 at java.lang.reflect.Method.invoke(Native Method)
                                                                 at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
                                                                 at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
                                                              Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.String com.app.realm.User.realmGet$firstName()' on a null object reference

Steps & Code to Reproduce

I make an API call to get the user data when they first sign in. The next place they go to is the main activity. Where I want to use that data. The API call goes off, returns and starts parsing. I'm manually parsing right now. I have a utility method to then open, a realm, beginTransaction(), copyToRealmOrUpdate(user) then to commitTransaction(). Then to realm.close();

I find the correct user json, then parse it like this:

user.firstName = returnStringValueFromJsonIfExistsOrNull(first_name, json);
user.lastName = returnStringValueFromJsonIfExistsOrNull(last_name, json);
RealmUtils.copyOrUpdate(user);

To me it seems like the data has not finished parsing. One thing to note is sometimes it works, sometimes it doesn't. Which of course I don't like. To get the user object when using it I have a little utlity method again.

Realm realm = Realm.getDefaultInstance();
User user = realm.where(User.class)
              .equalTo("id", myId)
              .findFirst();
realm.close();
return user;

I don't like how temperamental it is. So all in all I'm wondering, does Realm take longer than expected to insert data? Maybe different users with different amounts of data is causing it to be temperamental. Is this why I can't use my user object immediately? I'd like to understand a bit more about how this all works. Let me know if you need anything else to get a better picture.

Version of Realm and tooling

Realm version(s): 1.0.0

Android Studio version: 2.1.2

Which Android version and device: Nexus 5, Marshmallow. Nexus 6P Android N DP4. Not specific to device.

@cmelchior
Copy link
Contributor

If you are saving data on a background thread, there can be a small delay before the data is available on the UI thread because we use the Looper to coordinate it. I cannot see how you are saving your data in the above description?

Generally using change listeners to wait for the result to appear is safer than assume when it is ready.

@MiralDesai
Copy link
Author

Sorry just had lunch.

I thought change listeners only worked after getting the data. So are you saying I can use it even before the data exists, and it will wait for it?

When my api call returns I send the JSON to my parser class, and use the methods I quoted above to extract the correct data.

user.firstName = returnStringValueFromJsonIfExistsOrNull(first_name, json);
user.lastName = returnStringValueFromJsonIfExistsOrNull(last_name, json);
RealmUtils.copyOrUpdate(user);

the helper method just checks if the key exists, or returns null. I'll try a change listener and see what happens.

@Zhuinden
Copy link
Contributor

Zhuinden commented Jun 22, 2016

If you are writing to the Realm on the UI thread, then your data will be accessible only on the next looper event.

If you are writing to the Realm on a background thread, then that thread doesn't get auto-updated, so it won't see the data. (Imagine it like so, a background thread should open the realm, smash in the data, close the realm in a finally block and then just end).

If you are writing to the Realm on a background thread and waiting for that to appear on the UI thread, then that will only be accessible on the next Looper event.

Which means you should add a change listener to the relevant table.

 RealmResults<User> users = realm.where(User.class).findAll();
 RealmChangeListener<RealmResults<User>> changeListener = new RealmChangeListener<RealmResults<User>>() {
      public void onChange(RealmResults<User> element) {
           //data available on update
      }
 }; //keep me as a field
 users.addChangeListener(changeListener);

Because then you can update your stuff whenever the Realm underneath changes. If you're in a RecyclerView for example, that's when you call notifyDataSetChanged(), and your adapter handles all the magic.

Oh in case you're curious, you're running into this 0.89.0 breaking change:

Realm.refresh() and DynamicRealm.refresh() on a Looper no longer have any effect. RealmObject and RealmResults are always updated on the next event loop.

Previously a call to commitTransaction() forced a refresh on the given thread, but now only looper threads are updated, and only on the next looper event.

@MiralDesai
Copy link
Author

I guess I need to use placeholder values to start with, then update to the real values when they are available. Quite hard to debug because when I add a breakpoint, suddenly the parser has enough time to finish so everything seems fine.

@Zhuinden
Copy link
Contributor

Zhuinden commented Jun 22, 2016

You don't need placeholder values, you just need a RealmResults that you can bind your change listener to.

Well, either that, or execute the synchronous transaction on a background thread and notify your Activity by sending an event to the main looper, or just use executeTransactionAsync() and use OnSuccess callback (note: OnSuccess works only on looper thread, meaning the UI thread)

Make sure you don't close your Realm while you're waiting for the result of said async transaction though.

@MiralDesai
Copy link
Author

MiralDesai commented Jun 22, 2016

This is all to set up a navigation drawer on my main activity.

Here is what I'm doing, a method gets called to collect the data shown is the drawer. createNavigationDrawer(user); passes that data to a RecyclerView adapter

Realm realm = Realm.getDefaultInstance();
        user = realm.where(User.class)
                .equalTo("id", 5)
                .findFirst();

        RealmChangeListener<User> realmChangeListener = new RealmChangeListener<User>() {
            @Override
            public void onChange(User element) {
                if (user.firstName != null) {
                    createNavigationDrawer(user);
                }
            }
        };
        user.addChangeListener(realmChangeListener);
        if (user.firstName != null) {
            createNavigationDrawer(user);
        }

I'm getting an NPE when trying to calling addChangeListener();. Can I not add a change listener to a null object, so that it does it populated later...?

java.lang.RuntimeException: Unable to start activity ComponentInfo{com.app/com.cakeapp.navigation.main}: java.lang.NullPointerException: Attempt to invoke virtual method 'void com.app.realm.User.addChangeListener(io.realm.RealmChangeListener)' on a null object reference

@Zhuinden
Copy link
Contributor

Well, no, you cannot add a RealmChangeListener to null, which is why I said you should try using the other alternatives I mentioned.

Or I just thought of this, but just do executeTransaction() as you normally would synchronously, then do

Handler handler = new Handler(Looper.getMainLooper());
handler.post(new Runnable() {
     @Override
     public void run() {
           user = realm.where(User.class)
            .equalTo("id", 5)
            .findFirst();
          createNavigationDrawer(user);
     }
});

@MiralDesai
Copy link
Author

Hey, the problem sort of resolved itself because I updated my api call to be much smaller (it was in the works already and a planned inclusion). I guess I kind of side stepped the issue which isn't good, but at least the my app is faster.

@Zhuinden
Copy link
Contributor

You should really just post the update stuff with handler.post()

@kneth
Copy link
Member

kneth commented Jun 23, 2016

@MiralDesai If you have your issue solved, please close it.

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

7 participants