Realm access from incorrect thread

Shudy picture Shudy · Oct 31, 2016 · Viewed 20.9k times · Source

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

Answer

earthw0rmjim picture earthw0rmjim · Oct 31, 2016

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());
                }
            });
        }
    });
}