Android and Firebase Firestore: Wait nested tasks to complete - android

I think it is a simple problem, I have 3 main collections: collection_A, collection_B and collection_C. The problem is: I need a document "A" of collection_A. The document "A" contains an array attribute "collection_B_uids", after get document "A" I need to get every document of collection_B that is in collection_B_uids, so I did this:
public Task foo() {
return FirebaseFirestore.getInstance().collection("collection_A").document("A").get()
.addOnSuccessListener(docA -> {
List<String> collection_B_uids = docA.get("collection_B_uids");
// ...
for (String uid : collection_B_uids) {
FirebaseFirestore.getInstance().collection("collection_B").document(uid).get()
.addOnSuccessListener(docB -> {
// ...
});
}
});
}
Works fine, but then I need to access some documents of collection_C by another array attribute in every document of collection_B. I want to use the return of foo like that:
foo().addOnSuccessListener(...)
And that final addOnSuccessListener should be executed after all nested tasks are completed. I already use Tasks.whenAllSuccess inside the main Task, but it didn't worked.
My guesses:
a) create a recursive function using 2 listener at time;
b) use continueWithTask or continueWith (prefered);
c) create a global AsyncTask an then use Tasks.await (please, don't).
Ps: I'm not english native

Related

Does Tasks.whenAllSuccess guarantee the order in which I pass tasks to it?

I am using Cloud Firestore in an Android app.
I create 2 queries, one whereLessThan and one whereGreaterThan to effectively create an != query.
I order the results for each individual query, but when I run them separately, I might get one or another returned first, depending on which is faster.
When I combine 2 queries into 1 and then use the Tasks.whenAllSuccess method to get 1 Task and add 1 OnSuccessListener to it, will it keep up the order of results in which I pass my tasks?
public void loadNotes(View v) {
Task task1 = notebookRef
.whereLessThan("priority", 2)
.orderBy("priority")
.get();
Task task2 = notebookRef
.whereGreaterThan("priority", 2)
.orderBy("priority")
.get();
Task finalTask = Tasks.whenAllSuccess(task1, task2).addOnSuccessListener(new OnSuccessListener<List<Object>>() {
#Override
public void onSuccess(List<Object> objects) {
//will my results be ordered prioty 1 - 3 - 4 - 5....?
}
});
}
Tasks.whenAllSuccess (and other whenAll methods) will deliver the results from the tasks to the callback in a List in the order they were passed to whenAllSuccess. If you need some other way of getting the results, you can maintain some data structure with the original Task objects, then query them as you want.

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

How to create Observable for async response?

I have one async-method, like this:
void getPlaceInfo(Place place, PlaceCallback callback)
For example my PlaceCallback has one method:
void success(InfoPlace place);
I want create Observable for waiting response from two requests:
getPlaceInfo(...); // first call
getPlaceInfo(...); // second call
And then I want to get both response at the same time.
Can I make it?
So you need to combine 2 callbacks to evaluate a function like:
Response computeResponse(InfoPlace infoPlace, InfoPlace infoPlace2) {
...
}
And you want to use Rx for this. There is two problem here; wrapping the callback method into Observable, and combine them.
You can't just instantiate an Observable and call myObservable.send(value) or myObservable.next(value). The first solution is to use a Subject. Another solution (the one below) is to create the observable with Observable.create(). You call the callback method and create the listener inside the method Observable.create(), because it's inside Observable.create() that you can call onSuccess() method, the method who told the Observable to pass down a value:
Single<InfoPlace> getInfoPlaceSingle(Place place) {
return Single.create(e -> getPlaceInfo(place, new PlaceCallback() {
#Override
public void success(InfoPlace infoPlace) {
e.onSuccess(infoPlace);
}
}));
}
Or with Java 8:
Single<InfoPlace> getInfoPlaceSingle(Place place) {
return Single.create(e -> getPlaceInfo(place, e::onSuccess));
}
Note, I used Single over Observable, since you await only one value. The interest is a more expressive contract for your function.
Wrapping things into Observable is a pain, but now you are in the reactive realm where Rx provide you a complete toolbox to deal with them. The second part - combining the observables - is easy:
Single.zip(getInfoPlaceSingle(place1), getInfoPlaceSingle(place2), this::computeResponse);
Wrap your async calls using Observable.fromEmitter() and you can then use Observable.zip() to combine the calls.

Combining API calls with RX Java

I'm new to RXJava and i'm having trouble understanding how to chain together the result of API calls.
I'm making two API calls using retrofit, A and B, which both return an observable List of objects. Both API calls are independent so I want to make both at the same time, but to achieve my final result, I need to first take the result of A, do some work, then combine that with the result of B to populate my list adapter.
Make API Call A
Make API Call B
Take A's result and create result X
Take Result of B + X and populate adapter
#GET("/{object_id}/object_a")
Observable<List<Object_A>> getObjectAList(
#Path("object_id") long object_id);
#GET("/{object_id}/object_b")
Observable<List<Object_B>> getObjectBList(
#Path("object_id") long object_id);
This is where I get lost trying to use RX java. I can take the result of api call A and do my work
but I'm not sure how to take the result I just generated and combine it with API Call B.
aService. getObjectAList(object_a.getID())
.subscribeOn(AndroidSchedulers.mainThread())
.observeOn(AndroidSchedulers.main)
.subscribe(new Action1<List<Object_A>>() {
#Override
public void call(List<Section> sections) {
// Do Stuff Here...
// Now i need to take this result and combine it with API Call B...
}
});
I want to make both API calls at the same time, but i'm not sure how to chain together and combine API calls. Any help is appreciative.
Something like this?
Observable
// make api call for A list and B list
.combineLatest(getObjectAList(), getObjectBList(), new Func2<List<Object_A>, List<Object_B>, Object>() {
#Override
public Object call(List<Object_A> o, List<Object_B> o2) {
// Do what you need to do in the background thread
List x = createX(o);
List y = createY(x, o2);
return y;
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<Object>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(Object y) {
// UI thread, do what you need e.g. renders the list
mAdapter.setList(y);
}
});
Taking care of replacing the proper types should bring you quite close to the solution.
The question is : how would you combine results ?
Building a new result from List and List ? Combine A objects with B objects ?
Answer to this question help to find the right operator for your problem.
A simple example of combining results can be this :
getObjectAList().zipWith(getObjectBList(), (aList, bList) -> // combine both list to build a new result)).subscribe()
You can combine elements of the list too with another operator (combineLatest for example)
aObs = getObjectAList().flatMap(Observable::from);
bObs = getObjectBList().flatMap(Observable::from);
Observable.combineLatest(aObs, bObs, (a,b) -> // combine a object with b object).subscribe();
For all of this examples above, requests will be done in parallel by retrofit.
I'd probably do something like the following
Observable convertedObservable = getObjectAList
.map(object_as -> convertAToX(object_as));
Observable.combineLatest(convertedObservable, getObjectBList, (listx, listb) -> {
return listx.addAll(listb);
}).subscribeOn(AndroidSchedulers.mainThread())
.observeOn(AndroidSchedulers.main)
.subscribe(r -> {
setAdapterWith(r);
});
Keep in mind this is using lambdas instead of anonymous classes but you should get the gist. Map is a great way of converting one object type to another (results of A to Results of X). So you can decide how convertAToX method works for you. Then you can use combineLastest on the converted A-X and B to return the list of R which updates your adapter
Ideally this is all in a ViewModel of some kind where getObjectAList and getObjectBList can me mocked on with Mock observables and you can test all the logic easily :)

Categories

Resources