I am using Realm in my android application.I am receiving a notification from google drive via CompletionEvent so I need to modify my realm database in a service.
The exception I get is:
java.lang.IllegalStateException: Realm access from incorrect thread. Realm objects can only be accessed on the thread they were created.
I have set my default configuration in my Application class the next way:
RealmConfiguration realmConfiguration = new RealmConfiguration.Builder(getApplicationContext())
.deleteRealmIfMigrationNeeded()
.build();
Realm.setDefaultConfiguration(realmConfiguration);
And in the onCreate from my service I am getting my Realm instance like this:
mRealm = Realm.getDefaultInstance();
And then I use this realm instance in the service:
mRealm.executeTransaction(realm -> {
DocumentFileRealm documentFileRealm = realm.where(DocumentFileRealm.class)
.equalTo("id", documentFileId)
.findFirst();
documentFileRealm.setDriveId(driveId);
});
But when executing this last one the app launches the IllegalStateException. I don't know why. I am not sure if it has something to do with the way I have declared the service in my android manifest so I leave it here:
<service android:name=".package.UploadCompletionService" android:exported="true">
<intent-filter>
<action android:name="com.google.android.gms.drive.events.HANDLE_EVENT"/>
</intent-filter>
</service>
Is it possible calling Realm from a background service? What is wrong with the way I am using this?
Thanks in advance.
In an IntentService, you're supposed to treat the onHandleIntent method like the doInBackground method of an AsyncTask.
So it runs on the background thread and you should make sure you close the Realm in a finally block.
public class PollingService extends IntentService {
#Override
public void onHandleIntent(Intent intent) {
Realm realm = null;
try {
realm = Realm.getDefaultInstance();
// go do some network calls/etc and get some data
realm.executeTransaction(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
realm.createAllFromJson(Customer.class, customerApi.getCustomers()); // Save a bunch of new Customer objects
}
});
} finally {
if(realm != null) {
realm.close();
}
}
}
// ...
}
onCreate runs on the UI thread, so your initialization of the Realm happens on a different thread, which is a no-go.
Related
I rephrased the question in order not to confuse people on what I am really doing. The example used in the original question was way too simplified.
My project is client/server based. The android app is the client. To properly simply the situation, we can think about that I have two intents/activities and one background thread. Activity A is login . Activity B is where the rest of the app is. The background thread is a socket thread, and it needs to be kept up all the time until user exits the app, or network connection is interrupted.
A state machine is implemented to handle the app states, and this state machine is required by all activities, and the socket thread.
Singleton easily meets the requirement of my design, but there will be a lot of synchronized block and wait statement. I wonder is it possible to use realm to achieve the same goal
Original Question
I am new to Realm Java (Android) development. In my current code, I have a bunch of global variables and using them in a few AsyncTask or background threads.
I'll use some code here to demostrate my example
//Class holding global variable
public class GlobalInfo{
public static String info;
}
//Class changing the global variable
class A{
void doSomething(){
String info = GlobalInfo.info;
info = "start";
synchronized(info){
...... //do something
info = "done";
info.notifyAll();
}
}
}
//background thread waiting for info to be "done". Neglecting the class holding it
void doSomethingAfterDone(){
String info = GlobalInfo.info;
synchronized(info){
while(!info.Equals("done")){
info.wait();
}
//do something
}
}
Assume that when doSomethingAfterDone() is called, the method doSomething() is still running. Therefore, the doSomethingAfterDone() will be waiting for doSomething() to notify before exiting the while loop.
Is it possible to use Realm to replace them? For example, using a realm transaction + a listener waiting for change, rather than applying synchronized block on the global variable and wait for its notification?
I found Realm.waitForChange() will block the execution until there is a changed made to it. However, do all changes that apply to any objects registered or copied to realm returns true for this statement, regardless what get changed?
I know that I can pass String between intents, but I want to know if it is feasible that realm can do the job.
Well you generally don't need global variables at all. You can just write to the Realm on the background thread, and listen for change on the UI thread.
//Class holding global variable
//public class GlobalInfo{
//public static String info;
//}
//Class changing the global variable
class A{
void doSomething(){
//String info = GlobalInfo.info;
//info = "start";
//synchronized(info){
try(Realm realm = Realm.getDefaultInstance()) {
realm.executeTransaction(new Realm.Transaction() { // assuming background thread
#Override
public void execute(Realm realm) {
//!! do something with Realm
}
});
}
new Thread(new Runnable() { // could be merged to this background thread
#Override
public void run() {
doSomethingAfterDone();
}
}).start();
//info = "done";
//info.notifyAll();
//}
}
}
//background thread waiting for info to be "done". Neglecting the class holding it
void doSomethingAfterDone(){
//String info = GlobalInfo.info;
//synchronized(info){
//while(!info.Equals("done")){
// info.wait();
//}
try(Realm realm = Realm.getDefaultInstance()) {
realm.executeTransaction(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
//do something
}
});
}
//}
}
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:
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());
}
});
}
});
}
Just create Realm backgroundRealm = Realm.getDefaultInstance() each time you want to access database and don't forget to close it using realm.close()
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.
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.
I'm trying to save my Objects from Retrofit directly into Realm but always getting the Error:"Realm access from incorrect thread".
This is my code:
public class RestaurantRepositoryRetrofit implements IRestaurantRepository {
private RestaurantApi mApi;
private Realm realm;
private IMapper<RestaurantJson,Restaurant> mRestaurantMapper;
public RestaurantRepositoryRetrofit(IMapper<RestaurantJson, Restaurant> restaurantMapper) {
mApi = ApiProvider.getApi().create(RestaurantApi.class);
mRestaurantMapper = restaurantMapper;
// Get a Realm instance for this thread
realm = Realm.getDefaultInstance();
**}
#Override
public Observable<Restaurant> getRestaurantById(String restaurantId) {**
return mApi.getRestaurantById(restaurantId)
.map(new Func1<RestaurantJson, Restaurant>() {
#Override
public Restaurant call(RestaurantJson restaurantJson) {
realm.executeTransaction(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
realm.copyToRealm(restaurantJson);
}
});
return mRestaurantMapper.transform(restaurantJson);
}
});
}
}
You should open the Realm instance on the background thread that receives the results of the API.
return mApi.getRestaurantById(restaurantId)
.map(new Func1<RestaurantJson, Restaurant>() {
#Override
public Restaurant call(RestaurantJson restaurantJson) {
try(Realm realm = Realm.getDefaultInstance()) {
realm.executeTransaction(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
realm.copyToRealm(restaurantJson);
}
});
return mRestaurantMapper.transform(restaurantJson);
}
}
});
Although if you intend to return a managed RealmObject, you should map out the ID from the saved proxy and then observe on main thread and query with a UI thread instance of Realm using the ID.
realm = Realm.getDefaultInstance(); will return the instance for the thread on which the object is created. But Observable.map() is called on the thread the observable sends the message from.
Since the observable comes from Retrofit this can be 2 options:
If the CallAdapter was created without specifying a scheduler then it will execute on the thread Observable.subscribe was called on.
If the CallAdapter was created with a specific scheduler it will be executed on that thread.
If the option that applies is not the same thread as the one where the object is created the "Realm access from incorrect thread" error will be thrown.
Realm is thread confined, which means that you need to make sure that you make calls on Realm objects and the realm instance on the same thread that you got the reference on. You probably want to use the scheduling method observeOn() from RxAndroid to make sure that you call realm::executeTransaction() on the same thread that you got the realm instance on.
I'm doing Realm insertions on a extended NotificationListenerService, like this:
public class NLService extends NotificationListenerService {
#Override
public void onNotificationPosted(StatusBarNotification sbn) {
// building realmObject here
mRealm = Realm.getDefaultInstance();
RealmHelper.saveObj(myRealmObject, mRealm);
// mRealm.waitForChange(); / mRealm.refresh();
mRealm.close();
}
}
public class RealmHelper {
public static RealmModel saveObj(RealmObject realmObject, Realm realm) {
realm.beginTransaction();
RealmObject newRealmObject = realm.copyToRealm(realmObject);
realm.commitTransaction();
return newRealmObject;
}
}
Using Realm newer than v0.88.3, not a single RealmChangeListener (rcl) gets called if anything gets inserted in NLService.
I tried attaching rcl's directly to Realm, RealmResults and RealmObject, nothing works.
The App has for example simple rcl's for RealmResults<myRealmObject>.size() and
several RecyclerAdapters and the rcl inside RealmRecyclerViewAdapter is never called.
Rerunning queries however works and the "missing data" shows up.
Also if anything gets inserted on ui- or any other thread, rcl's get called and "missing data" shows up.
I stayed for months on Realm 0.88.3 because I can not bring it to work with any newer Realm version. With 0.88.3 mRealm.refresh(); was called in NLService, this is not available in newer versions and .waitForChange blocks endlessly.
Manifest.xml:
<service
android:name=".service.NLService"
android:label="#string/nl_service"
android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
<intent-filter>
<action android:name="android.service.notification.NotificationListenerService"/>
</intent-filter>
</service>
I can see two solutions to this, either use a looper thread (HandlerThread) with setAutoRefresh(true) (and call setAutoRefresh(false) before Looper.quit()), or force a refresh for the Realm instance on the thread.
NOTE: This relies on package-internal methods. Beware.
In v 1.1.1 (and v1.2.0), - and any version before 3.0.0 - instead of the following line
// mRealm.waitForChange(); / mRealm.refresh();
You could force the update on the local thread through the HandlerController associated with the Realm instance using package-internal stuff
package io.realm;
public class RealmRefresh {
public static void refreshRealm(Realm realm) {
Message message = Message.obtain();
message.what = HandlerControllerConstants.LOCAL_COMMIT;
realm.handlerController.handleMessage(message);
}
}
And then call
mRealm = Realm.getDefaultInstance();
RealmHelper.saveObj(myRealmObject, mRealm);
RealmRefresh.refreshRealm(mRealm);
mRealm.close();
Please note the change log's breaking changes though, because 0.89.0 changed iteration behavior, and results are no longer live during an active transaction; but from 3.0.0 they are again.
However, I must also note that if your NotificationListenerService is running in a remote process, then the Realm instances won't be able to notify each other.
EDIT:
In Realm Java 3.0.0, the notification behavior was changed completely, and HandlerController no longer exists.
Instead, the following should work:
package io.realm;
public class RealmRefresh {
public static void refreshRealm(Realm realm) {
realm.sharedRealm.refresh();
}
}
EDIT:
In Realm 3.2.+, this is all available with
realm.refresh();
I've a problem with the initialization of Realm.
I get systematically an error with :
Realm realminstance = Realm.getDefaultInstance();
I caught this exception :
No default RealmConfiguration was found. Call setDefaultConfiguration() first
I know I have to init Realm before using it, but could you tell me how can I check if Realm is initialized ?
It doesn't works with:
if(Realm.getDefaultConfiguration == null){...}
Thank you very much.
This code snippet here should work: https://realm.io/docs/java/latest/#controlling-the-lifecycle-of-realm-instances
// Setup Realm in your Application
public class MyApplication extends Application {
#Override
public void onCreate() {
super.onCreate();
RealmConfiguration realmConfiguration = new RealmConfiguration.Builder(this).build();
Realm.setDefaultConfiguration(realmConfiguration);
}
}
public class MyActivity extends Activity {
private Realm realm;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
realm = Realm.getDefaultInstance();
}
#Override
protected void onDestroy() {
super.onDestroy();
realm.close();
}
}
You could use a try catch block. I initially initialised my realm in the application class and used it in a content provider and sync adapter but the provider is created before the application apparently so I was forced to do a try catch in all 3 to avoid whatever race condition may occur (couldn't find documentation for what gets created first amongst the components).
`try{
realm = Realm.getDefaultInstance();
} catch (Exception e){
Realm.init()
//Configuration, schema, migration, and encryption details come here
//Now we get the default instance: again.
realm = Realm.getDefaultInstance();
}`