RxJava+Android+GreenDao : Cascade delete multiple entities - android

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

Related

How to Clear the RealmResults<> of a particular Query while Filtering through the Realm in Android?

I am applying filters on realm using RealmResults<>.
I begin to do like this -
RealmResults<data> filteredRealmResults;
List<data> tranfilteredlist;
private OrderedRealmCollectionChangeListener<RealmResults<data>> filteredTransChangeListener =
new OrderedRealmCollectionChangeListener<RealmResults<data>>() {
#Override
public void onChange(RealmResults<data> results, OrderedCollectionChangeSet changeSet) {
Log.d("realm", "filteredRealmResults.size():" + filteredRealmResults.size());
tranfilteredlist = results;
initFilterAdapter();
}
};
Now I want to delete the filteredRealmResults. I did like this -
void deleteFilteredRealmResults() {
realm.executeTransaction(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
// Delete all matches
filteredRealmResults.deleteAllFromRealm();
}
});
}
After doing this my data in the realm got deleted. So I just try to delete the tranfilteredlist but it throws an exception that it does not support .clear();
I want to clear if from the memory whatever is holder the query data. Correct me if I am wrong or doesn't understand or just worrying too much.
I read This class holds all the matches of a RealmQuery for a given Realm. The objects are not copied from the Realm to the RealmResults list, but are just referenced from the RealmResult instead. This saves memory and increases speed.
I want to clear if from the memory whatever is holder the query data.
Correct me if I am wrong or doesn't understand or just worrying too
much.
Once you invoke filteredRealmResults.deleteAllFromRealm, it will clear the internal resultant elements object(which holds the elements) and as you know, resultant objects are reference so data will be deleted from realm database too. Hence, there is no need to call clear on the RealmResults object.
You can verify this by calling filteredRealmResults.size() after deletion, it will return 0.
I just try to delete the tranfilteredlist but it throws an exception
that it does not support .clear();
It is the expected behaviour as clear has been deprecated so don't use it.
Why deprecated?
deleteAllFromRealm automatically clears the list so no need to call it again explicitly.
Calling clear on RealmResults object will result in deletion of data from database, can cause unexpected behaviour if the user is not aware so API is being modified to avoid unexpected behaviours.

What is the best practice to chain realm queries in Android?

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.

Android -room persistent library - DAO calls are async, therefore how to get callback?

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

Accessing RealmDB from multiple Threads

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

Chaining requests in Retrofit + RxJava

I have 2 APIs that I want to make request to in sequence and store their data in SQLite.
First I want to make request to API A and store its data in SQL table a. Then make request to API B and store its data in table b and some data in table a_b. The data stored in a_b is from request B alone.
How can I do this using RxJava. I read somewhere about using flatMap for this, something like this
apiService.A()
// store in DB here? How? maybe use map()?
.flatMap(modelA -> {
// or maybe store modelA in DB here?
return apiService.B().map(modelB -> {
storeInDB()l // store B here ?
return modelB;
});
});
If I wasn't using lambda functions, this would look as ugly as normal nested calls. Is this a better way to do it?
I don't think using map operator is the best way to go with things like storing the result of the api call.
What I like to do is to separate those things inside doOnNext operators. So your example would be something like this:
apiService.A()
.doOnNext(modelA -> db.store(modelA))
.flatMap(modelA -> apiService.B())
.doOnNext(modelB -> db.store(modelB));
(add necessary observeOn and subscribeOn yourself, exactly like you need them)
Yes, you can use flatmap for this exact purpose. See the below example (Assuming your service A returns Observable<FooA> and service B returns Observable<FooB>)
api.serviceA()
.flatMap(new Func1<FooA, Observable<FooB>>() {
#Override
public Observable<FooB> call(FooA fooA) {
// code to save data from service A to db
// call service B
return api.serviceB();
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<FooB>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(FooB fooB) {
// code to save data from service B to db
}
});

Categories

Resources