I would like to know if there is any recommended practice of using RealmDB across multiple threads.
My scenario: I am looping through the records in RealmDB using one thread and doing some action. Based on the response from the previous action I would like to remove the records from another thread.
What would be best way to achieve this?
You can pass RealmObject field values (e.g. id, primaryKey) across the threads, which means that when you are done with your "action1" on the other thread, you can transfer the id(s) to the thread that is responsible for handling Realm operations, query the Object(s) which needs to be removed and delete them from Realm, you can executeTransactionAsync to further takeaway delete operation(s) from the thread where Realm is operating.
EDIT
In Realm Write operations don't block Read operations.
RealmResults & RealmObjects are LIVE objects until we close Realm instance
If you use Read operations as Observable, all further modification will be notified, If you don't want to use Observable you can also use addChange listener.
Lets have a look at some code:
Lets say in one of your class you have a Realm instance, and you are doing read operation on ThreadA (mainThread in this example)
realm.where(GitHubUser.class).findAll().asObservable()
.filter(RealmResults::isLoaded)
.filter(RealmResults::isValid)
.subscribeOn(AndroidSchedulers.mainThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(gitHubUsers -> {
for (GitHubUser gitHubUser : gitHubUsers) {
Log.e("TAG", "data = " + gitHubUser.getLogin());
}
});
And a corresponding addChangeListener version
RealmResults realmResults = realm.where(GitHubUser.class).findAll();
realmResults.addChangeListener(new RealmChangeListener<RealmResults>() {
#Override
public void onChange(RealmResults element) {
for (GitHubUser gitHubUser : gitHubUsers) {
Log.e("TAG", "data = " + gitHubUser.getLogin());
}
}
});
for (GitHubUser gitHubUser : gitHubUsers) {
Log.e("TAG", "data = " + gitHubUser.getLogin());
}
and let's say you get the trigger and want to delete one of the entry on a separate thread, what you should do is, get a new Realm instance, delete the entry as shown below and close the Realm instance.
This way you will not face any thread issue and your Read query gets a notification after you delete the entry and you can update your view with updated data.
new Thread(() -> {
Realm realm1 = Realm.getDefaultInstance();
GitHubUser gitHubUser = realm1.where(GitHubUser.class)
.equalTo("login", "loginString")
.findFirst();
if (gitHubUser != null) {
realm1.executeTransaction(realm2 -> gitHubUser.deleteFromRealm());
}
realm1.close();
}).run();
Related
So, I have an Android app that uses realm.io. I have to run queries asynchronously like this :
public static void getProductsByCategoryId(Realm realm,
String categoryId,
OrderedRealmCollectionChangeListener<RealmResults<Product>> callback) {
RealmResults<Product> result = realm.where(Product.class)
.equalTo(CATEGORY, categoryId)
.findAllAsync();
result.addChangeListener(callback);
}
The callback will process this response, but then I need to run another query in sequence. So, you'll have queryA => process response => queryB => process response. So, the callback may have code like this
.....
getProductsByCategoryId(app.getRealmInstance(), "ABC123", firstCallback);
.....
private OrderedRealmCollectionChangeListener<RealmResults<Product>> firstCallback = new OrderedRealmCollectionChangeListener<RealmResults<Product>>() {
#Override
public void onChange(RealmResults<Product> realmProducts, OrderedCollectionChangeSet changeSet) {
mProdList.addAll(mRealm.copyFromRealm(realmProducts));
// get more product info (2nd call)
MainApplication.getMoreProductInfo(mRealm, mCatId, false, secondCallback);
}
};
Currently, my understanding is that you would run queryB in the callback of queryA ? Looking at the requirements for the app, I will end up with chains of 3 or 4 queries. Is this an appropriate approach, or is there a specific pattern I should be using ? I haven't found any guidance yet in the Realm documentation.
It's generally an indication of bad schema design if you need to do multiple queries in order to retrieve your result set, because the way Realm works is that if you can define your query results with one query (and you don't use realm.copyFromRealm() which you generally don't need to use anyways), then its elements and the results itself are all lazy-loaded.
If you cannot accomplish that, then even then, generally you probably shouldn't chain find*Async calls, because any RealmResults that you don't store as a field variable has a chance of being consumed by GC, and its change listener won't be called when isLoaded() is true (because said RealmResults no longer exists).
So what you really seem to want to do is just execute multiple queries on a background thread then return copied results to the main thread, in which case it'd just look like this
Executor executor = Executors.newSingleThreadedPool(); // or some other pool
Handler handler = new Handler(Looper.getMainLooper());
public void getQueryResults(DataLoadedCallback callback) {
executor.execute(() -> {
try(Realm realm = Realm.getDefaultInstance()) {
realm.refresh(); // <-- might not be necessary
RealmResults<XYZ> results1 = realm.where(XYZ.class)./*...*/.findAll();
RealmResults<ZXY> results2 = realm.where(ZXY.class)./*...*/.findAll();
RealmResults<YZX> results3 = realm.where(YZX.class)./*...*/.findAll();
List<Something> someList = new LinkedList<>();
for/*do magic transform things*/
someList.add(blah /* blah is not a managed RealmObject */);
}
handler.post(() -> {
callback.onDataLoaded(Collections.unmodifiableList(new ArrayList<>(someList)));
});
}
});
}
Chaining queries in the callbacks are fine and "should just work", but it would be far more efficient if you can express what you want is as few queries as possible.
Ideally, we should have a query language that is powerful enough to express everything you want in one query. We are not fully there yet, but we would be very interested to hear more about what specific requirements you have.
Also, it isn't clear why you are using copyFromRealm in the method you posted, but in an ideal situation that shouldn't be necessary.
I have 2 tables in database, Course and Lecture. They are 1:N relationship. My problem is I want to delete multiple courses, before that I have to make sure all its relative Lectures are deleted, as well as some files along with the lecture. That is, I want to delete multiple course, for every course, the following steps should be perform:
delete lecture file and record delete course
delete course
How to do it with RxJava 1.x? Thanks.
I think it would be like :
ArrayList<Course> courses = new ArrayList<>();
Observable.fromIterable(courses)
.doAfterNext(new Consumer<Course>() {
#Override
public void accept(Course course) throws Exception {
//DELETE this Course
}
}).flatMap(new Function<Course, ObservableSource<ArrayList<Lecture>>>() {
#Override
public ObservableSource<ArrayList<Lecture>> apply(Course course) throws Exception {
return Observable.fromArray(course.getAllLecture());
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<ArrayList<Lecture>>() {
#Override
public void accept(ArrayList<Lecture> lectures) throws Exception {
//delete all lectures
}
});
If you're using GreenDao, I don't think that using RxJava in this way is your best bet. Your major problem here is that you're not in a transaction, which leaves you at risk of your data ending up in an inconsistent state. While it can be a great exercise to consider, "how can I write this code in Rx style?" I suggest that it doesn't gain you anything to use it for every step of this process. Therefore, I suggest you write your delete code as procedural (not Rx) code inside of a GreenDao transaction, and only use RxJava to be notified when it is completed. When you are inside of a GreenDao transaction block, all of the database calls inside of it are made synchronously, one after another, in guaranteed order.
In addition, for the greatest consistency, I would delete all of the files at once only after the transaction block is committed (because you may not want to delete the files if part of the DB transaction fails and the database doesn't update). In addition, there are two major ways of doing deletes in GreenDao: directly, session.delete(entity) and queries, query.buildDelete().tableDeleteQuery.executeDeleteWithoutDetachingEntities(). Direct deletes are much simpler to code, but could be slower if you have huge amounts of data. If you have less than 1000 very simple entities, direct deletes are probably good enough.
So, your code might look like this:
final DaoSession daoSession = getDaoSession();
final List<Course> courses = getCoursesToDelete();
// rxTx() creates a tx that runs on RxJava's 'io' scheduler.
daoSession.rxTx().call(() -> {
List<File> filesToDelete = new ArrayList<>();
for(Course course : courses) {
for(Lecture lecture : course.getLectures()) {
filesToDelete.add(lecture.getFiles());
daoSession.delete(lecture);
}
daoSession.delete(course);
}
return filesToDelete;
})
// potentially handle DB errors here
// .flatMapIterable here if you want each File as an Rx event
.doOnNext(filesToDelete -> {
for(File f : filesToDelete) {
// Throw on failed delete here if needed
f.delete();
}
})
// handle file delete errors if desired.
.subscribeOn(Schedulers.io()) // technically redundant
.observeOn(AndroidSchedulers.mainThread())
.subscribe();
From what i have read Room doesn’t allow you to issue database queries on the main thread (as can cause delays on the main thread)). so imagine i am trying to update a textview on the UI main thread which some data how would i get a call back. Let me show you an example. Imagine i want to store my business model data into a object called Events. We would therefore have a EventDao object:
imagine we have this DAO object below:
#Dao
public interface EventDao {
#Query("SELECT * FROM " + Event.TABLE_NAME + " WHERE " + Event.DATE_FIELD + " > :minDate" limit 1)
LiveData<List<Event>> getEvent(LocalDateTime minDate);
#Insert(onConflict = REPLACE)
void addEvent(Event event);
#Delete
void deleteEvent(Event event);
#Update(onConflict = REPLACE)
void updateEvent(Event event);
}
and now in some activity i have a textview and i'd like to update its value so i do this:
myTextView.setText(EventDao.getEvent(someDate));/*i think this is illegal as im trying to call room dao on mainthread, therefore how is this done correctly ? would i need to show a spinner while it updates ?*/
since the fetching is occuring off of the main thread i dont think i can call it like this and expect a smooth update. Whats the best approach here ?
Some more information: i wanted to use the room database as mechanism for retrieving model information instead of keeping it statically in memory. so the model would be available to me locally through the db after i download it through a rest service.
UPDATE: so since i am returning a livedata then i can do this:
eventDao = eventDatabase.eventDao();
eventDao.getEvent().observe(this, event -> {
myTextView.setText(event.get(0));
});
and that works for something very small. but imagine my database has a million items. then when i do this call, there will be a delay retrieving the data. The very first time this gets called it will be visible to the user that there is a delay. How to avoid this ? So to be clear , there are times i do not want live data, i just need to update once the view. I need to know how to do this ? even if its not with liveData.
If you want to do your query synchronously and not receive notifications of updates on the dataset, just don't wrap you return value in a LiveData object. Check out the sample code from Google.
Take a look at loadProductSync() here
There is a way to turn off async and allow synchronous access.
when building the database you can use :allowMainThreadQueries()
and for in memory use: Room.inMemoryDatabaseBuilder()
Although its not recommended. So in the end i can use a in memory database and main thread access if i wanted super fast access. i guess it depends how big my data is and in this case is very small.
but if you did want to use a callback.... using rxJava here is one i made for a list of countries i wanted to store in a database:
public Observable<CountryModel> queryCountryInfoFor(final String isoCode) {
return Observable.fromCallable(new Callable<CountryModel>() {
#Override
public CountryModel call() throws Exception {
return db.countriesDao().getCountry(isoCode);
}
}).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
you can then easily add a subscriber to this function to get the callback with Rxjava.
As Bohsen suggested use livedata for query synchronously. But in some special case, we want to do some asynchronous operation based on logic.
In below example case, I need to fetch some child comments for the parent comments. It is already available in DB, but need to fetch based on its parent_id in recyclerview adapter. To do this I used return concept of AsyncTask to get back the result. (Return in Kotlin)
Repositor Class
fun getChildDiscussions(parentId: Int): List<DiscussionEntity>? {
return GetChildDiscussionAsyncTask(discussionDao).execute(parentId).get()
}
private class GetChildDiscussionAsyncTask constructor(private val discussionDao: DiscussionDao?): AsyncTask<Int, Void, List<DiscussionEntity>?>() {
override fun doInBackground(vararg params: Int?): List<DiscussionEntity>? {
return discussionDao?.getChildDiscussionList(params[0]!!)
}
}
Dao Class
#Query("SELECT * FROM discussion_table WHERE parent_id = :parentId")
fun getChildDiscussionList(parentId: Int): List<DiscussionEntity>?
Well, the right answer is to use ListenableFuture or Observable depending if you need one shot query or a new value emitted after database change and the framework you want to use.
From the doc "To prevent queries from blocking the UI, Room does not allow database access on the main thread. This restriction means that you must make your DAO queries asynchronous. The Room library includes integrations with several different frameworks to provide asynchronous query execution."
Exemple with a one shot query. You just have to add this in your gradle file.
// optional - Guava support for Room, including Optional and ListenableFuture
implementation "androidx.room:room-guava:$room_version"
Then your SQL query in your DAO become.
#Query("SELECT * FROM " + Event.TABLE_NAME)
ListenableFuture<List<Event>> getEventList();
Last step is the future call itself.
ListenableFuture<List<Event>> future = dao.getEventList();
future.addListener(new Runnable() {
#Override
public void run() {
try {
List<Event>> result = future.get();
} catch (ExecutionException | InterruptedException e) {
}
}
}, Executors.newSingleThreadExecutor());
Source : https://developer.android.com/training/data-storage/room/async-queries#guava-livedata
I insert records from a ContentObserver in an Android Service like:
try {
realm.beginTransaction();
realm.copyToRealm(sms);
realm.commitTransaction();
} catch (RealmException re) {
realm.cancelTransaction();
}
Then in another Service I run a JobService with the following query:
Realm realm = Realm.getDefaultInstance();
RealmQuery<Sms> query = realm.where(Sms.class);
RealmResults<Sms> sms = query.findAll();
ArrayList<Sms> smsArrayList = new ArrayList<>();
smsArrayList.addAll(sms);
for (Sms sms : smsArrayList) {
process row
delete row
}
Is the query read-consistent in point in time, like Oracle?
What happens if the query runs for some time and new data is inserted from the other Service?
I like my query to be read-consistent. The new records inserted after the query starts should not be seen or be included. Is this the case or how should I code it?
I need to be sure I process the records in the job queue properly and I want to avoid read the same job twice or mix up due to the other Service inserting.
So I'd like to do a for loop over RealmResults<< Sms>>, process it, delete the row.
Then at some point in time start the query again and process the new records.
On looper threads, Realms (and RealmResults and RealmObjects) are updated automatically. You can disable this by calling setAutoRefresh (https://realm.io/docs/java/latest/api/io/realm/Realm.html#setAutoRefresh-boolean-). Alternatively you can start a write transaction.
So you could do:
Realm realm = Realm.getDefaultInstance();
realm.setAutoRefresh(false);
// do all your reads here ...
realm.waitForChange(); // to update the instance if other threads have commit changes
or alternatively:
Realm realm = Realm.getDefaultInstance();
realm.writeTransaction(); // will wait if another write transaction is in progress
// do all your reads here ...
realm.cancelTransaction(); // no writes :-)
I am using Realm with RxAndroid. i am having this strange issue where realm is not picking up the latest modification done on DB.
There are 2 methods that i am using.
Observable<Integer> save(Bitmap bitmap).
Observable<Integer> getImageList(Context applicationContext).
Like this
Activity 1
getImageList(applicationContext)
button click -> Activity 2
save(bitmap)
finish()
getImageList(applicationContext)
This method "save" basically adds a newly created model into RealmList.
private Observable<Integer> save(Bitmap bitmap) {
return Observable.create((Observable.OnSubscribe<Integer>) subscriber -> {
--------------------------------------
-----Various file creation stuff------
--------------------------------------
UserImagesModel model = realm
.where(UserImagesModel.class)
.findFirst();
//ImageModel class extends RealmObject
ImageModel imageModel = new ImageModel();
realm.beginTransaction();
//realm object must be Edited inside transaction
model.getResponse().add(0, imageModel);
realm.commitTransaction();
realm.close();
subscriber.onNext(1);
subscriber.onCompleted();
}
}
Ans this method fetches saved list.
public Observable<Integer> getImageList(Context applicationContext) {
return Observable.create((Observable.OnSubscribe<Integer>) subscriber -> {
AppUtils.logD("User image observable instance " + this);
UserImagesModel model;
Realm realm = Realm.getInstance(applicationContext);
model = realm.where(UserImagesModel.class).findFirst();
^
This model doesn't replicate data added in save call
------------------------------------------------
----Various validation and service calls.-------
------------------------------------------------
subscriber.onCompleted();
realm.close();
});
}
}
As i mentioned in code, UserImageModel that i get from Realm doesn't replicate changes i made in save method.
the problem occurs when i call getImageList method second time. also when i print this.toString inside Observable.create it prints same object that was returned first time.
So i believe this issue seems to be with the way i am using RxAndroid. can anyone tell me what i am missing? and how can i resolve it?
UPDATE :
After few tests i realized that this.toString inside Observable.create is actually points to parent object as i have used lamda expression so that is not seems to be the issue and now i am back to square one ;(
Turns out, this is expected behavior of Realm. as i was subscribing those observables on IO threads which doesn't have Looper.
Op here has similar issue. answer explains the case.