Android and LiveQuery - Advice/best-practise for filtering

Hello,

I have activities running LiveQuery class, and then emmiting documents.

My documents contains 3 keys:

{
       "key1": "xxx",
       "key2": "yyy",
       "key3": "zzz",
       "owner": "JohnDoe",
       "type": "profile",     
}

I want to test if either value of key1, key2 OR key3 of this doc is equals to localkey1, localkey2 OR localkey3…before emitting the doc.

For example, if key1 is equals to localkey3, it should return true and then i emit the doc.

Whats the best way to do this ?

regards,

Sorry, you can’t do that. The map function runs while the view is being indexed, before the query. And multiple queries will use the same index. That’s why the map function has to be a pure function with no external state and no side effects.

So when you design the map function you have to think of handling all queries, not a specific query. In your case you want to put all three keys into the index so they can be searched for. The way to implement this is to make the map function emit all three of the keys —

emit(doc.key1, ...);
emit(doc.key2, ...);
emit(doc.key3, ...);

Then set the query’s keys property to an array [localkey1, localkey2, localkey3]. That will find all docs with any of those local key strings. You may get duplicate rows in the result if one of the documents contains more than one of the local keys; you’ll have to filter those out yourself.

Thank you for these explanations.
I managed to emit my document this way, and i adapted my code.

I have a “code design” question for you, if you can help…

If one of my local variable change (localKey1 for example), is it okay to reinitialize query entirely ?
By recalling my method below " initializeQuery() ".

At the moment i call my query like that :

protected static void initializeQuery() {
    
liveQuery = getQuery(database, mood1, mood2, mood3).toLiveQuery();

liveQuery.addChangeListener(new LiveQuery.ChangeListener() {
    @Override
    public void changed(LiveQuery.ChangeEvent event) {

        if (event.getSource().equals(liveQuery)) {
           
            QueryEnumerator result = event.getRows();
            for (Iterator<QueryRow> it = result; it != null && it.hasNext(); ) {
                QueryRow row = it.next();
.......
.......
.......   **********HERE i do my "filtering of result""**************

My getQuery method is

 public static Query getQuery(Database database, String localKey1, String localKey2, String localKey3) {

    /* --------------- CREATE A VIEW   --------------*/

    final com.couchbase.lite.View view = database.getView("matchView");

    if (view.getMap() == null) {
        final Mapper map = new Mapper() {
            @Override
            public void map(Map<String, Object> document, Emitter emitter) {

                if((boolean) document.get("isOnline"))
                {
                    if (!(boolean) document.get("isEnabled"))   
                    {
                        if ((document.get("numberOfKey")).equals("3"))
                        {
                            emitter.emit(document.get("key1"), document.get("userId")) ;
                            emitter.emit(document.get("key2"), document.get("userId")) ;
                            emitter.emit(document.get("key3"), document.get("userId")) ;
                        }
                        else if ((document.get("numberOfKey")).equals("2"))
                        {
                            emitter.emit(document.get("key1"), document.get("userId")) ;
                            emitter.emit(document.get("key2"), document.get("userId")) ;
                        }
                        else if ((document.get("numberOfKey")).equals("1"))
                        {
                            emitter.emit(document.get("key3"), document.get("userId")) ;
                        }
                    }
                }
            }
        };
        view.setMap(map, "1");
    }

/* ---------------CREATE the Query Object --------------*/

Query query = view.createQuery();
query.setDescending(true);
java.util.List<Object> keys = new ArrayList<Object>();
keys.add(key1);
keys.add(key2);
keys.add(key3);
query.setKeys(keys);

return query;
}

So is my code correct ? Particulary about using static methods and compare string in the view ?

Thanks for you assitance !

Yes, if you’re changing parameters you have to run the query again.

@jens

Okay so if i have 3 static LiveQuery with each of them mapping its own view (like my code above) :

  • 1 in MainActivity - Static - called like above - This LiveQuery runs continuously

  • 2 others in Fragment class (com.android.fragment) - called in onCreateView (com.android.fragment.onCreateView)

Is it a design that works ?

I mean having 2 static Livequery instance running on my database from different Object(Fragment or Activity) of my App is not a problem ?

Regards,

You can have as many LiveQueries as you want, although it’ll eventually slow down the app because each one will keep re-running the query after the database changes. For efficiency it’s best to have them running only when you’re actively using their results, like when a table view driven by one is being displayed.

I’m not an Android programmer so I don’t know anything about fragments or activities. Maybe @hideki can answer this.

You partially answered anyway, thanks.

In fact on 4 documents containing the Keys i give to my query, it returns me only 2 document…
Even when restarting the livequery these 2 docs are still not there.

Any idea on this kind of issue ? (I checked in my bucket, and Keys are present in the doc).

@jens :

I solved the issue, i was using Log class inside the map function and also i renamed the view differently on each query.

Since then, my query results are ok.

Regards,

Hello @jens,

Indeed, i have duplicated results :wink:

I am passing the live query to a Document Adapter which populates a listView (same was as your app “ToDoLite”) like this :

DocumentAdapter mAdapter = new DocumentAdapter(getActivity(), query);

Where would you advice me to filter results to avoid duplicates so ?

Is there no way to do track duplicates inside my database View or inside my Query ?

Where would you advice me to filter results to avoid duplicates so ?

Well, you’d iterate over the returned query rows and skip the ones that are from the same document.

Is there no way to do track duplicates inside my database View or inside my Query ?

You can use grouping to merge duplicate keys, but what you’re getting are multiple keys emitted from the same document. There’s no automatic mechanism to merge those.

Thanks, i did that yes. and how to delete a row from QueryRow or QueryEnumerator ?

You can’t; they’re immutable. I did say skip, not delete.

Please how to do this ?

Here is my code so far…i detect duplicate like this.

Code REMOVED - It contained defects and wasn't working.

I don’t understand what you’re asking for … You’re already detecting the duplicates in the code you showed above. (Although using an ArrayList is going to be really inefficient! Use a HashMap instead.)

Basically wherever you’re iterating the results to do whatever you need to do, check for duplicates that way and skip them.

@jens : Hello,

Still about filtering :

In somes documents, i have a Key that contain embedded JSON. Example :

{

“docId”: “…”

“books”: {
“id1”: {
“title”: aaaa",
“author”: “bbbb”,
“genre”: “comic”,
}
“id2”: {
“title”: “dddd”,
“author”: “eeee”,
“genre”: “comic”,
}
}
}

Is it possible to filter inside my query with for example : query.setKeys(“comic”) ?

Or i will have to do it inside the iteration ?

Regards,

Sure, just do something like (pseudocode):

for (bookID in doc.books) {
    book = doc.books[bookID]
    emit(book.genre, null)
}

Now you have an index of the genres of all books. If you want to get a list of genres, set the groupLevel of the query to 1 so it will aggregate all the identical genre keys together.

Thanks for your reply,

If i understand correctly :

1.Make a list of the “books” value of the document

2.I am not sure of the variable type of book in your example. Is it a Document, an Array, a Jsonobject ?

3.Also in the emitter, your “book.genre” translated in my scenario would be “comic” so ?

“book” in the example is a Map, in Java terms. It’s a JSON object nested inside your document.

Also in the emitter, your “book.genre” translated in my scenario would be “comic” so ?

I don’t understand what you’re asking. Could you describe in detail what task you’re trying to accomplish?

You wrote this…

Si if book is a map, shouldn’t it be rather :

emit(book.get("genre"), null)

?

I said it was pseudocode.