Realm access from incorrect thread Android Retrofit2 RxJava - android

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.

Related

Realm on Android: keep DB logic separate

I used to work with un-auto-managed model objects that were copied from Realm. I switched to using auto-managed Realm object for my new projects and have a problem.
Where before I could keep my DB logic separate, in DAO classes, right now, the Realm code is all over my application.
Realm objects should be managed (closed) well, everywhere, on every thread, on every activity and fragment. But what I dislike most: every setter on every model object needs to be in a Realm transaction. There is Realm all over my code right now!
Did anyone find a way to keep the DB logic somewhat separate, while using auto managed Realm objects?
Conclusion, after a few months with Realm auto-managed objects:
Having your database code centralised, in Data Access Object (DAO) classes or the like, is hardly possible when using Realm’s auto-updated objects.
Every setter on every model object needs to be inside a Realm transaction block. If you use RetroLambda, the calls are relatively clean:
realm.executeTransaction(r -> user.setFirstName(firstName));
These blocks will be all over your project within no time. Forgetting to wrap a setter method (or a constructor call) in a transaction will crash your app. Believe me, this will happen a lot in the first weeks of adopting auto-updated objects.
Auto-updated objects cannot be shared across threads. The same is true for Realm instances.
All threads, and often activities and fragments, will have to open and close Realm instances. You’ll be thinking constantly “On which thread am I?”. If you cross thread boundaries with model objects or Realm instances, you will crash your app.
More info here: https://medium.com/#ffvanderlaan/realm-auto-updated-objects-what-you-need-to-know-b2d769d12d76
and here: https://medium.com/#Zhuinden/how-to-use-realm-for-android-like-a-champ-and-how-to-tell-if-youre-doing-it-wrong-ac4f66b7f149#.gazrajqwt
What I did was created a RealmController class that handles all of my Realm transactions, and fetches new data from the API. The skeleton for the implementation is borrowed from another SO post which I can't find right now, but you should be able to find it on Google by searching RealmController.
public class RealmController {
private static RealmController instance;
private final Realm realm;
private ServiceInterface restInterface;
private final String TAG = "RealmController";
private static boolean browseFetchAllowed = true;
private RealmConfiguration config = new RealmConfiguration.Builder()
.name("myRealm")
.schemaVersion(0)
.deleteRealmIfMigrationNeeded()
.build();
public RealmController(Application application) {
restInterface = ServiceGenerator.createService(ServiceInterface.class);
realm = Realm.getInstance(config);
}
public void setAuthentication(String token, String uid) {
this.restInterface = ServiceGenerator.createService(ServiceInterface.class, token, uid);
}
public static RealmController with(Fragment fragment) {
if (instance == null) {
instance = new RealmController(fragment.getActivity().getApplication());
}
return instance;
}
public static RealmController with(Activity activity) {
if (instance == null) {
instance = new RealmController(activity.getApplication());
}
return instance;
}
public static RealmController with(Application application) {
if (instance == null) {
instance = new RealmController(application);
}
return instance;
}
public RealmController getInstance() {
return instance;
}
public ServiceInterface getRestInterface() {
return restInterface;
}
public Realm getRealm() {
return realm;
}
public RealmResults<MyRealmObject> getStuff() {
return realm.where(MyRealObject.class).findAll();
}
public void setStuff(RealmList<MyRealmObject> stuff) {
realm.beginTransaction();
realm.copyToRealmOrUpdate(stuff);
realm.commitTransaction();
}
public void getStuffFromServer() {
restInterface.getStuff().enqueue(new Callback<RealmList<MyRealmObject>>() {
#Override
public void onResponse(Call<RealmList<MyRealmObject>> call, Response<RealmList<MyRealmObject>> response) {
if (response.isSuccessful()) {
realm.beginTransaction();
realm.copyToRealmOrUpdate(response.body);
realm.commitTransaction();
}
}
#Override
public void onFailure(Call<RealmList<RealmAd>> call, Throwable t) {
t.printStackTrace();
}
});
}
}
Towards the bottom of the file I've added a few examples, but usage is quite simple:
RealmResults<MyRealmObject> results = RealmController.with(this).getStuff();
In terms of having the objects auto-managed, what I've been using is RealmResults, which is always auto-managed (even if you're querying for a single object). Then just add a change listener to the results of the query and voilà. Also, if you want to have auto-updating data in recyclerviews, I would recommend RealmRecyclerView.

Realm object not accesible when realm is closed. Android

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
}
}

Realm access from incorrect thread

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.

Instrumented test with Realm failing

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.

What is the best practice of managing realm instance in Clean Architecture?

My project using clean architecture. In this situation, the UI layer is separate from Domain layer. So I think it would be better the UI layer doesn't own realm instance. As realm's doc recommend managing the realm instance in Activity's lifecycle, how should I deal with the realm instance then?
To be more clear, my project is too heavy to change all objects extends RealmObject. So I use separate object to persistent data. When the api call finish, a business object convert to a realm object, opposite when query from realm. I create the method like this:
public void insert(T object){
final Realm realm = RealmProvider.getRealm();
realm.executeTransactionAsync(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
realm.copyToRealmOrUpdate(createRealmObject(object));
}
}, new Realm.Transaction.OnSuccess() {
#Override
public void onSuccess() {
realm.close();
}
}, new Realm.Transaction.OnError() {
#Override
public void onError(Throwable error) {
realm.close();
}
});
}
Actually, it works fine. But below I don't know how to handle closing realm instance.
public Observable<T> queryAsync(Condition<? extends RealmObject> condition) {
final Realm realm = RealmProvider.getRealm();
return condition.getQuery(realm).findFirstAsync()
.asObservable()
.filter(new Func1<RealmObject, Boolean>() {
#Override
public Boolean call(RealmObject realmObject) {
return realmObject.isLoaded();
}
})
.map(new Func1<RealmObject, T>() {
#Override
public T call(RealmObject realmObject) {
return createObjectFromRealm(realmObject);
}
});
}
If you want a clean separation between UI and database layers in your code, and you want to abstract away your database logic so that ideally your activity can call database layer without knowing how that layer is implemented, then Realm is probably not what you're looking for.
Realm objects are tied to realm instances which means that if you retrieve an object from a realm instance and then close that instance (which you must), you can no longer use the object. Which defeats the entire purpose of using Realm.
If you are going to use Realm, you should keep the realm logic closely tied to your activities/services etc, and don't try to hide it in a separate layer, so that you have full control over it.
.map(new Func1<RealmObject, T>() {
#Override
public T call(RealmObject realmObject) {
Object o = createObjectFromRealm(realmObject);
realm.close();
return o;
}
});
One of the major aspect of a clean architecture is, isolation of major libraries (i.e. Realm). Since Realm, RealmObject, RealmResults are not accessible outside of the Thread they are created in, it makes it even more important to keep Realm & Realm related calculations isolated from rest of the code.
You are using RxJava in your queryAsync() method, and at the same time you are using executeTransactionAsync() method, which defies the whole purpose of using RxJava. You could have done like this,
public void insert(T object){
final Realm realm = RealmProvider.getRealm();
realm.executeTransaction(realm1 ->
realm1.copyToRealmOrUpdate(createRealmObject(object)));
realm.close();
}
In a good Architecture, for each jsonModel class there should be a corresponding realmModel class & a DAO (Data Access Object). DAO class must take jsonModel as argument and must return jsonModel as result. All Realm related operations must be restricted within the DAO file, that way none of the code other than DAO and realmModel knows about Realm.
Here is an article about Realm best practices with a good architechture https://medium.com/#Viraj.Tank/realm-integration-in-android-best-practices-449919d25f2f
Also a sample project demonstrating Integration of Realm on Android with MVP(Model View Presenter), RxJava, Retrofit, Dagger, Annotations & Testing.
https://github.com/viraj49/Realm_android-injection-rx-test

Categories

Resources