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.
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'm using Realm for Android.
I have issue (not really a big problem), I have this lines of code:
Account account;
....
realm = Realm.getDefaultInstance();
account = realm.where(Account.class).findFirst();
realm.close();
if (account.getJid().equals(mUser.getText().toString())) { // User is the same as logged before
launchLogin(mUser.getText().toString().split("#")[0],mPassword.getText().toString());
}
If I launch the app, when the execution arrives to IF statement, it crash because account object does'nt exist. Even when exist accounts in the db.
But If move the realm.close() inside the IF, after the launchLogin(..), it works .
What I understand is that account "dissapears" when I close the realm db. and I can get real problem in a future.
So I want to know how can I made "persistent" this type of problem. I mean, close realm after queries and the object still exist after it.
In addition to EpicPandaForces answer, if you really want to close the realm and discard any auto-update advantages Realm offers, you can create an unmanaged copy of the RealmObject using realm.copyFromRealm(realmObject);
Managed RealmObject instances can only be accessed from Realm instances that are not closed.
Following the official documentation, you should have an open instance for the UI thread bound to the lifecycle of the application itself.
From the docs:
// onCreate()/onDestroy() overlap when switching between activities so onCreate()
// on Activity 2 will be called before onDestroy() on Activity 1.
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();
}
}
And
// Use onCreateView()/onDestroyView() for Fragments as onDestroy() might not be called.
public class MyFragment extends Fragment {
private Realm realm;
#Override
public void onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) {
realm = Realm.getDefaultInstance();
View view = inflater.inflate(R.layout.my_fragment, parent, false);
return view;
}
#Override
public void onDestroyView() {
super.onDestroyView();
realm.close();
}
}
And for background threads:
// Run a non-Looper thread with a Realm instance.
Thread thread = new Thread(new Runnable() {
#Override
public void run() {
Realm realm = null;
try {
realm = Realm.getDefaultInstance();
// ... Use the Realm instance ...
} finally {
if (realm != null) {
realm.close();
}
}
}
});
thread.start();
Following the docs, the problem you mention won't occur.
You're supposed to open the Realm instance at the beginning of the background thread, close it at the end of execution in that background thread,
try {
realm = Realm.getDefaultInstance();
account = realm.where(Account.class).findFirst();
if (account.getJid().equals(mUser.getText().toString())) { // User is the same as logged before
launchLogin(mUser.getText().toString().split("#")[0],mPassword.getText().toString());
}
} finally {
if(realm != null) {
realm.close(); // important
}
}
I'm implementing an instrumented test to test a Database Data Source class that is using Realm. So now I'm facing some problems about how to use fixtures and how to mock Realm.
My database data source looks like:
public class DatabaseDataSource {
private Realm realm;
public DatabaseDataSource(Realm realm) {
this.realm = realm;
}
public Observable<RealmResults> getContacts(String firstName, String lastName, String city, String zipCode) {
final RealmQuery realmQuery = realm.where(Contact.class);
if(!TextUtils.isEmpty(firstName)) {
realmQuery.contains("firstName", firstName);
}
if(!TextUtils.isEmpty(lastName)) {
realmQuery.contains("lastName", lastName));
}
if(!TextUtils.isEmpty(city)) {
realmQuery.contains("city", city);
}
if(!TextUtils.isEmpty(zipCode)) {
realmQuery.contains("zipCode", zipCode);
}
return realmQuery.findAll()
.asObservable();
}
}
I want to have a list of contacts in my mocked realm so I can check that filtering is working fine. How can I do that?
I've tried doing:
#RunWith(AndroidJUnit4.class)
public class DatabaseDataSourceTest extends BaseInstrumentedTest{
private DatabaseDataSource databaseDataSource;
private List<Contact> contacts;
#Before
public void setup() {
Realm.init(InstrumentationRegistry.getTargetContext());
Realm.setDefaultConfiguration(new RealmConfiguration.Builder().build());
databaseDataSource = new DatabaseDataSource(new DatabaseClient());
}
#Test
public void trial() throws Exception {
subscribeContactsListObservable(databaseDataSource.getContacts("firstName", null, null, null));
assertEquals(2, contacts.size());
}
private void subscribeContactsListObservable(final Observable<RealmResults> observable) {
notaries = null;
observable.map(new Func1<RealmResults, List<Contact>>() {
#Override
public List<Notary> call(RealmResults realmResults) {
return realmResults != null? new ArrayList<>(realmResults) : null;
}
}).observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<List<Contact>>() {
#Override
public void onCompleted() {
contacts = null;
}
#Override
public void onError(Throwable e) {
contacts = null;
}
#Override
public void onNext(List<Contact> contactsList) {
contacts = contactsList;
}
});
}
}
But the test is failing when doing the Observable.subscribe with the following exception:
You can't register a listener from a non-Looper thread or IntentService thread.
What may I do?
Thanks in advance
Well it specifically tells you the solution to your problem in that error message:
You can't register a listener from **a non-Looper thread** or IntentService thread.
This is because asObservable() needs to register a RealmChangeListener in order to listen to changes in the Realm.
The instrumentation thread is a non-looper thread, so that means you can't listen to changes in it.
Solution, you need to either use a Looper thread (like the main thread), or create a Looper thread, and create the Realm instance in that looper thread. Conveniently, RxAndroid features a so-called LooperScheduler which you can create using AndroidSchedulers.from(Looper), which allows you to execute logic on an arbitrary looper thread.
A possibility is looking into how Realm already tests their looper-related stuff with this RunInLooperThread test rule.
Apparently your getContacts() methods run on a non-looper background thread which doesn't work with our change listeners (and thus our asObservable() method).
You can just create the observable instead, but keep in mind that it will emit your list once and then complete. For continuous updates you need to be on a Looper thread.
return Observable.just(realmQuery.findAll());
Here is fragment of one of my tests:
public class PlaybackDatabaseTest extends ApplicationTestCase<Application> {
public PlaybackDao dao;
public PlaybackDatabaseTest(){ super(Application.class);}
#Override
protected void setUp() throws Exception {
super.setUp();
DatabaseModule module = new DatabaseModule();
RealmConfiguration config = module.providePlaybackRealmConfiguration(getContext());
dao = module.providePlaybackDatabase(config);
}
public void testAddingPlaylistAndDeletingDatabase() {
dao.purgeDatabase();
int id1 = 0;
Playlist playlist = createTestPlaylist(id1, 0, 100);
dao.addPlaylist(playlist);
boolean exist1 = dao.isPlaylistExist(String.valueOf(id1));
assertTrue(exist1);
dao.purgeDatabase();
exist1 = dao.isPlaylistExist(String.valueOf(id1));
assertFalse(exist1);
}
}
But, it use Dagger2 to create database 'data access object' .
Realm can work from main thread, use Observable.toblocking() so your
test thread will wait until jub is done.
Realm use Android's Handler for concurrency (.map(), .flatmap() operators return results on Schedulers.computation() by default), so to solve Handler issue use ApplicationTestCase.
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 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.