23

I have an application with a LoginActivity, that when the user login correctly, I register to receive messages. And the LoginActivity jumps to MainActivity. The arriving messages are supposed to be stored in database (Realm), to recover from a Realm instance in Main.

But when the message arrives It crash realm launching this errror:

Exception in packet listener
    java.lang.IllegalStateException: Realm access from incorrect thread. Realm objects can only be accessed on the thread they were created.
    at io.realm.BaseRealm.checkIfValid(BaseRealm.java:383)
    at io.realm.Realm.executeTransactionAsync(Realm.java:1324)
    at io.realm.Realm.executeTransactionAsync(Realm.java:1276)
    at es.in2.in2tant.LoginActivity.newMessageReceived(LoginActivity.java:124)
    at es.in2.in2tant.Connection.Connection$4$1.processMessage(Connection.java:227)
    at org.jivesoftware.smack.chat.Chat.deliver(Chat.java:180)
    at org.jivesoftware.smack.chat.ChatManager.deliverMessage(ChatManager.java:351)
    at org.jivesoftware.smack.chat.ChatManager.access$300(ChatManager.java:53)
    at org.jivesoftware.smack.chat.ChatManager$2.processPacket(ChatManager.java:162)
    at org.jivesoftware.smack.AbstractXMPPConnection$4.run(AbstractXMPPConnection.java:1126)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1113)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:588)
    at java.lang.Thread.run(Thread.java:818)

I'm a bit lost on how Realm works, and I don't know how to make realm accessible across the application without a crash and keep storing this received messages from LoginActivity. Some help, or approaches to achieving this?

LoginActivity.java:

public class LoginActivity extends AppCompatActivity implements ConnectionConnectResponse {
.....
protected void onCreate(Bundle savedInstanceState) {
//Realm Init config:
        Realm.init(this);
        RealmConfiguration realmConfiguration = new RealmConfiguration.Builder().build();
        Realm.deleteRealm(realmConfiguration); // Clean slate
        Realm.setDefaultConfiguration(realmConfiguration); // Make this Realm the default


@Override
    public void newMessageReceived(final ChatMessage message) {
        Logger.d("NEWMESSAGERECEIVED :" + message);


        realm.executeTransactionAsync(new Realm.Transaction() {
            @Override
            public void execute(Realm realm) {

                Message receivedMessage = realm.createObject(Message.class, message.id);
                receivedMessage.setBodyMessage(message.message);
                receivedMessage.setFrom(message.from);
                receivedMessage.setTo(message.to);
                receivedMessage.setDelivered(false);
                receivedMessage.setMine(false);
                receivedMessage.setDate(Calendar.getInstance().getTime());
            }
        });
        //Logger.d("NEWMESSRE: LAST MESSAGE:" + realm.where(Message.class).equalTo("chatID", message.id));
    }

@Override
    protected void onStart() {
        super.onStart();
        realm = Realm.getDefaultInstance();
    }

    @Override
    protected void onStop() {
        super.onStop();
        realm.close();
    }

Image of what is needed:

enter image description here

earthw0rmjim
  • 19,027
  • 9
  • 49
  • 63
Shudy
  • 7,806
  • 19
  • 63
  • 98

4 Answers4

25

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

This error message is quite self-explanatory.

As i see you're initializing realm by calling Realm.getDefaultInstance() on the UI thread.

The error is coming from newMessageReceived(), so i guess that method is called from a background thread.

Either obtain a Realm instance on the background thread and use that instead of the global instance:

@Override
public void run () {
    Realm backgroundRealm = Realm.getDefaultInstance();
    backgroundRealm.executeTransactionAsync(new Realm.Transaction() {
        @Override
        public void execute(Realm realm) {
            Message receivedMessage = realm.createObject(Message.class, message.id);
            receivedMessage.setBodyMessage(message.message);
            receivedMessage.setFrom(message.from);
            receivedMessage.setTo(message.to);
            receivedMessage.setDelivered(false);
            receivedMessage.setMine(false);
            receivedMessage.setDate(Calendar.getInstance().getTime());
        }
    });
}

Or, if you would like to stick to the global Realm instance for some reason, then make sure your code is executed on the UI thread by calling runOnUiThread() (or directly posting a Runnable to the message queue of the main thread through a Handler):

@Override
public void newMessageReceived(final ChatMessage message) {
    runOnUiThread(new Runnable() {
        @Override
        public void run() {
            realm.executeTransactionAsync(new Realm.Transaction() {
                @Override
                public void execute(Realm realm) {
                    Message receivedMessage = realm.createObject(Message.class,
                        message.id);
                    receivedMessage.setBodyMessage(message.message);
                    receivedMessage.setFrom(message.from);
                    receivedMessage.setTo(message.to);
                    receivedMessage.setDelivered(false);
                    receivedMessage.setMine(false);
                    receivedMessage.setDate(Calendar.getInstance().getTime());
                }
            });
        }
    });
}
earthw0rmjim
  • 19,027
  • 9
  • 49
  • 63
2

Just create Realm backgroundRealm = Realm.getDefaultInstance() each time you want to access database and don't forget to close it using realm.close()

Stephen Rauch
  • 47,830
  • 31
  • 106
  • 135
latifalbr
  • 153
  • 2
  • 11
1

Allocate instance before transaction and release it right after transaction is complete, so you won't have linegring connection and by doing so, you perform savings from thread scheduler. I use 'Data Access Object' interface for manipulations with data and that interface is implemented using Realm. Don't use 'transaction async', use all calls synchronously, but perform calls on 'data access object' from background thread. Good solution for that - rxJava library. Just get and release Realm instance all the time - library makes it inexpensive.

Alex Shutov
  • 3,217
  • 2
  • 13
  • 11
  • There is nothing wrong with using the async transaction API. And the guy barely knows Realm, why would you also tell him to go full-FRP with Rx? – EpicPandaForce Nov 01 '16 at 21:21
  • Imagine, you have to execute one transaction, and, after that transaction is complete, perform write transaction or some read transaction. You have to start second operation only when first complete. If you use async(), you won't be able to track when previous transaction completes. Or, if you create object on one thread and accidentally use that instance on another (using Thread Pool), you will get error. On the other hand, when you allocate instance before transaction and release it after transaction, that will not be the case – Alex Shutov Nov 01 '16 at 21:54
0

I haven't use Realm yet. But what i understood from the error message is that ConnectionConnectResponse may be alive when Loginactivity die. So you should pass Realm instance as a parameter inside

newMessageReceived(Realm realm, final ChatMessage message)

and put all the Realm init code in the class where you fire this method.

ugur
  • 3,604
  • 3
  • 26
  • 57